Progress on new transformation.
diff --git a/source/fuzz/fact_manager.cpp b/source/fuzz/fact_manager.cpp index ac3ea30..c25d171 100644 --- a/source/fuzz/fact_manager.cpp +++ b/source/fuzz/fact_manager.cpp
@@ -800,9 +800,37 @@ // End of data synonym facts //============================== +//============================== +// Dead id facts + +// TODO comment. +class FactManager::DeadIdFacts { + public: + // See method in FactManager which delegates to this method. + void AddFact(const protobufs::FactIdIsDead& fact); + + // See method in FactManager which delegates to this method. + bool IdIsDead(uint32_t id) const; + + private: + std::set<uint32_t> dead_ids_; +}; + +void FactManager::DeadIdFacts::AddFact(const protobufs::FactIdIsDead& fact) { + dead_ids_.insert(fact.id()); +} + +bool FactManager::DeadIdFacts::IdIsDead(uint32_t id) const { + return dead_ids_.count(id) != 0; +} + +// End of dead id facts +//============================== + FactManager::FactManager() : uniform_constant_facts_(MakeUnique<ConstantUniformFacts>()), - data_synonym_facts_(MakeUnique<DataSynonymFacts>()) {} + data_synonym_facts_(MakeUnique<DataSynonymFacts>()), + dead_id_facts_(MakeUnique<DeadIdFacts>()) {} FactManager::~FactManager() = default; @@ -827,6 +855,9 @@ case protobufs::Fact::kDataSynonymFact: data_synonym_facts_->AddFact(fact.data_synonym_fact(), context); return true; + case protobufs::Fact::kIdIsDeadFact: + dead_id_facts_->AddFact(fact.id_is_dead_fact()); + return true; default: assert(false && "Unknown fact type."); return false; @@ -898,5 +929,15 @@ context); } +bool FactManager::IdIsDead(uint32_t id) const { + return dead_id_facts_->IdIsDead(id); +} + +void FactManager::AddFactIdIsDead(uint32_t id) { + protobufs::FactIdIsDead fact; + fact.set_id(id); + dead_id_facts_->AddFact(fact); +} + } // namespace fuzz } // namespace spvtools
diff --git a/source/fuzz/fact_manager.h b/source/fuzz/fact_manager.h index 62d9dac..fb5ff71 100644 --- a/source/fuzz/fact_manager.h +++ b/source/fuzz/fact_manager.h
@@ -58,6 +58,9 @@ const protobufs::DataDescriptor& data2, opt::IRContext* context); + // Records the fact that |id| is dead. + void AddFactIdIsDead(uint32_t id); + // The fact manager is responsible for managing a few distinct categories of // facts. In principle there could be different fact managers for each kind // of fact, but in practice providing one 'go to' place for facts is @@ -130,6 +133,14 @@ // End of id synonym facts //============================== + //============================== + // Querying facts about dead ids + + bool IdIsDead(uint32_t id) const; + + // End of dead id facts + //============================== + private: // For each distinct kind of fact to be managed, we use a separate opaque // class type. @@ -142,6 +153,10 @@ class DataSynonymFacts; // Opaque class for management of data synonym facts. std::unique_ptr<DataSynonymFacts> data_synonym_facts_; // Unique pointer to internal data. + + class DeadIdFacts; // Opaque class for management of dead id facts. + std::unique_ptr<DeadIdFacts> + dead_id_facts_; // Unique pointer to internal data. }; } // namespace fuzz
diff --git a/source/fuzz/fuzzer.cpp b/source/fuzz/fuzzer.cpp index 95913d0..4695b62 100644 --- a/source/fuzz/fuzzer.cpp +++ b/source/fuzz/fuzzer.cpp
@@ -21,6 +21,7 @@ #include "fuzzer_pass_adjust_memory_operands_masks.h" #include "source/fuzz/fact_manager.h" #include "source/fuzz/fuzzer_context.h" +#include "source/fuzz/fuzzer_pass_add_dead_blocks.h" #include "source/fuzz/fuzzer_pass_add_dead_breaks.h" #include "source/fuzz/fuzzer_pass_add_dead_continues.h" #include "source/fuzz/fuzzer_pass_add_no_contraction_decorations.h" @@ -169,6 +170,9 @@ // Apply some semantics-preserving passes. std::vector<std::unique_ptr<FuzzerPass>> passes; while (passes.empty()) { + MaybeAddPass<FuzzerPassAddDeadBlocks>(&passes, ir_context.get(), + &fact_manager, &fuzzer_context, + transformation_sequence_out); MaybeAddPass<FuzzerPassAddDeadBreaks>(&passes, ir_context.get(), &fact_manager, &fuzzer_context, transformation_sequence_out);
diff --git a/source/fuzz/fuzzer_context.cpp b/source/fuzz/fuzzer_context.cpp index 98585d9..f2db42a 100644 --- a/source/fuzz/fuzzer_context.cpp +++ b/source/fuzz/fuzzer_context.cpp
@@ -23,6 +23,7 @@ // Default <minimum, maximum> pairs of probabilities for applying various // transformations. All values are percentages. Keep them in alphabetical order. +const std::pair<uint32_t, uint32_t> kChanceOfAddingDeadBlock = {20, 90}; const std::pair<uint32_t, uint32_t> kChanceOfAddingDeadBreak = {5, 80}; const std::pair<uint32_t, uint32_t> kChanceOfAddingDeadContinue = {5, 80}; const std::pair<uint32_t, uint32_t> kChanceOfAddingNoContractionDecoration = { @@ -66,6 +67,8 @@ next_fresh_id_(min_fresh_id), go_deeper_in_constant_obfuscation_( kDefaultGoDeeperInConstantObfuscation) { + chance_of_adding_dead_block_ = + ChooseBetweenMinAndMax(kChanceOfAddingDeadBlock); chance_of_adding_dead_break_ = ChooseBetweenMinAndMax(kChanceOfAddingDeadBreak); chance_of_adding_dead_continue_ =
diff --git a/source/fuzz/fuzzer_context.h b/source/fuzz/fuzzer_context.h index 619c131..42ac320 100644 --- a/source/fuzz/fuzzer_context.h +++ b/source/fuzz/fuzzer_context.h
@@ -58,6 +58,7 @@ // Probabilities associated with applying various transformations. // Keep them in alphabetical order. + uint32_t GetChanceOfAddingDeadBlock() { return chance_of_adding_dead_block_; } uint32_t GetChanceOfAddingDeadBreak() { return chance_of_adding_dead_break_; } uint32_t GetChanceOfAddingDeadContinue() { return chance_of_adding_dead_continue_; @@ -114,6 +115,7 @@ // Probabilities associated with applying various transformations. // Keep them in alphabetical order. + uint32_t chance_of_adding_dead_block_; uint32_t chance_of_adding_dead_break_; uint32_t chance_of_adding_dead_continue_; uint32_t chance_of_adding_no_contraction_decoration_;
diff --git a/source/fuzz/fuzzer_pass_add_dead_blocks.cpp b/source/fuzz/fuzzer_pass_add_dead_blocks.cpp index f964146..ac2aef3 100644 --- a/source/fuzz/fuzzer_pass_add_dead_blocks.cpp +++ b/source/fuzz/fuzzer_pass_add_dead_blocks.cpp
@@ -14,6 +14,9 @@ #include "source/fuzz/fuzzer_pass_add_dead_blocks.h" +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/transformation_add_dead_block.h" + namespace spvtools { namespace fuzz { @@ -26,7 +29,40 @@ FuzzerPassAddDeadBlocks::~FuzzerPassAddDeadBlocks() = default; void FuzzerPassAddDeadBlocks::Apply() { - assert(false && "Implement"); + std::vector<opt::BasicBlock*> candidate_blocks; + for (auto& function : *GetIRContext()->module()) { + for (auto& block : function) { + if (!GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext()->GetChanceOfAddingDeadBlock())) { + continue; + } + if (block.IsLoopHeader()) { + continue; + } + if (block.terminator()->opcode() != SpvOpBranch) { + continue; + } + if (fuzzerutil::IsMergeOrContinue( + GetIRContext(), block.terminator()->GetSingleWordInOperand(0))) { + continue; + } + // TODO think about OpPhi here + candidate_blocks.push_back(&block); + } + } + while (!candidate_blocks.empty()) { + uint32_t index = GetFuzzerContext()->RandomIndex(candidate_blocks); + auto block = candidate_blocks.at(index); + candidate_blocks.erase(candidate_blocks.begin() + index); + // TODO: address OpPhi situation + TransformationAddDeadBlock transformation( + GetFuzzerContext()->GetFreshId(), block->id(), + GetFuzzerContext()->ChooseEven(), {}); + if (transformation.IsApplicable(GetIRContext(), *GetFactManager())) { + transformation.Apply(GetIRContext(), GetFactManager()); + *GetTransformations()->add_transformation() = transformation.ToMessage(); + } + } } } // namespace fuzz
diff --git a/source/fuzz/fuzzer_pass_add_dead_blocks.h b/source/fuzz/fuzzer_pass_add_dead_blocks.h index 0577151..9ebb2f3 100644 --- a/source/fuzz/fuzzer_pass_add_dead_blocks.h +++ b/source/fuzz/fuzzer_pass_add_dead_blocks.h
@@ -24,8 +24,8 @@ class FuzzerPassAddDeadBlocks : public FuzzerPass { public: FuzzerPassAddDeadBlocks(opt::IRContext* ir_context, FactManager* fact_manager, - FuzzerContext* fuzzer_context, - protobufs::TransformationSequence* transformations); + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations); ~FuzzerPassAddDeadBlocks();
diff --git a/source/fuzz/fuzzer_util.cpp b/source/fuzz/fuzzer_util.cpp index a6e3657..82d761c 100644 --- a/source/fuzz/fuzzer_util.cpp +++ b/source/fuzz/fuzzer_util.cpp
@@ -112,14 +112,14 @@ uint32_t MaybeGetBoolConstantId(opt::IRContext* context, bool value) { opt::analysis::Bool bool_type; auto registered_bool_type = - context->get_type_mgr()->GetRegisteredType(&bool_type); + context->get_type_mgr()->GetRegisteredType(&bool_type); if (!registered_bool_type) { return 0; } opt::analysis::BoolConstant bool_constant(registered_bool_type->AsBool(), - value); + value); return context->get_constant_mgr()->FindDeclaredConstant( - &bool_constant, context->get_type_mgr()->GetId(&bool_type)); + &bool_constant, context->get_type_mgr()->GetId(&bool_type)); } void AddUnreachableEdgeAndUpdateOpPhis( @@ -133,7 +133,9 @@ // Get the id of the boolean constant to be used as the condition. uint32_t bool_id = MaybeGetBoolConstantId(context, condition_value); - assert(bool_id && "Precondition that condition value must be available is not satisfied"); + assert( + bool_id && + "Precondition that condition value must be available is not satisfied"); const bool from_to_edge_already_exists = bb_from->IsSuccessor(bb_to); auto successor = bb_from->terminator()->GetSingleWordInOperand(0); @@ -330,19 +332,18 @@ bool IsMergeOrContinue(opt::IRContext* ir_context, uint32_t block_id) { bool result = false; ir_context->get_def_use_mgr()->WhileEachUse( - block_id, - [&result]( - const opt::Instruction* use_instruction, - uint32_t /*unused*/) -> bool { - switch (use_instruction->opcode()) { - case SpvOpLoopMerge: - case SpvOpSelectionMerge: - result = true; - return false; - default: - return true; - } - }); + block_id, + [&result](const opt::Instruction* use_instruction, + uint32_t /*unused*/) -> bool { + switch (use_instruction->opcode()) { + case SpvOpLoopMerge: + case SpvOpSelectionMerge: + result = true; + return false; + default: + return true; + } + }); return result; }
diff --git a/source/fuzz/protobufs/spvtoolsfuzz.proto b/source/fuzz/protobufs/spvtoolsfuzz.proto index ef8074e..a0efd6e 100644 --- a/source/fuzz/protobufs/spvtoolsfuzz.proto +++ b/source/fuzz/protobufs/spvtoolsfuzz.proto
@@ -166,6 +166,7 @@ // Order the fact options by numeric id (rather than alphabetically). FactConstantUniform constant_uniform_fact = 1; FactDataSynonym data_synonym_fact = 2; + FactIdIsDead id_is_dead_fact = 3; } } @@ -200,6 +201,16 @@ } +message FactIdIsDead { + + // Records the fact that a block label instruction, or an instruction inside + // a block, is guaranteed to be dynamically unreachable. This is useful + // because it informs the fuzzer that rather arbitrary changes can be made + // in relation to this instruction. + + uint32 id = 1; +} + message TransformationSequence { repeated Transformation transformation = 1; }
diff --git a/source/fuzz/transformation.cpp b/source/fuzz/transformation.cpp index 3f5375e..c7aae58 100644 --- a/source/fuzz/transformation.cpp +++ b/source/fuzz/transformation.cpp
@@ -70,8 +70,7 @@ return MakeUnique<TransformationAddConstantScalar>( message.add_constant_scalar()); case protobufs::Transformation::TransformationCase::kAddDeadBlock: - return MakeUnique<TransformationAddDeadBlock>( - message.add_dead_block()); + return MakeUnique<TransformationAddDeadBlock>(message.add_dead_block()); case protobufs::Transformation::TransformationCase::kAddDeadBreak: return MakeUnique<TransformationAddDeadBreak>(message.add_dead_break()); case protobufs::Transformation::TransformationCase::kAddDeadContinue:
diff --git a/source/fuzz/transformation_add_dead_block.cpp b/source/fuzz/transformation_add_dead_block.cpp index ff9cebc..0d6b2f7 100644 --- a/source/fuzz/transformation_add_dead_block.cpp +++ b/source/fuzz/transformation_add_dead_block.cpp
@@ -24,9 +24,8 @@ : message_(message) {} TransformationAddDeadBlock::TransformationAddDeadBlock( - uint32_t fresh_id, uint32_t existing_block, -bool condition_value, - std::vector<uint32_t> phi_id) { + uint32_t fresh_id, uint32_t existing_block, bool condition_value, + std::vector<uint32_t> phi_id) { message_.set_fresh_id(fresh_id); message_.set_existing_block(existing_block); message_.set_condition_value(condition_value); @@ -45,14 +44,16 @@ // First, we check that a constant with the same value as // |message_.condition_value| is present. - if (!fuzzerutil::MaybeGetBoolConstantId(context, message_.condition_value())) { + if (!fuzzerutil::MaybeGetBoolConstantId(context, + message_.condition_value())) { // The required constant is not present, so the transformation cannot be // applied. return false; } // The existing block must indeed exist. - auto existing_block = fuzzerutil::MaybeFindBlock(context, message_.existing_block()); + auto existing_block = + fuzzerutil::MaybeFindBlock(context, message_.existing_block()); if (!existing_block) { return false; } @@ -68,32 +69,54 @@ } // Its successor must not be a merge block nor continue target. - if (fuzzerutil::IsMergeOrContinue(context, existing_block->terminator()->GetSingleWordInOperand(0))) { + if (fuzzerutil::IsMergeOrContinue( + context, existing_block->terminator()->GetSingleWordInOperand(0))) { return false; } return true; } void TransformationAddDeadBlock::Apply( - opt::IRContext* context, spvtools::fuzz::FactManager* /*unused*/) const { + opt::IRContext* context, spvtools::fuzz::FactManager* fact_manager) const { // TODO comment fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id()); auto existing_block = context->cfg()->block(message_.existing_block()); - auto successor_block_id = existing_block->terminator()->GetSingleWordInOperand(0); - auto bool_id = fuzzerutil::MaybeGetBoolConstantId(context, message_.condition_value()); + auto successor_block_id = + existing_block->terminator()->GetSingleWordInOperand(0); + auto bool_id = + fuzzerutil::MaybeGetBoolConstantId(context, message_.condition_value()); auto enclosing_function = existing_block->GetParent(); - std::unique_ptr<opt::BasicBlock> new_block = - MakeUnique<opt::BasicBlock>(MakeUnique<opt::Instruction>( - context, SpvOpLabel, 0, message_.fresh_id(), opt::Instruction::OperandList())); + std::unique_ptr<opt::BasicBlock> new_block = MakeUnique<opt::BasicBlock>( + MakeUnique<opt::Instruction>(context, SpvOpLabel, 0, message_.fresh_id(), + opt::Instruction::OperandList())); new_block->SetParent(enclosing_function); - new_block->AddInstruction(MakeUnique<opt::Instruction>(context, SpvOpBranch, 0, 0, opt::Instruction::OperandList({{SPV_OPERAND_TYPE_ID, {successor_block_id}}}))); + new_block->AddInstruction(MakeUnique<opt::Instruction>( + context, SpvOpBranch, 0, 0, + opt::Instruction::OperandList( + {{SPV_OPERAND_TYPE_ID, {successor_block_id}}}))); + + existing_block->terminator()->InsertBefore(MakeUnique<opt::Instruction>( + context, SpvOpSelectionMerge, 0, 0, + opt::Instruction::OperandList( + {{SPV_OPERAND_TYPE_ID, {successor_block_id}}, + {SPV_OPERAND_TYPE_SELECTION_CONTROL, + {SpvSelectionControlMaskNone}}}))); + existing_block->terminator()->SetOpcode(SpvOpBranchConditional); existing_block->terminator()->SetInOperands( - {{SPV_OPERAND_TYPE_ID, {bool_id}}, - {SPV_OPERAND_TYPE_ID, {message_.condition_value() ? successor_block_id : message_.fresh_id()}}, - {SPV_OPERAND_TYPE_ID, {message_.condition_value() ? message_.fresh_id() : successor_block_id}}}); - enclosing_function->InsertBasicBlockAfter(std::move(new_block), existing_block); + {{SPV_OPERAND_TYPE_ID, {bool_id}}, + {SPV_OPERAND_TYPE_ID, + {message_.condition_value() ? successor_block_id + : message_.fresh_id()}}, + {SPV_OPERAND_TYPE_ID, + {message_.condition_value() ? message_.fresh_id() + : successor_block_id}}}); + enclosing_function->InsertBasicBlockAfter(std::move(new_block), + existing_block); + + fact_manager->AddFactIdIsDead(message_.fresh_id()); + context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone); }
diff --git a/source/fuzz/transformation_add_dead_block.h b/source/fuzz/transformation_add_dead_block.h index 203b9a9..3c85615 100644 --- a/source/fuzz/transformation_add_dead_block.h +++ b/source/fuzz/transformation_add_dead_block.h
@@ -28,8 +28,7 @@ explicit TransformationAddDeadBlock( const protobufs::TransformationAddDeadBlock& message); - TransformationAddDeadBlock(uint32_t fresh_id, - uint32_t existing_block, + TransformationAddDeadBlock(uint32_t fresh_id, uint32_t existing_block, bool condition_value, std::vector<uint32_t> phi_id);
diff --git a/source/fuzz/transformation_add_dead_break.cpp b/source/fuzz/transformation_add_dead_break.cpp index 9fa2df6..43847fa 100644 --- a/source/fuzz/transformation_add_dead_break.cpp +++ b/source/fuzz/transformation_add_dead_break.cpp
@@ -111,7 +111,8 @@ opt::IRContext* context, const FactManager& /*unused*/) const { // First, we check that a constant with the same value as // |message_.break_condition_value| is present. - if (!fuzzerutil::MaybeGetBoolConstantId(context, message_.break_condition_value())) { + if (!fuzzerutil::MaybeGetBoolConstantId(context, + message_.break_condition_value())) { // The required constant is not present, so the transformation cannot be // applied. return false;
diff --git a/source/fuzz/transformation_add_dead_continue.cpp b/source/fuzz/transformation_add_dead_continue.cpp index b614a23..ffa182e 100644 --- a/source/fuzz/transformation_add_dead_continue.cpp +++ b/source/fuzz/transformation_add_dead_continue.cpp
@@ -37,7 +37,8 @@ opt::IRContext* context, const FactManager& /*unused*/) const { // First, we check that a constant with the same value as // |message_.continue_condition_value| is present. - if (!fuzzerutil::MaybeGetBoolConstantId(context, message_.continue_condition_value())) { + if (!fuzzerutil::MaybeGetBoolConstantId( + context, message_.continue_condition_value())) { // The required constant is not present, so the transformation cannot be // applied. return false;
diff --git a/source/fuzz/transformation_split_block.cpp b/source/fuzz/transformation_split_block.cpp index 9f6da7c..f05e77b 100644 --- a/source/fuzz/transformation_split_block.cpp +++ b/source/fuzz/transformation_split_block.cpp
@@ -80,7 +80,7 @@ } void TransformationSplitBlock::Apply(opt::IRContext* context, - FactManager* /*unused*/) const { + FactManager* fact_manager) const { opt::Instruction* instruction_to_split_before = FindInstruction(message_.instruction_to_split_before(), context); opt::BasicBlock* block_to_split = @@ -114,6 +114,13 @@ "one predecessor."); phi_inst->SetInOperand(1, {block_to_split->id()}); }); + + // If the block being split was dead, the new block arising from the split is + // also dead. + if (fact_manager->IdIsDead(block_to_split->id())) { + fact_manager->AddFactIdIsDead(message_.fresh_id()); + } + // Invalidate all analyses context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone); }
diff --git a/test/fuzz/transformation_add_dead_block_test.cpp b/test/fuzz/transformation_add_dead_block_test.cpp index 1044647..fbb9c3e 100644 --- a/test/fuzz/transformation_add_dead_block_test.cpp +++ b/test/fuzz/transformation_add_dead_block_test.cpp
@@ -48,16 +48,20 @@ FactManager fact_manager; // Id 4 is already in use - ASSERT_FALSE(TransformationAddDeadBlock(4, 5, true, {}).IsApplicable(context.get(), fact_manager)); + ASSERT_FALSE(TransformationAddDeadBlock(4, 5, true, {}) + .IsApplicable(context.get(), fact_manager)); // Id 7 is not a block - ASSERT_FALSE(TransformationAddDeadBlock(100, 7, true, {}).IsApplicable(context.get(), fact_manager)); + ASSERT_FALSE(TransformationAddDeadBlock(100, 7, true, {}) + .IsApplicable(context.get(), fact_manager)); TransformationAddDeadBlock transformation(100, 5, true, {}); ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager)); transformation.Apply(context.get(), &fact_manager); ASSERT_TRUE(IsValid(env, context.get())); + ASSERT_TRUE(fact_manager.IdIsDead(100)); + std::string after_transformation = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" @@ -72,7 +76,7 @@ %7 = OpConstantTrue %6 %4 = OpFunction %2 None %3 %5 = OpLabel - OpSelectionMerge %8 + OpSelectionMerge %8 None OpBranchConditional %7 %8 %100 %100 = OpLabel OpBranch %8 @@ -83,11 +87,12 @@ ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); } -// Target block must not be merge or continue +// TODO Target block must not be merge or continue -// Source block must not be loop head +// TODO Source block must not be loop head -// Target block can start with OpPhi; need to give suitable ids in that case +// TODO Target block can start with OpPhi; need to give suitable ids in that +// case } // namespace } // namespace fuzz
diff --git a/test/fuzz/transformation_split_block_test.cpp b/test/fuzz/transformation_split_block_test.cpp index d162e07..8c46bda 100644 --- a/test/fuzz/transformation_split_block_test.cpp +++ b/test/fuzz/transformation_split_block_test.cpp
@@ -774,6 +774,77 @@ ASSERT_TRUE(IsEqual(env, after_split, context.get())); } +TEST(TransformationSplitBlockTest, DeadBlockShouldSplitToTwoDeadBlocks) { + // This checks that if a block B is marked as dead, it should split into a + // pair of dead blocks. + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeBool + %7 = OpConstantFalse %6 + %4 = OpFunction %2 None %3 + %5 = OpLabel + OpSelectionMerge %9 None + OpBranchConditional %7 %8 %9 + %8 = OpLabel + OpBranch %9 + %9 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + + FactManager fact_manager; + + // Record the fact that block 8 is dead. + fact_manager.AddFactIdIsDead(8); + + auto split = TransformationSplitBlock( + MakeInstructionDescriptor(8, SpvOpBranch, 0), 100); + ASSERT_TRUE(split.IsApplicable(context.get(), fact_manager)); + split.Apply(context.get(), &fact_manager); + ASSERT_TRUE(IsValid(env, context.get())); + + ASSERT_TRUE(fact_manager.IdIsDead(8)); + ASSERT_TRUE(fact_manager.IdIsDead(100)); + + std::string after_split = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeBool + %7 = OpConstantFalse %6 + %4 = OpFunction %2 None %3 + %5 = OpLabel + OpSelectionMerge %9 None + OpBranchConditional %7 %8 %9 + %8 = OpLabel + OpBranch %100 + %100 = OpLabel + OpBranch %9 + %9 = OpLabel + OpReturn + OpFunctionEnd + )"; + ASSERT_TRUE(IsEqual(env, after_split, context.get())); +} + } // namespace } // namespace fuzz } // namespace spvtools