diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt index 570cc7a6..288ea25c 100644 --- a/source/fuzz/CMakeLists.txt +++ b/source/fuzz/CMakeLists.txt @@ -94,6 +94,7 @@ if(SPIRV_BUILD_FUZZER) fuzzer_pass_replace_irrelevant_ids.h fuzzer_pass_replace_linear_algebra_instructions.h fuzzer_pass_replace_loads_stores_with_copy_memories.h + fuzzer_pass_replace_opphi_ids_from_dead_predecessors.h fuzzer_pass_replace_parameter_with_global.h fuzzer_pass_replace_params_with_struct.h fuzzer_pass_split_blocks.h @@ -172,6 +173,7 @@ if(SPIRV_BUILD_FUZZER) transformation_replace_irrelevant_id.h transformation_replace_linear_algebra_instruction.h transformation_replace_load_store_with_copy_memory.h + transformation_replace_opphi_id_from_dead_predecessor.h transformation_replace_parameter_with_global.h transformation_replace_params_with_struct.h transformation_set_function_control.h @@ -251,6 +253,7 @@ if(SPIRV_BUILD_FUZZER) fuzzer_pass_replace_irrelevant_ids.cpp fuzzer_pass_replace_linear_algebra_instructions.cpp fuzzer_pass_replace_loads_stores_with_copy_memories.cpp + fuzzer_pass_replace_opphi_ids_from_dead_predecessors.cpp fuzzer_pass_replace_parameter_with_global.cpp fuzzer_pass_replace_params_with_struct.cpp fuzzer_pass_split_blocks.cpp @@ -328,6 +331,7 @@ if(SPIRV_BUILD_FUZZER) transformation_replace_irrelevant_id.cpp transformation_replace_linear_algebra_instruction.cpp transformation_replace_load_store_with_copy_memory.cpp + transformation_replace_opphi_id_from_dead_predecessor.cpp transformation_replace_parameter_with_global.cpp transformation_replace_params_with_struct.cpp transformation_set_function_control.cpp diff --git a/source/fuzz/fuzzer.cpp b/source/fuzz/fuzzer.cpp index 6107f45c..3656e924 100644 --- a/source/fuzz/fuzzer.cpp +++ b/source/fuzz/fuzzer.cpp @@ -70,6 +70,7 @@ #include "source/fuzz/fuzzer_pass_replace_irrelevant_ids.h" #include "source/fuzz/fuzzer_pass_replace_linear_algebra_instructions.h" #include "source/fuzz/fuzzer_pass_replace_loads_stores_with_copy_memories.h" +#include "source/fuzz/fuzzer_pass_replace_opphi_ids_from_dead_predecessors.h" #include "source/fuzz/fuzzer_pass_replace_parameter_with_global.h" #include "source/fuzz/fuzzer_pass_replace_params_with_struct.h" #include "source/fuzz/fuzzer_pass_split_blocks.h" @@ -379,6 +380,9 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run( MaybeAddPass( &final_passes, ir_context.get(), &transformation_context, &fuzzer_context, transformation_sequence_out); + MaybeAddPass( + &final_passes, ir_context.get(), &transformation_context, &fuzzer_context, + transformation_sequence_out); MaybeAddPass( &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 1dfa717f..153b9ef6 100644 --- a/source/fuzz/fuzzer_context.cpp +++ b/source/fuzz/fuzzer_context.cpp @@ -103,6 +103,8 @@ const std::pair kChanceOfReplacingLinearAlgebraInstructions = {10, 90}; const std::pair kChanceOfReplacingLoadStoreWithCopyMemory = {20, 90}; +const std::pair + kChanceOfReplacingOpPhiIdFromDeadPredecessor = {20, 90}; const std::pair kChanceOfReplacingParametersWithGlobals = { 30, 70}; const std::pair kChanceOfReplacingParametersWithStruct = { @@ -267,6 +269,8 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator, ChooseBetweenMinAndMax(kChanceOfReplacingLinearAlgebraInstructions); chance_of_replacing_load_store_with_copy_memory_ = ChooseBetweenMinAndMax(kChanceOfReplacingLoadStoreWithCopyMemory); + chance_of_replacing_opphi_id_from_dead_predecessor_ = + ChooseBetweenMinAndMax(kChanceOfReplacingOpPhiIdFromDeadPredecessor); chance_of_replacing_parameters_with_globals_ = ChooseBetweenMinAndMax(kChanceOfReplacingParametersWithGlobals); chance_of_replacing_parameters_with_struct_ = diff --git a/source/fuzz/fuzzer_context.h b/source/fuzz/fuzzer_context.h index 14ada037..594cb907 100644 --- a/source/fuzz/fuzzer_context.h +++ b/source/fuzz/fuzzer_context.h @@ -261,6 +261,9 @@ class FuzzerContext { uint32_t GetChanceOfReplacingLoadStoreWithCopyMemory() { return chance_of_replacing_load_store_with_copy_memory_; } + uint32_t GetChanceOfReplacingOpPhiIdFromDeadPredecessor() { + return chance_of_replacing_opphi_id_from_dead_predecessor_; + } uint32_t GetChanceOfReplacingParametersWithGlobals() { return chance_of_replacing_parameters_with_globals_; } @@ -417,6 +420,7 @@ class FuzzerContext { uint32_t chance_of_replacing_irrelevant_id_; uint32_t chance_of_replacing_linear_algebra_instructions_; uint32_t chance_of_replacing_load_store_with_copy_memory_; + uint32_t chance_of_replacing_opphi_id_from_dead_predecessor_; uint32_t chance_of_replacing_parameters_with_globals_; uint32_t chance_of_replacing_parameters_with_struct_; uint32_t chance_of_splitting_block_; diff --git a/source/fuzz/fuzzer_pass_replace_opphi_ids_from_dead_predecessors.cpp b/source/fuzz/fuzzer_pass_replace_opphi_ids_from_dead_predecessors.cpp new file mode 100644 index 00000000..7d89e389 --- /dev/null +++ b/source/fuzz/fuzzer_pass_replace_opphi_ids_from_dead_predecessors.cpp @@ -0,0 +1,117 @@ +// Copyright (c) 2020 Google LLC +// +// 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_replace_opphi_ids_from_dead_predecessors.h" +#include "source/fuzz/transformation_replace_opphi_id_from_dead_predecessor.h" + +namespace spvtools { +namespace fuzz { + +FuzzerPassReplaceOpPhiIdsFromDeadPredecessors:: + FuzzerPassReplaceOpPhiIdsFromDeadPredecessors( + opt::IRContext* ir_context, + TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations) + : FuzzerPass(ir_context, transformation_context, fuzzer_context, + transformations) {} + +FuzzerPassReplaceOpPhiIdsFromDeadPredecessors:: + ~FuzzerPassReplaceOpPhiIdsFromDeadPredecessors() = default; + +void FuzzerPassReplaceOpPhiIdsFromDeadPredecessors::Apply() { + // Keep a vector of the transformations to apply. + std::vector transformations; + + // Loop through the blocks in the module. + for (auto& function : *GetIRContext()->module()) { + for (auto& block : function) { + // Only consider dead blocks. + if (!GetTransformationContext()->GetFactManager()->BlockIsDead( + block.id())) { + continue; + } + + // Find all the uses of the label id of the block inside OpPhi + // instructions. + GetIRContext()->get_def_use_mgr()->ForEachUse( + block.id(), [this, &function, &block, &transformations]( + opt::Instruction* instruction, uint32_t) { + // Only consider OpPhi instructions. + if (instruction->opcode() != SpvOpPhi) { + return; + } + + // Randomly decide whether to consider this use. + if (!GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext() + ->GetChanceOfReplacingOpPhiIdFromDeadPredecessor())) { + return; + } + + // Get the current id corresponding to the predecessor. + uint32_t current_id = 0; + for (uint32_t i = 1; i < instruction->NumInOperands(); i += 2) { + if (instruction->GetSingleWordInOperand(i) == block.id()) { + // The corresponding id is at the index of the block - 1. + current_id = instruction->GetSingleWordInOperand(i - 1); + break; + } + } + assert(current_id != 0 && + "The predecessor - and corresponding id - should always be " + "found."); + + uint32_t type_id = instruction->type_id(); + + // Find all the suitable instructions to replace the id. + const auto& candidates = FindAvailableInstructions( + &function, &block, block.end(), + [type_id, current_id](opt::IRContext* /* unused */, + opt::Instruction* candidate) -> bool { + + // Only consider instructions with a result id different from + // the currently-used one, and with the right type. + return candidate->HasResultId() && + candidate->type_id() == type_id && + candidate->result_id() != current_id; + }); + + // If there is no possible replacement, we cannot apply any + // transformation. + if (candidates.empty()) { + return; + } + + // Choose one of the candidates. + uint32_t replacement_id = + candidates[GetFuzzerContext()->RandomIndex(candidates)] + ->result_id(); + + // Add a new transformation to the list of transformations to apply. + transformations.emplace_back( + TransformationReplaceOpPhiIdFromDeadPredecessor( + instruction->result_id(), block.id(), replacement_id)); + }); + } + } + + // Apply all the transformations. + for (const auto& transformation : transformations) { + ApplyTransformation(transformation); + } +} + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/fuzzer_pass_replace_opphi_ids_from_dead_predecessors.h b/source/fuzz/fuzzer_pass_replace_opphi_ids_from_dead_predecessors.h new file mode 100644 index 00000000..972c5f9d --- /dev/null +++ b/source/fuzz/fuzzer_pass_replace_opphi_ids_from_dead_predecessors.h @@ -0,0 +1,39 @@ +// Copyright (c) 2020 Google LLC +// +// 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_REPLACE_OPPHI_IDS_FROM_DEAD_PREDECESSORS_H_ +#define SOURCE_FUZZ_FUZZER_PASS_REPLACE_OPPHI_IDS_FROM_DEAD_PREDECESSORS_H_ + +#include "source/fuzz/fuzzer_pass.h" + +namespace spvtools { +namespace fuzz { + +// Replaces id operands in OpPhi instructions with other available ids of the +// right type, where the corresponding predecessor is dead. +class FuzzerPassReplaceOpPhiIdsFromDeadPredecessors : public FuzzerPass { + public: + FuzzerPassReplaceOpPhiIdsFromDeadPredecessors( + opt::IRContext* ir_context, TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations); + + ~FuzzerPassReplaceOpPhiIdsFromDeadPredecessors(); + + void Apply() override; +}; +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_FUZZER_PASS_REPLACE_OPPHI_IDS_FROM_DEAD_PREDECESSORS_H_ diff --git a/source/fuzz/protobufs/spvtoolsfuzz.proto b/source/fuzz/protobufs/spvtoolsfuzz.proto index 0ac485f8..0cc7a78e 100644 --- a/source/fuzz/protobufs/spvtoolsfuzz.proto +++ b/source/fuzz/protobufs/spvtoolsfuzz.proto @@ -417,6 +417,7 @@ message Transformation { TransformationAddOpPhiSynonym add_opphi_synonym = 70; TransformationMutatePointer mutate_pointer = 71; TransformationReplaceIrrelevantId replace_irrelevant_id = 72; + TransformationReplaceOpPhiIdFromDeadPredecessor replace_opphi_id_from_dead_predecessor = 73; // Add additional option using the next available number. } } @@ -1543,6 +1544,26 @@ message TransformationReplaceLoadStoreWithCopyMemory { InstructionDescriptor store_instruction_descriptor = 2; } +message TransformationReplaceOpPhiIdFromDeadPredecessor { + + // Replaces one of the ids used by an OpPhi instruction, when + // the corresponding predecessor is dead, with any available id + // of the correct type. + + // The result id of the OpPhi instruction. + uint32 opphi_id = 1; + + // The label id of one of the predecessors of the block containing + // the OpPhi instruction, corresponding to the id that we want to + // replace. + uint32 pred_label_id = 2; + + // The id that, after the transformation, will be associated with + // the given predecessor. + uint32 replacement_id = 3; + +} + message TransformationReplaceParamsWithStruct { // Replaces parameters of the function with a struct containing diff --git a/source/fuzz/transformation.cpp b/source/fuzz/transformation.cpp index e6baa155..23609013 100644 --- a/source/fuzz/transformation.cpp +++ b/source/fuzz/transformation.cpp @@ -77,6 +77,7 @@ #include "source/fuzz/transformation_replace_irrelevant_id.h" #include "source/fuzz/transformation_replace_linear_algebra_instruction.h" #include "source/fuzz/transformation_replace_load_store_with_copy_memory.h" +#include "source/fuzz/transformation_replace_opphi_id_from_dead_predecessor.h" #include "source/fuzz/transformation_replace_parameter_with_global.h" #include "source/fuzz/transformation_replace_params_with_struct.h" #include "source/fuzz/transformation_set_function_control.h" @@ -283,6 +284,10 @@ std::unique_ptr Transformation::FromMessage( kReplaceParamsWithStruct: return MakeUnique( message.replace_params_with_struct()); + case protobufs::Transformation::TransformationCase:: + kReplaceOpphiIdFromDeadPredecessor: + return MakeUnique( + message.replace_opphi_id_from_dead_predecessor()); case protobufs::Transformation::TransformationCase::kSetFunctionControl: return MakeUnique( message.set_function_control()); diff --git a/source/fuzz/transformation_replace_opphi_id_from_dead_predecessor.cpp b/source/fuzz/transformation_replace_opphi_id_from_dead_predecessor.cpp new file mode 100644 index 00000000..d5d324b9 --- /dev/null +++ b/source/fuzz/transformation_replace_opphi_id_from_dead_predecessor.cpp @@ -0,0 +1,110 @@ +// Copyright (c) 2020 Google LLC +// +// 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_replace_opphi_id_from_dead_predecessor.h" + +#include "source/fuzz/fuzzer_util.h" + +namespace spvtools { +namespace fuzz { + +TransformationReplaceOpPhiIdFromDeadPredecessor:: + TransformationReplaceOpPhiIdFromDeadPredecessor( + const protobufs::TransformationReplaceOpPhiIdFromDeadPredecessor& + message) + : message_(message) {} + +TransformationReplaceOpPhiIdFromDeadPredecessor:: + TransformationReplaceOpPhiIdFromDeadPredecessor(uint32_t opphi_id, + uint32_t pred_label_id, + uint32_t replacement_id) { + message_.set_opphi_id(opphi_id); + message_.set_pred_label_id(pred_label_id); + message_.set_replacement_id(replacement_id); +} + +bool TransformationReplaceOpPhiIdFromDeadPredecessor::IsApplicable( + opt::IRContext* ir_context, + const TransformationContext& transformation_context) const { + // |opphi_id| must be the id of an OpPhi instruction. + auto opphi_def = ir_context->get_def_use_mgr()->GetDef(message_.opphi_id()); + if (!opphi_def || opphi_def->opcode() != SpvOpPhi) { + return false; + } + + // |pred_label_id| must be the label id of a dead block. + auto pred_block = ir_context->get_instr_block(message_.pred_label_id()); + if (!pred_block || pred_block->id() != message_.pred_label_id() || + !transformation_context.GetFactManager()->BlockIsDead(pred_block->id())) { + return false; + } + + // |pred_label_id| must be one of the predecessors of the block containing the + // OpPhi instruction. + bool found = false; + for (auto pred : + ir_context->cfg()->preds(ir_context->get_instr_block(opphi_def)->id())) { + if (pred == message_.pred_label_id()) { + found = true; + break; + } + } + + if (!found) { + return false; + } + + // |replacement_id| must have the same type id as the OpPhi instruction. + auto replacement_def = + ir_context->get_def_use_mgr()->GetDef(message_.replacement_id()); + + if (!replacement_def || replacement_def->type_id() != opphi_def->type_id()) { + return false; + } + + // The replacement id must be available at the end of the predecessor. + return fuzzerutil::IdIsAvailableBeforeInstruction( + ir_context, pred_block->terminator(), replacement_def->result_id()); +} + +void TransformationReplaceOpPhiIdFromDeadPredecessor::Apply( + opt::IRContext* ir_context, + TransformationContext* /* transformation_context */) const { + // Get the OpPhi instruction. + auto opphi_def = ir_context->get_def_use_mgr()->GetDef(message_.opphi_id()); + + // Find the index corresponding to the operand being replaced and replace it, + // by looping through the odd-indexed input operands and finding + // |pred_label_id|. The index that we are interested in is the one before + // that. + for (uint32_t i = 1; i < opphi_def->NumInOperands(); i += 2) { + if (opphi_def->GetSingleWordInOperand(i) == message_.pred_label_id()) { + // The operand to be replaced is at index i-1. + opphi_def->SetInOperand(i - 1, {message_.replacement_id()}); + } + } + + // Invalidate the analyses because we have altered the usages of ids. + ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone); +} + +protobufs::Transformation +TransformationReplaceOpPhiIdFromDeadPredecessor::ToMessage() const { + protobufs::Transformation result; + *result.mutable_replace_opphi_id_from_dead_predecessor() = message_; + return result; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/transformation_replace_opphi_id_from_dead_predecessor.h b/source/fuzz/transformation_replace_opphi_id_from_dead_predecessor.h new file mode 100644 index 00000000..2833eb28 --- /dev/null +++ b/source/fuzz/transformation_replace_opphi_id_from_dead_predecessor.h @@ -0,0 +1,56 @@ +// Copyright (c) 2020 Google LLC +// +// 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_REPLACE_OPPHI_ID_FROM_DEAD_PREDECESSOR_H_ +#define SOURCE_FUZZ_TRANSFORMATION_REPLACE_OPPHI_ID_FROM_DEAD_PREDECESSOR_H_ + +#include "source/fuzz/transformation.h" + +namespace spvtools { +namespace fuzz { + +class TransformationReplaceOpPhiIdFromDeadPredecessor : public Transformation { + public: + explicit TransformationReplaceOpPhiIdFromDeadPredecessor( + const protobufs::TransformationReplaceOpPhiIdFromDeadPredecessor& + message); + + TransformationReplaceOpPhiIdFromDeadPredecessor(uint32_t opphi_id, + uint32_t pred_label_id, + uint32_t replacement_id); + + // - |message_.opphi_id| is the id of an OpPhi instruction. + // - |message_.pred_label_id| is the label id of one of the predecessors of + // the block containing the OpPhi instruction. + // - The predecessor has been recorded as dead. + // - |message_.replacement_id| is the id of an instruction with the same type + // as the OpPhi instruction, available at the end of the predecessor. + bool IsApplicable( + opt::IRContext* ir_context, + const TransformationContext& transformation_context) const override; + + // Replaces the id corresponding to predecessor |message_.pred_label_id|, in + // the OpPhi instruction |message_.opphi_id|, with |message_.replacement_id|. + void Apply(opt::IRContext* ir_context, + TransformationContext* transformation_context) const override; + + protobufs::Transformation ToMessage() const override; + + private: + protobufs::TransformationReplaceOpPhiIdFromDeadPredecessor message_; +}; +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_TRANSFORMATION_REPLACE_OPPHI_ID_FROM_DEAD_PREDECESSOR_H_ diff --git a/test/fuzz/CMakeLists.txt b/test/fuzz/CMakeLists.txt index 105d4a6f..34317d00 100644 --- a/test/fuzz/CMakeLists.txt +++ b/test/fuzz/CMakeLists.txt @@ -86,6 +86,7 @@ if (${SPIRV_BUILD_FUZZER}) transformation_replace_irrelevant_id_test.cpp transformation_replace_linear_algebra_instruction_test.cpp transformation_replace_load_store_with_copy_memory_test.cpp + transformation_replace_opphi_id_from_dead_predecessor_test.cpp transformation_replace_parameter_with_global_test.cpp transformation_replace_params_with_struct_test.cpp transformation_set_function_control_test.cpp diff --git a/test/fuzz/transformation_replace_opphi_id_from_dead_predecessor_test.cpp b/test/fuzz/transformation_replace_opphi_id_from_dead_predecessor_test.cpp new file mode 100644 index 00000000..fd1c1808 --- /dev/null +++ b/test/fuzz/transformation_replace_opphi_id_from_dead_predecessor_test.cpp @@ -0,0 +1,205 @@ +// Copyright (c) 2020 Google LLC +// +// 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_replace_opphi_id_from_dead_predecessor.h" +#include "test/fuzz/fuzz_test_util.h" + +namespace spvtools { +namespace fuzz { +namespace { + +std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + OpName %2 "main" + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeBool + %6 = OpConstantTrue %5 + %7 = OpConstantFalse %5 + %8 = OpTypeInt 32 1 + %9 = OpConstant %8 2 + %10 = OpConstant %8 3 + %11 = OpConstant %8 4 + %12 = OpConstant %8 5 + %13 = OpConstant %8 6 + %2 = OpFunction %3 None %4 + %14 = OpLabel + OpSelectionMerge %15 None + OpBranchConditional %6 %16 %17 + %16 = OpLabel + %18 = OpCopyObject %8 %9 + OpSelectionMerge %19 None + OpBranchConditional %7 %20 %21 + %20 = OpLabel + %22 = OpCopyObject %8 %10 + %23 = OpCopyObject %8 %12 + OpBranch %19 + %21 = OpLabel + %24 = OpCopyObject %8 %9 + OpBranch %19 + %19 = OpLabel + %25 = OpPhi %8 %22 %20 %24 %21 + OpBranch %15 + %17 = OpLabel + %26 = OpCopyObject %8 %12 + %27 = OpCopyObject %8 %13 + OpBranch %28 + %28 = OpLabel + %29 = OpPhi %8 %27 %17 + OpBranch %15 + %15 = OpLabel + %30 = OpPhi %8 %25 %19 %26 %28 + OpReturn + OpFunctionEnd +)"; + +TEST(TransformationReplaceOpPhiIdFromDeadPredecessorTest, Inapplicable) { + const auto env = SPV_ENV_UNIVERSAL_1_5; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + + FactManager fact_manager; + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + ASSERT_TRUE(IsValid(env, context.get())); + + // Record the fact that blocks 20, 17, 28 are dead. + transformation_context.GetFactManager()->AddFactBlockIsDead(20); + transformation_context.GetFactManager()->AddFactBlockIsDead(17); + transformation_context.GetFactManager()->AddFactBlockIsDead(28); + + // %26 is not an OpPhi instruction. + ASSERT_FALSE(TransformationReplaceOpPhiIdFromDeadPredecessor(26, 14, 10) + .IsApplicable(context.get(), transformation_context)); + + // %25 is not a block label. + ASSERT_FALSE(TransformationReplaceOpPhiIdFromDeadPredecessor(30, 25, 10) + .IsApplicable(context.get(), transformation_context)); + + // %14 is not a predecessor of %28 (which contains %29). + ASSERT_FALSE(TransformationReplaceOpPhiIdFromDeadPredecessor(29, 14, 10) + .IsApplicable(context.get(), transformation_context)); + + // %19 is not a dead block. + ASSERT_FALSE(TransformationReplaceOpPhiIdFromDeadPredecessor(30, 19, 10) + .IsApplicable(context.get(), transformation_context)); + + // %7 does not have the same type id as %25. + ASSERT_FALSE( + TransformationReplaceOpPhiIdFromDeadPredecessor(25, 20, 7).IsApplicable( + context.get(), transformation_context)); + + // %29 is not available at the end of %20. + ASSERT_FALSE(TransformationReplaceOpPhiIdFromDeadPredecessor(25, 20, 29) + .IsApplicable(context.get(), transformation_context)); +} + +TEST(TransformationReplaceOpPhiIdFromDeadPredecessorTest, Apply) { + const auto env = SPV_ENV_UNIVERSAL_1_5; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + + FactManager fact_manager; + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + ASSERT_TRUE(IsValid(env, context.get())); + + // Record the fact that blocks 20, 17, 28 are dead. + transformation_context.GetFactManager()->AddFactBlockIsDead(20); + transformation_context.GetFactManager()->AddFactBlockIsDead(17); + transformation_context.GetFactManager()->AddFactBlockIsDead(28); + + auto transformation1 = + TransformationReplaceOpPhiIdFromDeadPredecessor(25, 20, 18); + ASSERT_TRUE( + transformation1.IsApplicable(context.get(), transformation_context)); + transformation1.Apply(context.get(), &transformation_context); + + auto transformation2 = + TransformationReplaceOpPhiIdFromDeadPredecessor(30, 28, 29); + ASSERT_TRUE( + transformation2.IsApplicable(context.get(), transformation_context)); + transformation2.Apply(context.get(), &transformation_context); + + auto transformation3 = + TransformationReplaceOpPhiIdFromDeadPredecessor(29, 17, 10); + ASSERT_TRUE( + transformation3.IsApplicable(context.get(), transformation_context)); + transformation3.Apply(context.get(), &transformation_context); + + ASSERT_TRUE(IsValid(env, context.get())); + + std::string after_transformations = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + OpName %2 "main" + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeBool + %6 = OpConstantTrue %5 + %7 = OpConstantFalse %5 + %8 = OpTypeInt 32 1 + %9 = OpConstant %8 2 + %10 = OpConstant %8 3 + %11 = OpConstant %8 4 + %12 = OpConstant %8 5 + %13 = OpConstant %8 6 + %2 = OpFunction %3 None %4 + %14 = OpLabel + OpSelectionMerge %15 None + OpBranchConditional %6 %16 %17 + %16 = OpLabel + %18 = OpCopyObject %8 %9 + OpSelectionMerge %19 None + OpBranchConditional %7 %20 %21 + %20 = OpLabel + %22 = OpCopyObject %8 %10 + %23 = OpCopyObject %8 %12 + OpBranch %19 + %21 = OpLabel + %24 = OpCopyObject %8 %9 + OpBranch %19 + %19 = OpLabel + %25 = OpPhi %8 %18 %20 %24 %21 + OpBranch %15 + %17 = OpLabel + %26 = OpCopyObject %8 %12 + %27 = OpCopyObject %8 %13 + OpBranch %28 + %28 = OpLabel + %29 = OpPhi %8 %10 %17 + OpBranch %15 + %15 = OpLabel + %30 = OpPhi %8 %25 %19 %29 %28 + OpReturn + OpFunctionEnd +)"; + + ASSERT_TRUE(IsEqual(env, after_transformations, context.get())); +} + +} // namespace +} // namespace fuzz +} // namespace spvtools