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