spirv-fuzz: Add push id through variable transformation (#3359)
Makes an id synonym by storing an id to a new variable and then
loading it back from that variable.
Fixes #3192.
diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt
index 6582927..1fadd8f 100644
--- a/source/fuzz/CMakeLists.txt
+++ b/source/fuzz/CMakeLists.txt
@@ -63,6 +63,7 @@
fuzzer_pass_outline_functions.h
fuzzer_pass_permute_blocks.h
fuzzer_pass_permute_function_parameters.h
+ fuzzer_pass_push_ids_through_variables.h
fuzzer_pass_split_blocks.h
fuzzer_pass_swap_commutable_operands.h
fuzzer_pass_toggle_access_chain_instruction.h
@@ -111,6 +112,7 @@
transformation_move_block_down.h
transformation_outline_function.h
transformation_permute_function_parameters.h
+ transformation_push_id_through_variable.h
transformation_replace_boolean_constant_with_constant_binary.h
transformation_replace_constant_with_uniform.h
transformation_replace_id_with_synonym.h
@@ -159,6 +161,7 @@
fuzzer_pass_outline_functions.cpp
fuzzer_pass_permute_blocks.cpp
fuzzer_pass_permute_function_parameters.cpp
+ fuzzer_pass_push_ids_through_variables.cpp
fuzzer_pass_split_blocks.cpp
fuzzer_pass_swap_commutable_operands.cpp
fuzzer_pass_toggle_access_chain_instruction.cpp
@@ -206,6 +209,7 @@
transformation_move_block_down.cpp
transformation_outline_function.cpp
transformation_permute_function_parameters.cpp
+ transformation_push_id_through_variable.cpp
transformation_replace_boolean_constant_with_constant_binary.cpp
transformation_replace_constant_with_uniform.cpp
transformation_replace_id_with_synonym.cpp
diff --git a/source/fuzz/fuzzer.cpp b/source/fuzz/fuzzer.cpp
index 3343abc..801fae3 100644
--- a/source/fuzz/fuzzer.cpp
+++ b/source/fuzz/fuzzer.cpp
@@ -46,6 +46,7 @@
#include "source/fuzz/fuzzer_pass_outline_functions.h"
#include "source/fuzz/fuzzer_pass_permute_blocks.h"
#include "source/fuzz/fuzzer_pass_permute_function_parameters.h"
+#include "source/fuzz/fuzzer_pass_push_ids_through_variables.h"
#include "source/fuzz/fuzzer_pass_split_blocks.h"
#include "source/fuzz/fuzzer_pass_swap_commutable_operands.h"
#include "source/fuzz/fuzzer_pass_toggle_access_chain_instruction.h"
@@ -249,6 +250,9 @@
MaybeAddPass<FuzzerPassPermuteFunctionParameters>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
+ MaybeAddPass<FuzzerPassPushIdsThroughVariables>(
+ &passes, ir_context.get(), &transformation_context, &fuzzer_context,
+ transformation_sequence_out);
MaybeAddPass<FuzzerPassSplitBlocks>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
diff --git a/source/fuzz/fuzzer_context.cpp b/source/fuzz/fuzzer_context.cpp
index 1779709..923c255 100644
--- a/source/fuzz/fuzzer_context.cpp
+++ b/source/fuzz/fuzzer_context.cpp
@@ -62,6 +62,7 @@
const std::pair<uint32_t, uint32_t> kChanceOfObfuscatingConstant = {10, 90};
const std::pair<uint32_t, uint32_t> kChanceOfOutliningFunction = {10, 90};
const std::pair<uint32_t, uint32_t> kChanceOfPermutingParameters = {30, 90};
+const std::pair<uint32_t, uint32_t> kChanceOfPushingIdThroughVariable = {5, 50};
const std::pair<uint32_t, uint32_t> kChanceOfReplacingIdWithSynonym = {10, 90};
const std::pair<uint32_t, uint32_t> kChanceOfSplittingBlock = {40, 95};
const std::pair<uint32_t, uint32_t> kChanceOfTogglingAccessChainInstruction = {
@@ -157,6 +158,8 @@
ChooseBetweenMinAndMax(kChanceOfOutliningFunction);
chance_of_permuting_parameters_ =
ChooseBetweenMinAndMax(kChanceOfPermutingParameters);
+ chance_of_pushing_id_through_variable_ =
+ ChooseBetweenMinAndMax(kChanceOfPushingIdThroughVariable);
chance_of_replacing_id_with_synonym_ =
ChooseBetweenMinAndMax(kChanceOfReplacingIdWithSynonym);
chance_of_splitting_block_ = ChooseBetweenMinAndMax(kChanceOfSplittingBlock);
diff --git a/source/fuzz/fuzzer_context.h b/source/fuzz/fuzzer_context.h
index dd19d9a..b1392e7 100644
--- a/source/fuzz/fuzzer_context.h
+++ b/source/fuzz/fuzzer_context.h
@@ -179,6 +179,9 @@
uint32_t GetChanceOfPermutingParameters() {
return chance_of_permuting_parameters_;
}
+ uint32_t GetChanceOfPushingIdThroughVariable() {
+ return chance_of_pushing_id_through_variable_;
+ }
uint32_t GetChanceOfReplacingIdWithSynonym() {
return chance_of_replacing_id_with_synonym_;
}
@@ -263,6 +266,7 @@
uint32_t chance_of_obfuscating_constant_;
uint32_t chance_of_outlining_function_;
uint32_t chance_of_permuting_parameters_;
+ uint32_t chance_of_pushing_id_through_variable_;
uint32_t chance_of_replacing_id_with_synonym_;
uint32_t chance_of_splitting_block_;
uint32_t chance_of_toggling_access_chain_instruction_;
diff --git a/source/fuzz/fuzzer_pass_push_ids_through_variables.cpp b/source/fuzz/fuzzer_pass_push_ids_through_variables.cpp
new file mode 100644
index 0000000..fcf220a
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_push_ids_through_variables.cpp
@@ -0,0 +1,109 @@
+// Copyright (c) 2020 André Perez Maselco
+//
+// 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 "source/fuzz/fuzzer_pass_push_ids_through_variables.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/instruction_descriptor.h"
+#include "source/fuzz/transformation_push_id_through_variable.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassPushIdsThroughVariables::FuzzerPassPushIdsThroughVariables(
+ opt::IRContext* ir_context, TransformationContext* transformation_context,
+ FuzzerContext* fuzzer_context,
+ protobufs::TransformationSequence* transformations)
+ : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+ transformations) {}
+
+FuzzerPassPushIdsThroughVariables::~FuzzerPassPushIdsThroughVariables() =
+ default;
+
+void FuzzerPassPushIdsThroughVariables::Apply() {
+ ForEachInstructionWithInstructionDescriptor(
+ [this](opt::Function* function, opt::BasicBlock* block,
+ opt::BasicBlock::iterator instruction_iterator,
+ const protobufs::InstructionDescriptor& instruction_descriptor)
+ -> void {
+ assert(instruction_iterator->opcode() ==
+ instruction_descriptor.target_instruction_opcode() &&
+ "The opcode of the instruction we might insert before must be "
+ "the same as the opcode in the descriptor for the instruction");
+
+ // Randomly decide whether to try pushing an id through a variable.
+ if (!GetFuzzerContext()->ChoosePercentage(
+ GetFuzzerContext()->GetChanceOfPushingIdThroughVariable())) {
+ return;
+ }
+
+ // The block containing the instruction we are going to insert before
+ // must be reachable.
+ if (!fuzzerutil::BlockIsReachableInItsFunction(GetIRContext(), block)) {
+ return;
+ }
+
+ // It must be valid to insert OpStore and OpLoad instructions
+ // before the instruction to insert before.
+ if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(
+ SpvOpStore, instruction_iterator) ||
+ !fuzzerutil::CanInsertOpcodeBeforeInstruction(
+ SpvOpLoad, instruction_iterator)) {
+ return;
+ }
+
+ // Randomly decides whether a global or local variable will be added.
+ auto variable_storage_class = GetFuzzerContext()->ChooseEven()
+ ? SpvStorageClassPrivate
+ : SpvStorageClassFunction;
+
+ // Gets the available basic and pointer types.
+ auto basic_type_ids_and_pointers =
+ GetAvailableBasicTypesAndPointers(variable_storage_class);
+ auto& basic_types = basic_type_ids_and_pointers.first;
+ uint32_t basic_type_id =
+ basic_types[GetFuzzerContext()->RandomIndex(basic_types)];
+
+ // Looks for ids that we might wish to consider pushing through a
+ // variable.
+ std::vector<opt::Instruction*> value_instructions =
+ FindAvailableInstructions(
+ function, block, instruction_iterator,
+ [basic_type_id](opt::IRContext* /*unused*/,
+ opt::Instruction* instruction) -> bool {
+ if (!instruction->result_id() || !instruction->type_id()) {
+ return false;
+ }
+ return instruction->type_id() == basic_type_id;
+ });
+
+ if (value_instructions.empty()) {
+ return;
+ }
+
+ // If the pointer type does not exist, then create it.
+ FindOrCreatePointerType(basic_type_id, variable_storage_class);
+
+ // Applies the push id through variable transformation.
+ ApplyTransformation(TransformationPushIdThroughVariable(
+ value_instructions[GetFuzzerContext()->RandomIndex(
+ value_instructions)]
+ ->result_id(),
+ GetFuzzerContext()->GetFreshId(), GetFuzzerContext()->GetFreshId(),
+ variable_storage_class, instruction_descriptor));
+ });
+}
+
+} // namespace fuzz
+} // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_push_ids_through_variables.h b/source/fuzz/fuzzer_pass_push_ids_through_variables.h
new file mode 100644
index 0000000..ffec89e
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_push_ids_through_variables.h
@@ -0,0 +1,41 @@
+// Copyright (c) 2020 André Perez Maselco
+//
+// 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 SOURCE_FUZZ_FUZZER_PASS_PUSH_IDS_THROUGH_VARIABLES_H_
+#define SOURCE_FUZZ_FUZZER_PASS_PUSH_IDS_THROUGH_VARIABLES_H_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// Adds instructions to the module that store existing ids into fresh variables
+// and immediately load from said variables into new ids, thus creating synonyms
+// between the existing and fresh ids.
+class FuzzerPassPushIdsThroughVariables : public FuzzerPass {
+ public:
+ FuzzerPassPushIdsThroughVariables(
+ opt::IRContext* ir_context, TransformationContext* transformation_context,
+ FuzzerContext* fuzzer_context,
+ protobufs::TransformationSequence* transformations);
+
+ ~FuzzerPassPushIdsThroughVariables();
+
+ void Apply() override;
+};
+
+} // namespace fuzz
+} // namespace spvtools
+
+#endif // SOURCE_FUZZ_FUZZER_PASS_PUSH_IDS_THROUGH_VARIABLES_H_
diff --git a/source/fuzz/protobufs/spvtoolsfuzz.proto b/source/fuzz/protobufs/spvtoolsfuzz.proto
index 775b2ad..645b733 100644
--- a/source/fuzz/protobufs/spvtoolsfuzz.proto
+++ b/source/fuzz/protobufs/spvtoolsfuzz.proto
@@ -375,6 +375,7 @@
TransformationAddConstantNull add_constant_null = 44;
TransformationComputeDataSynonymFactClosure compute_data_synonym_fact_closure = 45;
TransformationAdjustBranchWeights adjust_branch_weights = 46;
+ TransformationPushIdThroughVariable push_id_through_variable = 47;
// Add additional option using the next available number.
}
}
@@ -983,6 +984,30 @@
}
+message TransformationPushIdThroughVariable {
+
+ // A transformation that makes |value_synonym_id| and |value_id| to be
+ // synonymous by storing |value_id| into |variable_id| and
+ // loading |variable_id| to |value_synonym_id|.
+
+ // The value to be stored.
+ uint32 value_id = 1;
+
+ // A fresh id for the result of the load instruction.
+ uint32 value_synonym_id = 2;
+
+ // A fresh id for the variable to be stored to.
+ uint32 variable_id = 3;
+
+ // The variable storage class (global or local).
+ uint32 variable_storage_class = 4;
+
+ // A descriptor for an instruction which the new OpStore
+ // and OpLoad instructions might be inserted before.
+ InstructionDescriptor instruction_descriptor = 5;
+
+}
+
message TransformationReplaceBooleanConstantWithConstantBinary {
// A transformation to capture replacing a use of a boolean constant with
diff --git a/source/fuzz/transformation.cpp b/source/fuzz/transformation.cpp
index 8b84169..42168cb 100644
--- a/source/fuzz/transformation.cpp
+++ b/source/fuzz/transformation.cpp
@@ -51,6 +51,7 @@
#include "source/fuzz/transformation_move_block_down.h"
#include "source/fuzz/transformation_outline_function.h"
#include "source/fuzz/transformation_permute_function_parameters.h"
+#include "source/fuzz/transformation_push_id_through_variable.h"
#include "source/fuzz/transformation_replace_boolean_constant_with_constant_binary.h"
#include "source/fuzz/transformation_replace_constant_with_uniform.h"
#include "source/fuzz/transformation_replace_id_with_synonym.h"
@@ -163,6 +164,9 @@
kPermuteFunctionParameters:
return MakeUnique<TransformationPermuteFunctionParameters>(
message.permute_function_parameters());
+ case protobufs::Transformation::TransformationCase::kPushIdThroughVariable:
+ return MakeUnique<TransformationPushIdThroughVariable>(
+ message.push_id_through_variable());
case protobufs::Transformation::TransformationCase::
kReplaceBooleanConstantWithConstantBinary:
return MakeUnique<TransformationReplaceBooleanConstantWithConstantBinary>(
diff --git a/source/fuzz/transformation_push_id_through_variable.cpp b/source/fuzz/transformation_push_id_through_variable.cpp
new file mode 100644
index 0000000..c691148
--- /dev/null
+++ b/source/fuzz/transformation_push_id_through_variable.cpp
@@ -0,0 +1,159 @@
+// Copyright (c) 2020 André Perez Maselco
+//
+// 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 "source/fuzz/transformation_push_id_through_variable.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/instruction_descriptor.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationPushIdThroughVariable::TransformationPushIdThroughVariable(
+ const spvtools::fuzz::protobufs::TransformationPushIdThroughVariable&
+ message)
+ : message_(message) {}
+
+TransformationPushIdThroughVariable::TransformationPushIdThroughVariable(
+ uint32_t value_id, uint32_t value_synonym_id, uint32_t variable_id,
+ uint32_t variable_storage_class,
+ const protobufs::InstructionDescriptor& instruction_descriptor) {
+ message_.set_value_id(value_id);
+ message_.set_value_synonym_id(value_synonym_id);
+ message_.set_variable_id(variable_id);
+ message_.set_variable_storage_class(variable_storage_class);
+ *message_.mutable_instruction_descriptor() = instruction_descriptor;
+}
+
+bool TransformationPushIdThroughVariable::IsApplicable(
+ opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
+ // |message_.value_synonym_id| and |message_.variable_id| must be fresh.
+ if (!fuzzerutil::IsFreshId(ir_context, message_.value_synonym_id()) ||
+ !fuzzerutil::IsFreshId(ir_context, message_.variable_id())) {
+ return false;
+ }
+
+ // The instruction to insert before must be defined.
+ auto instruction_to_insert_before =
+ FindInstruction(message_.instruction_descriptor(), ir_context);
+ if (!instruction_to_insert_before) {
+ return false;
+ }
+
+ // It must be valid to insert the OpStore and OpLoad instruction before it.
+ if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(
+ SpvOpStore, instruction_to_insert_before) ||
+ !fuzzerutil::CanInsertOpcodeBeforeInstruction(
+ SpvOpLoad, instruction_to_insert_before)) {
+ return false;
+ }
+
+ // The instruction to insert before must belong to a reachable block.
+ auto basic_block = ir_context->get_instr_block(instruction_to_insert_before);
+ if (!fuzzerutil::BlockIsReachableInItsFunction(ir_context, basic_block)) {
+ return false;
+ }
+
+ // The value instruction must be defined and have a type.
+ auto value_instruction =
+ ir_context->get_def_use_mgr()->GetDef(message_.value_id());
+ if (!value_instruction || !value_instruction->type_id()) {
+ return false;
+ }
+
+ // A pointer type instruction pointing to the value type must be defined.
+ auto pointer_type_id = fuzzerutil::MaybeGetPointerType(
+ ir_context, value_instruction->type_id(),
+ static_cast<SpvStorageClass>(message_.variable_storage_class()));
+ if (!pointer_type_id) {
+ return false;
+ }
+
+ // |message_.variable_storage_class| must be private or function.
+ assert((message_.variable_storage_class() == SpvStorageClassPrivate ||
+ message_.variable_storage_class() == SpvStorageClassFunction) &&
+ "The variable storage class must be private or function.");
+
+ // |message_.value_id| must be available at the insertion point.
+ return fuzzerutil::IdIsAvailableBeforeInstruction(
+ ir_context, instruction_to_insert_before, message_.value_id());
+}
+
+void TransformationPushIdThroughVariable::Apply(
+ opt::IRContext* ir_context,
+ TransformationContext* transformation_context) const {
+ auto value_instruction =
+ ir_context->get_def_use_mgr()->GetDef(message_.value_id());
+
+ // A pointer type instruction pointing to the value type must be defined.
+ auto pointer_type_id = fuzzerutil::MaybeGetPointerType(
+ ir_context, value_instruction->type_id(),
+ static_cast<SpvStorageClass>(message_.variable_storage_class()));
+ assert(pointer_type_id && "The required pointer type must be available.");
+
+ // Adds whether a global or local variable.
+ fuzzerutil::UpdateModuleIdBound(ir_context, message_.variable_id());
+ if (message_.variable_storage_class() == SpvStorageClassPrivate) {
+ ir_context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
+ ir_context, SpvOpVariable, pointer_type_id, message_.variable_id(),
+ opt::Instruction::OperandList(
+ {{SPV_OPERAND_TYPE_STORAGE_CLASS, {SpvStorageClassPrivate}}})));
+ } else {
+ ir_context
+ ->get_instr_block(
+ FindInstruction(message_.instruction_descriptor(), ir_context))
+ ->GetParent()
+ ->begin()
+ ->begin()
+ ->InsertBefore(MakeUnique<opt::Instruction>(
+ ir_context, SpvOpVariable, pointer_type_id, message_.variable_id(),
+ opt::Instruction::OperandList({{SPV_OPERAND_TYPE_STORAGE_CLASS,
+ {SpvStorageClassFunction}}})));
+ }
+
+ // Stores value id to variable id.
+ FindInstruction(message_.instruction_descriptor(), ir_context)
+ ->InsertBefore(MakeUnique<opt::Instruction>(
+ ir_context, SpvOpStore, 0, 0,
+ opt::Instruction::OperandList(
+ {{SPV_OPERAND_TYPE_ID, {message_.variable_id()}},
+ {SPV_OPERAND_TYPE_ID, {message_.value_id()}}})));
+
+ // Loads variable id to value synonym id.
+ fuzzerutil::UpdateModuleIdBound(ir_context, message_.value_synonym_id());
+ FindInstruction(message_.instruction_descriptor(), ir_context)
+ ->InsertBefore(MakeUnique<opt::Instruction>(
+ ir_context, SpvOpLoad, value_instruction->type_id(),
+ message_.value_synonym_id(),
+ opt::Instruction::OperandList(
+ {{SPV_OPERAND_TYPE_ID, {message_.variable_id()}}})));
+
+ ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
+
+ // Adds the fact that |message_.value_synonym_id|
+ // and |message_.value_id| are synonymous.
+ transformation_context->GetFactManager()->AddFactDataSynonym(
+ MakeDataDescriptor(message_.value_synonym_id(), {}),
+ MakeDataDescriptor(message_.value_id(), {}), ir_context);
+}
+
+protobufs::Transformation TransformationPushIdThroughVariable::ToMessage()
+ const {
+ protobufs::Transformation result;
+ *result.mutable_push_id_through_variable() = message_;
+ return result;
+}
+
+} // namespace fuzz
+} // namespace spvtools
diff --git a/source/fuzz/transformation_push_id_through_variable.h b/source/fuzz/transformation_push_id_through_variable.h
new file mode 100644
index 0000000..e685ff8
--- /dev/null
+++ b/source/fuzz/transformation_push_id_through_variable.h
@@ -0,0 +1,64 @@
+// Copyright (c) 2020 André Perez Maselco
+//
+// 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 SOURCE_FUZZ_TRANSFORMATION_PUSH_ID_THROUGH_VARIABLE_H_
+#define SOURCE_FUZZ_TRANSFORMATION_PUSH_ID_THROUGH_VARIABLE_H_
+
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/fuzz/transformation.h"
+#include "source/fuzz/transformation_context.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace fuzz {
+
+class TransformationPushIdThroughVariable : public Transformation {
+ public:
+ explicit TransformationPushIdThroughVariable(
+ const protobufs::TransformationPushIdThroughVariable& message);
+
+ TransformationPushIdThroughVariable(
+ uint32_t value_id, uint32_t value_synonym_fresh_id,
+ uint32_t variable_fresh_id, uint32_t variable_storage_class,
+ const protobufs::InstructionDescriptor& instruction_descriptor);
+
+ // - |message_.value_id| must be an instruction result id that has the same
+ // type as the pointee type of |message_.pointer_id|
+ // - |message_.value_synonym_id| must be fresh
+ // - |message_.variable_id| must be fresh
+ // - |message_.variable_storage_class| must be either StorageClassPrivate or
+ // StorageClassFunction
+ // - |message_.instruction_descriptor| must identify an instruction
+ // which it is valid to insert the OpStore and OpLoad instructions before it
+ // and must be belongs to a reachable block.
+ bool IsApplicable(
+ opt::IRContext* ir_context,
+ const TransformationContext& transformation_context) const override;
+
+ // Stores |value_id| to |variable_id|, loads |variable_id| to
+ // |value_synonym_id| and adds the fact that |value_synonym_id| and |value_id|
+ // are synonymous.
+ void Apply(opt::IRContext* ir_context,
+ TransformationContext* transformation_context) const override;
+
+ protobufs::Transformation ToMessage() const override;
+
+ private:
+ protobufs::TransformationPushIdThroughVariable message_;
+};
+
+} // namespace fuzz
+} // namespace spvtools
+
+#endif // SOURCE_FUZZ_TRANSFORMATION_PUSH_ID_THROUGH_VARIABLE_H_
diff --git a/test/fuzz/CMakeLists.txt b/test/fuzz/CMakeLists.txt
index dca142a..624eab3 100644
--- a/test/fuzz/CMakeLists.txt
+++ b/test/fuzz/CMakeLists.txt
@@ -58,6 +58,7 @@
transformation_move_block_down_test.cpp
transformation_outline_function_test.cpp
transformation_permute_function_parameters_test.cpp
+ transformation_push_id_through_variable_test.cpp
transformation_replace_boolean_constant_with_constant_binary_test.cpp
transformation_replace_constant_with_uniform_test.cpp
transformation_replace_id_with_synonym_test.cpp
diff --git a/test/fuzz/transformation_push_id_through_variable_test.cpp b/test/fuzz/transformation_push_id_through_variable_test.cpp
new file mode 100644
index 0000000..e00ee3a
--- /dev/null
+++ b/test/fuzz/transformation_push_id_through_variable_test.cpp
@@ -0,0 +1,446 @@
+// Copyright (c) 2020 André Perez Maselco
+//
+// 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 "source/fuzz/transformation_push_id_through_variable.h"
+#include "source/fuzz/instruction_descriptor.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationPushIdThroughVariableTest, IsApplicable) {
+ std::string reference_shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main" %92 %52 %53
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ OpDecorate %92 BuiltIn FragCoord
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypeFloat 32
+ %8 = OpTypeStruct %6 %7
+ %9 = OpTypePointer Function %8
+ %10 = OpTypeFunction %6 %9
+ %14 = OpConstant %6 0
+ %15 = OpTypePointer Function %6
+ %51 = OpTypePointer Private %6
+ %21 = OpConstant %6 2
+ %23 = OpConstant %6 1
+ %24 = OpConstant %7 1
+ %25 = OpTypePointer Function %7
+ %50 = OpTypePointer Private %7
+ %34 = OpTypeBool
+ %35 = OpConstantFalse %34
+ %60 = OpConstantNull %50
+ %61 = OpUndef %51
+ %52 = OpVariable %50 Private
+ %53 = OpVariable %51 Private
+ %80 = OpConstantComposite %8 %21 %24
+ %90 = OpTypeVector %7 4
+ %91 = OpTypePointer Input %90
+ %92 = OpVariable %91 Input
+ %93 = OpConstantComposite %90 %24 %24 %24 %24
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %20 = OpVariable %9 Function
+ %27 = OpVariable %9 Function
+ %22 = OpAccessChain %15 %20 %14
+ %44 = OpCopyObject %9 %20
+ %26 = OpAccessChain %25 %20 %23
+ %29 = OpFunctionCall %6 %12 %27
+ %30 = OpAccessChain %15 %20 %14
+ %45 = OpCopyObject %15 %30
+ %81 = OpCopyObject %9 %27
+ %33 = OpAccessChain %15 %20 %14
+ OpSelectionMerge %37 None
+ OpBranchConditional %35 %36 %37
+ %36 = OpLabel
+ %38 = OpAccessChain %15 %20 %14
+ %40 = OpAccessChain %15 %20 %14
+ %43 = OpAccessChain %15 %20 %14
+ %82 = OpCopyObject %9 %27
+ OpBranch %37
+ %37 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %12 = OpFunction %6 None %10
+ %11 = OpFunctionParameter %9
+ %13 = OpLabel
+ %46 = OpCopyObject %9 %11
+ %16 = OpAccessChain %15 %11 %14
+ %95 = OpCopyObject %8 %80
+ OpReturnValue %21
+ %100 = OpLabel
+ OpUnreachable
+ OpFunctionEnd
+ )";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_4;
+ const auto consumer = nullptr;
+ const auto context =
+ BuildModule(env, consumer, reference_shader, kFuzzAssembleOption);
+
+ FactManager fact_manager;
+ spvtools::ValidatorOptions validator_options;
+ TransformationContext transformation_context(&fact_manager,
+ validator_options);
+
+ // Tests the reference shader validity.
+ ASSERT_TRUE(IsValid(env, context.get()));
+
+ // Tests |value_synonym_id| and |variable_id| are fresh ids.
+ uint32_t value_id = 21;
+ uint32_t value_synonym_id = 62;
+ uint32_t variable_id = 63;
+ uint32_t variable_storage_class = SpvStorageClassPrivate;
+ auto instruction_descriptor =
+ MakeInstructionDescriptor(95, SpvOpReturnValue, 0);
+ auto transformation = TransformationPushIdThroughVariable(
+ value_id, value_synonym_id, variable_id, variable_storage_class,
+ instruction_descriptor);
+ ASSERT_TRUE(
+ transformation.IsApplicable(context.get(), transformation_context));
+
+ // Tests |value_synonym_id| and |variable_id| are non-fresh ids.
+ value_id = 80;
+ value_synonym_id = 60;
+ variable_id = 61;
+ variable_storage_class = SpvStorageClassFunction;
+ instruction_descriptor = MakeInstructionDescriptor(38, SpvOpAccessChain, 0);
+ transformation = TransformationPushIdThroughVariable(
+ value_id, value_synonym_id, variable_id, variable_storage_class,
+ instruction_descriptor);
+ ASSERT_FALSE(
+ transformation.IsApplicable(context.get(), transformation_context));
+
+ // The instruction to insert before is not defined.
+ value_id = 80;
+ value_synonym_id = 62;
+ variable_id = 63;
+ variable_storage_class = SpvStorageClassFunction;
+ instruction_descriptor = MakeInstructionDescriptor(64, SpvOpAccessChain, 0);
+ transformation = TransformationPushIdThroughVariable(
+ value_id, value_synonym_id, variable_id, variable_storage_class,
+ instruction_descriptor);
+ ASSERT_FALSE(
+ transformation.IsApplicable(context.get(), transformation_context));
+
+ // Attempting to insert the store and load instructions
+ // before an OpVariable instruction.
+ value_id = 24;
+ value_synonym_id = 62;
+ variable_id = 63;
+ variable_storage_class = SpvStorageClassFunction;
+ instruction_descriptor = MakeInstructionDescriptor(27, SpvOpVariable, 0);
+ transformation = TransformationPushIdThroughVariable(
+ value_id, value_synonym_id, variable_id, variable_storage_class,
+ instruction_descriptor);
+ ASSERT_FALSE(
+ transformation.IsApplicable(context.get(), transformation_context));
+
+ // The block containing instruction descriptor must be reachable.
+ value_id = 80;
+ value_synonym_id = 62;
+ variable_id = 63;
+ variable_storage_class = SpvStorageClassFunction;
+ instruction_descriptor = MakeInstructionDescriptor(100, SpvOpUnreachable, 0);
+ transformation = TransformationPushIdThroughVariable(
+ value_id, value_synonym_id, variable_id, variable_storage_class,
+ instruction_descriptor);
+ ASSERT_FALSE(
+ transformation.IsApplicable(context.get(), transformation_context));
+
+ // Tests value instruction not available.
+ value_id = 64;
+ value_synonym_id = 62;
+ variable_id = 63;
+ variable_storage_class = SpvStorageClassFunction;
+ instruction_descriptor = MakeInstructionDescriptor(95, SpvOpReturnValue, 0);
+ transformation = TransformationPushIdThroughVariable(
+ value_id, value_synonym_id, variable_id, variable_storage_class,
+ instruction_descriptor);
+ ASSERT_FALSE(
+ transformation.IsApplicable(context.get(), transformation_context));
+
+ // Tests pointer type not available.
+ value_id = 80;
+ value_synonym_id = 62;
+ variable_id = 63;
+ variable_storage_class = SpvStorageClassPrivate;
+ instruction_descriptor = MakeInstructionDescriptor(95, SpvOpReturnValue, 0);
+ transformation = TransformationPushIdThroughVariable(
+ value_id, value_synonym_id, variable_id, variable_storage_class,
+ instruction_descriptor);
+ ASSERT_FALSE(
+ transformation.IsApplicable(context.get(), transformation_context));
+
+ // Tests not a private nor function storage class.
+ value_id = 93;
+ value_synonym_id = 62;
+ variable_id = 63;
+ variable_storage_class = SpvStorageClassInput;
+ instruction_descriptor = MakeInstructionDescriptor(95, SpvOpReturnValue, 0);
+ transformation = TransformationPushIdThroughVariable(
+ value_id, value_synonym_id, variable_id, variable_storage_class,
+ instruction_descriptor);
+#ifndef NDEBUG
+ ASSERT_DEATH(
+ transformation.IsApplicable(context.get(), transformation_context),
+ "The variable storage class must be private or function");
+#endif
+
+ // Tests value instruction not available before instruction.
+ value_id = 95;
+ value_synonym_id = 62;
+ variable_id = 63;
+ variable_storage_class = SpvStorageClassFunction;
+ instruction_descriptor = MakeInstructionDescriptor(40, SpvOpAccessChain, 0);
+ transformation = TransformationPushIdThroughVariable(
+ value_id, value_synonym_id, variable_id, variable_storage_class,
+ instruction_descriptor);
+ ASSERT_FALSE(
+ transformation.IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationPushIdThroughVariableTest, Apply) {
+ std::string reference_shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main" %92 %52 %53
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ OpDecorate %92 BuiltIn FragCoord
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypeFloat 32
+ %8 = OpTypeStruct %6 %7
+ %9 = OpTypePointer Function %8
+ %10 = OpTypeFunction %6 %9
+ %14 = OpConstant %6 0
+ %15 = OpTypePointer Function %6
+ %51 = OpTypePointer Private %6
+ %21 = OpConstant %6 2
+ %23 = OpConstant %6 1
+ %24 = OpConstant %7 1
+ %25 = OpTypePointer Function %7
+ %50 = OpTypePointer Private %7
+ %34 = OpTypeBool
+ %35 = OpConstantFalse %34
+ %60 = OpConstantNull %50
+ %61 = OpUndef %51
+ %52 = OpVariable %50 Private
+ %53 = OpVariable %51 Private
+ %80 = OpConstantComposite %8 %21 %24
+ %90 = OpTypeVector %7 4
+ %91 = OpTypePointer Input %90
+ %92 = OpVariable %91 Input
+ %93 = OpConstantComposite %90 %24 %24 %24 %24
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %20 = OpVariable %9 Function
+ %27 = OpVariable %9 Function
+ %22 = OpAccessChain %15 %20 %14
+ %44 = OpCopyObject %9 %20
+ %26 = OpAccessChain %25 %20 %23
+ %29 = OpFunctionCall %6 %12 %27
+ %30 = OpAccessChain %15 %20 %14
+ %45 = OpCopyObject %15 %30
+ %81 = OpCopyObject %9 %27
+ %33 = OpAccessChain %15 %20 %14
+ OpSelectionMerge %37 None
+ OpBranchConditional %35 %36 %37
+ %36 = OpLabel
+ %38 = OpAccessChain %15 %20 %14
+ %40 = OpAccessChain %15 %20 %14
+ %43 = OpAccessChain %15 %20 %14
+ %82 = OpCopyObject %9 %27
+ OpBranch %37
+ %37 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %12 = OpFunction %6 None %10
+ %11 = OpFunctionParameter %9
+ %13 = OpLabel
+ %46 = OpCopyObject %9 %11
+ %16 = OpAccessChain %15 %11 %14
+ %95 = OpCopyObject %8 %80
+ OpReturnValue %21
+ OpFunctionEnd
+ )";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_4;
+ const auto consumer = nullptr;
+ const auto context =
+ BuildModule(env, consumer, reference_shader, kFuzzAssembleOption);
+
+ FactManager fact_manager;
+ spvtools::ValidatorOptions validator_options;
+ TransformationContext transformation_context(&fact_manager,
+ validator_options);
+
+ uint32_t value_id = 80;
+ uint32_t value_synonym_id = 100;
+ uint32_t variable_id = 101;
+ uint32_t variable_storage_class = SpvStorageClassFunction;
+ auto instruction_descriptor =
+ MakeInstructionDescriptor(38, SpvOpAccessChain, 0);
+ auto transformation = TransformationPushIdThroughVariable(
+ value_id, value_synonym_id, variable_id, variable_storage_class,
+ instruction_descriptor);
+ transformation.Apply(context.get(), &transformation_context);
+
+ value_id = 21;
+ value_synonym_id = 102;
+ variable_id = 103;
+ variable_storage_class = SpvStorageClassFunction;
+ instruction_descriptor = MakeInstructionDescriptor(38, SpvOpAccessChain, 0);
+ transformation = TransformationPushIdThroughVariable(
+ value_id, value_synonym_id, variable_id, variable_storage_class,
+ instruction_descriptor);
+ transformation.Apply(context.get(), &transformation_context);
+
+ value_id = 95;
+ value_synonym_id = 104;
+ variable_id = 105;
+ variable_storage_class = SpvStorageClassFunction;
+ instruction_descriptor = MakeInstructionDescriptor(95, SpvOpReturnValue, 0);
+ transformation = TransformationPushIdThroughVariable(
+ value_id, value_synonym_id, variable_id, variable_storage_class,
+ instruction_descriptor);
+ transformation.Apply(context.get(), &transformation_context);
+
+ value_id = 80;
+ value_synonym_id = 106;
+ variable_id = 107;
+ variable_storage_class = SpvStorageClassFunction;
+ instruction_descriptor = MakeInstructionDescriptor(95, SpvOpReturnValue, 0);
+ transformation = TransformationPushIdThroughVariable(
+ value_id, value_synonym_id, variable_id, variable_storage_class,
+ instruction_descriptor);
+ transformation.Apply(context.get(), &transformation_context);
+
+ value_id = 21;
+ value_synonym_id = 108;
+ variable_id = 109;
+ variable_storage_class = SpvStorageClassPrivate;
+ instruction_descriptor = MakeInstructionDescriptor(95, SpvOpReturnValue, 0);
+ transformation = TransformationPushIdThroughVariable(
+ value_id, value_synonym_id, variable_id, variable_storage_class,
+ instruction_descriptor);
+ transformation.Apply(context.get(), &transformation_context);
+
+ std::string variant_shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main" %92 %52 %53
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ OpDecorate %92 BuiltIn FragCoord
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypeFloat 32
+ %8 = OpTypeStruct %6 %7
+ %9 = OpTypePointer Function %8
+ %10 = OpTypeFunction %6 %9
+ %14 = OpConstant %6 0
+ %15 = OpTypePointer Function %6
+ %51 = OpTypePointer Private %6
+ %21 = OpConstant %6 2
+ %23 = OpConstant %6 1
+ %24 = OpConstant %7 1
+ %25 = OpTypePointer Function %7
+ %50 = OpTypePointer Private %7
+ %34 = OpTypeBool
+ %35 = OpConstantFalse %34
+ %60 = OpConstantNull %50
+ %61 = OpUndef %51
+ %52 = OpVariable %50 Private
+ %53 = OpVariable %51 Private
+ %80 = OpConstantComposite %8 %21 %24
+ %90 = OpTypeVector %7 4
+ %91 = OpTypePointer Input %90
+ %92 = OpVariable %91 Input
+ %93 = OpConstantComposite %90 %24 %24 %24 %24
+ %109 = OpVariable %51 Private
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %103 = OpVariable %15 Function
+ %101 = OpVariable %9 Function
+ %20 = OpVariable %9 Function
+ %27 = OpVariable %9 Function
+ %22 = OpAccessChain %15 %20 %14
+ %44 = OpCopyObject %9 %20
+ %26 = OpAccessChain %25 %20 %23
+ %29 = OpFunctionCall %6 %12 %27
+ %30 = OpAccessChain %15 %20 %14
+ %45 = OpCopyObject %15 %30
+ %81 = OpCopyObject %9 %27
+ %33 = OpAccessChain %15 %20 %14
+ OpSelectionMerge %37 None
+ OpBranchConditional %35 %36 %37
+ %36 = OpLabel
+ OpStore %101 %80
+ %100 = OpLoad %8 %101
+ OpStore %103 %21
+ %102 = OpLoad %6 %103
+ %38 = OpAccessChain %15 %20 %14
+ %40 = OpAccessChain %15 %20 %14
+ %43 = OpAccessChain %15 %20 %14
+ %82 = OpCopyObject %9 %27
+ OpBranch %37
+ %37 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %12 = OpFunction %6 None %10
+ %11 = OpFunctionParameter %9
+ %13 = OpLabel
+ %107 = OpVariable %9 Function
+ %105 = OpVariable %9 Function
+ %46 = OpCopyObject %9 %11
+ %16 = OpAccessChain %15 %11 %14
+ %95 = OpCopyObject %8 %80
+ OpStore %105 %95
+ %104 = OpLoad %8 %105
+ OpStore %107 %80
+ %106 = OpLoad %8 %107
+ OpStore %109 %21
+ %108 = OpLoad %6 %109
+ OpReturnValue %21
+ OpFunctionEnd
+ )";
+
+ ASSERT_TRUE(IsEqual(env, variant_shader, context.get()));
+ ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(80, {}),
+ MakeDataDescriptor(100, {})));
+ ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(21, {}),
+ MakeDataDescriptor(102, {})));
+ ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(95, {}),
+ MakeDataDescriptor(104, {})));
+ ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(80, {}),
+ MakeDataDescriptor(106, {})));
+ ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(21, {}),
+ MakeDataDescriptor(108, {})));
+}
+
+} // namespace
+} // namespace fuzz
+} // namespace spvtools