spirv-fuzz: Fix the bug in TransformationReplaceBranchFromDeadBlockWithExit (#4140)

Fixes #4136.
This commit is contained in:
Vasyl Teliman 2021-03-05 16:27:37 +02:00 committed by GitHub
parent 7d514cf1c7
commit e6a9f4e430
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 504 additions and 97 deletions

View File

@ -25,6 +25,25 @@ namespace fuzz {
namespace fuzzerutil {
namespace {
// A utility class that uses RAII to change and restore the terminator
// instruction of the |block|.
class ChangeTerminatorRAII {
public:
explicit ChangeTerminatorRAII(opt::BasicBlock* block,
opt::Instruction new_terminator)
: block_(block), old_terminator_(std::move(*block->terminator())) {
*block_->terminator() = std::move(new_terminator);
}
~ChangeTerminatorRAII() {
*block_->terminator() = std::move(old_terminator_);
}
private:
opt::BasicBlock* block_;
opt::Instruction old_terminator_;
};
uint32_t MaybeGetOpConstant(opt::IRContext* ir_context,
const TransformationContext& transformation_context,
const std::vector<uint32_t>& words,
@ -163,35 +182,46 @@ bool PhiIdsOkForNewEdge(
return true;
}
void AddUnreachableEdgeAndUpdateOpPhis(
opt::IRContext* context, opt::BasicBlock* bb_from, opt::BasicBlock* bb_to,
uint32_t bool_id,
const google::protobuf::RepeatedField<google::protobuf::uint32>& phi_ids) {
assert(PhiIdsOkForNewEdge(context, bb_from, bb_to, phi_ids) &&
"Precondition on phi_ids is not satisfied");
opt::Instruction CreateUnreachableEdgeInstruction(opt::IRContext* ir_context,
uint32_t bb_from_id,
uint32_t bb_to_id,
uint32_t bool_id) {
const auto* bb_from = MaybeFindBlock(ir_context, bb_from_id);
assert(bb_from && "|bb_from_id| is invalid");
assert(MaybeFindBlock(ir_context, bb_to_id) && "|bb_to_id| is invalid");
assert(bb_from->terminator()->opcode() == SpvOpBranch &&
"Precondition on terminator of bb_from is not satisfied");
// Get the id of the boolean constant to be used as the condition.
auto condition_inst = context->get_def_use_mgr()->GetDef(bool_id);
auto condition_inst = ir_context->get_def_use_mgr()->GetDef(bool_id);
assert(condition_inst &&
(condition_inst->opcode() == SpvOpConstantTrue ||
condition_inst->opcode() == SpvOpConstantFalse) &&
"|bool_id| is invalid");
auto condition_value = condition_inst->opcode() == SpvOpConstantTrue;
const bool from_to_edge_already_exists = bb_from->IsSuccessor(bb_to);
auto successor = bb_from->terminator()->GetSingleWordInOperand(0);
auto successor_id = bb_from->terminator()->GetSingleWordInOperand(0);
// Add the dead branch, by turning OpBranch into OpBranchConditional, and
// ordering the targets depending on whether the given boolean corresponds to
// true or false.
bb_from->terminator()->SetOpcode(SpvOpBranchConditional);
bb_from->terminator()->SetInOperands(
return opt::Instruction(
ir_context, SpvOpBranchConditional, 0, 0,
{{SPV_OPERAND_TYPE_ID, {bool_id}},
{SPV_OPERAND_TYPE_ID, {condition_value ? successor : bb_to->id()}},
{SPV_OPERAND_TYPE_ID, {condition_value ? bb_to->id() : successor}}});
{SPV_OPERAND_TYPE_ID, {condition_value ? successor_id : bb_to_id}},
{SPV_OPERAND_TYPE_ID, {condition_value ? bb_to_id : successor_id}}});
}
void AddUnreachableEdgeAndUpdateOpPhis(
opt::IRContext* context, opt::BasicBlock* bb_from, opt::BasicBlock* bb_to,
uint32_t bool_id,
const google::protobuf::RepeatedField<google::protobuf::uint32>& phi_ids) {
assert(PhiIdsOkForNewEdge(context, bb_from, bb_to, phi_ids) &&
"Precondition on phi_ids is not satisfied");
const bool from_to_edge_already_exists = bb_from->IsSuccessor(bb_to);
*bb_from->terminator() = CreateUnreachableEdgeInstruction(
context, bb_from->id(), bb_to->id(), bool_id);
// Update OpPhi instructions in the target block if this branch adds a
// previously non-existent edge from source to target.
@ -1856,6 +1886,104 @@ std::set<uint32_t> GetReachableReturnBlocks(opt::IRContext* ir_context,
return result;
}
bool NewTerminatorPreservesDominationRules(opt::IRContext* ir_context,
uint32_t block_id,
opt::Instruction new_terminator) {
auto* mutated_block = MaybeFindBlock(ir_context, block_id);
assert(mutated_block && "|block_id| is invalid");
ChangeTerminatorRAII change_terminator_raii(mutated_block,
std::move(new_terminator));
opt::DominatorAnalysis dominator_analysis;
dominator_analysis.InitializeTree(*ir_context->cfg(),
mutated_block->GetParent());
// Check that each dominator appears before each dominated block.
std::unordered_map<uint32_t, size_t> positions;
for (const auto& block : *mutated_block->GetParent()) {
positions[block.id()] = positions.size();
}
std::queue<uint32_t> q({mutated_block->GetParent()->begin()->id()});
std::unordered_set<uint32_t> visited;
while (!q.empty()) {
auto block = q.front();
q.pop();
visited.insert(block);
auto success = ir_context->cfg()->block(block)->WhileEachSuccessorLabel(
[&positions, &visited, &dominator_analysis, block, &q](uint32_t id) {
if (id == block) {
// Handle the case when loop header and continue target are the same
// block.
return true;
}
if (dominator_analysis.Dominates(block, id) &&
positions[block] > positions[id]) {
// |block| dominates |id| but appears after |id| - violates
// domination rules.
return false;
}
if (!visited.count(id)) {
q.push(id);
}
return true;
});
if (!success) {
return false;
}
}
// For each instruction in the |block->GetParent()| function check whether
// all its dependencies satisfy domination rules (i.e. all id operands
// dominate that instruction).
for (const auto& block : *mutated_block->GetParent()) {
if (!dominator_analysis.IsReachable(&block)) {
// If some block is not reachable then we don't need to worry about the
// preservation of domination rules for its instructions.
continue;
}
for (const auto& inst : block) {
for (uint32_t i = 0; i < inst.NumInOperands();
i += inst.opcode() == SpvOpPhi ? 2 : 1) {
const auto& operand = inst.GetInOperand(i);
if (!spvIsInIdType(operand.type)) {
continue;
}
if (MaybeFindBlock(ir_context, operand.words[0])) {
// Ignore operands that refer to OpLabel instructions.
continue;
}
const auto* dependency_block =
ir_context->get_instr_block(operand.words[0]);
if (!dependency_block) {
// A global instruction always dominates all instructions in any
// function.
continue;
}
auto domination_target_id = inst.opcode() == SpvOpPhi
? inst.GetSingleWordInOperand(i + 1)
: block.id();
if (!dominator_analysis.Dominates(dependency_block->id(),
domination_target_id)) {
return false;
}
}
}
}
return true;
}
} // namespace fuzzerutil
} // namespace fuzz
} // namespace spvtools

View File

@ -68,6 +68,16 @@ bool PhiIdsOkForNewEdge(
opt::IRContext* context, opt::BasicBlock* bb_from, opt::BasicBlock* bb_to,
const google::protobuf::RepeatedField<google::protobuf::uint32>& phi_ids);
// Returns an OpBranchConditional instruction that will create an unreachable
// branch from |bb_from_id| to |bb_to_id|. |bool_id| must be a result id of
// either OpConstantTrue or OpConstantFalse. Based on the opcode of |bool_id|,
// operands of the returned instruction will be positioned in a way that the
// branch from |bb_from_id| to |bb_to_id| is always unreachable.
opt::Instruction CreateUnreachableEdgeInstruction(opt::IRContext* ir_context,
uint32_t bb_from_id,
uint32_t bb_to_id,
uint32_t bool_id);
// Requires that |bool_id| is a valid result id of either OpConstantTrue or
// OpConstantFalse, that PhiIdsOkForNewEdge(context, bb_from, bb_to, phi_ids)
// holds, and that bb_from ends with "OpBranch %some_block". Turns OpBranch
@ -597,6 +607,14 @@ bool InstructionHasNoSideEffects(const opt::Instruction& instruction);
std::set<uint32_t> GetReachableReturnBlocks(opt::IRContext* ir_context,
uint32_t function_id);
// Returns true if changing terminator instruction to |new_terminator| in the
// basic block with id |block_id| preserves domination rules and valid block
// order (i.e. dominator must always appear before dominated in the CFG).
// Returns false otherwise.
bool NewTerminatorPreservesDominationRules(opt::IRContext* ir_context,
uint32_t block_id,
opt::Instruction new_terminator);
} // namespace fuzzerutil
} // namespace fuzz
} // namespace spvtools

View File

@ -112,9 +112,10 @@ bool TransformationAddDeadBreak::IsApplicable(
const TransformationContext& transformation_context) const {
// First, we check that a constant with the same value as
// |message_.break_condition_value| is present.
if (!fuzzerutil::MaybeGetBoolConstant(ir_context, transformation_context,
message_.break_condition_value(),
false)) {
const auto bool_id =
fuzzerutil::MaybeGetBoolConstant(ir_context, transformation_context,
message_.break_condition_value(), false);
if (!bool_id) {
// The required constant is not present, so the transformation cannot be
// applied.
return false;
@ -171,25 +172,23 @@ bool TransformationAddDeadBreak::IsApplicable(
}
// Adding the dead break is only valid if SPIR-V rules related to dominance
// hold. Rather than checking these rules explicitly, we defer to the
// validator. We make a clone of the module, apply the transformation to the
// clone, and check whether the transformed clone is valid.
//
// In principle some of the above checks could be removed, with more reliance
// being places on the validator. This should be revisited if we are sure
// the validator is complete with respect to checking structured control flow
// rules.
auto cloned_context = fuzzerutil::CloneIRContext(ir_context);
ApplyImpl(cloned_context.get(), transformation_context);
return fuzzerutil::IsValid(cloned_context.get(),
transformation_context.GetValidatorOptions(),
fuzzerutil::kSilentMessageConsumer);
// hold.
return fuzzerutil::NewTerminatorPreservesDominationRules(
ir_context, message_.from_block(),
fuzzerutil::CreateUnreachableEdgeInstruction(
ir_context, message_.from_block(), message_.to_block(), bool_id));
}
void TransformationAddDeadBreak::Apply(
opt::IRContext* ir_context,
TransformationContext* transformation_context) const {
ApplyImpl(ir_context, *transformation_context);
fuzzerutil::AddUnreachableEdgeAndUpdateOpPhis(
ir_context, ir_context->cfg()->block(message_.from_block()),
ir_context->cfg()->block(message_.to_block()),
fuzzerutil::MaybeGetBoolConstant(ir_context, *transformation_context,
message_.break_condition_value(), false),
message_.phi_id());
// Invalidate all analyses
ir_context->InvalidateAnalysesExceptFor(
opt::IRContext::Analysis::kAnalysisNone);
@ -201,17 +200,6 @@ protobufs::Transformation TransformationAddDeadBreak::ToMessage() const {
return result;
}
void TransformationAddDeadBreak::ApplyImpl(
spvtools::opt::IRContext* ir_context,
const TransformationContext& transformation_context) const {
fuzzerutil::AddUnreachableEdgeAndUpdateOpPhis(
ir_context, ir_context->cfg()->block(message_.from_block()),
ir_context->cfg()->block(message_.to_block()),
fuzzerutil::MaybeGetBoolConstant(ir_context, transformation_context,
message_.break_condition_value(), false),
message_.phi_id());
}
std::unordered_set<uint32_t> TransformationAddDeadBreak::GetFreshIds() const {
return std::unordered_set<uint32_t>();
}

View File

@ -71,15 +71,6 @@ class TransformationAddDeadBreak : public Transformation {
bool AddingBreakRespectsStructuredControlFlow(opt::IRContext* ir_context,
opt::BasicBlock* bb_from) const;
// Used by 'Apply' to actually apply the transformation to the module of
// interest, and by 'IsApplicable' to do a dry-run of the transformation on a
// cloned module, in order to check that the transformation leads to a valid
// module. This is only invoked by 'IsApplicable' after certain basic
// applicability checks have been made, ensuring that the invocation of this
// method is legal.
void ApplyImpl(opt::IRContext* ir_context,
const TransformationContext& transformation_context) const;
protobufs::TransformationAddDeadBreak message_;
};

View File

@ -38,9 +38,10 @@ bool TransformationAddDeadContinue::IsApplicable(
const TransformationContext& transformation_context) const {
// First, we check that a constant with the same value as
// |message_.continue_condition_value| is present.
if (!fuzzerutil::MaybeGetBoolConstant(ir_context, transformation_context,
message_.continue_condition_value(),
false)) {
const auto bool_id = fuzzerutil::MaybeGetBoolConstant(
ir_context, transformation_context, message_.continue_condition_value(),
false);
if (!bool_id) {
// The required constant is not present, so the transformation cannot be
// applied.
return false;
@ -111,25 +112,30 @@ bool TransformationAddDeadContinue::IsApplicable(
}
// Adding the dead break is only valid if SPIR-V rules related to dominance
// hold. Rather than checking these rules explicitly, we defer to the
// validator. We make a clone of the module, apply the transformation to the
// clone, and check whether the transformed clone is valid.
//
// In principle some of the above checks could be removed, with more reliance
// being placed on the validator. This should be revisited if we are sure
// the validator is complete with respect to checking structured control flow
// rules.
auto cloned_context = fuzzerutil::CloneIRContext(ir_context);
ApplyImpl(cloned_context.get(), transformation_context);
return fuzzerutil::IsValid(cloned_context.get(),
transformation_context.GetValidatorOptions(),
fuzzerutil::kSilentMessageConsumer);
// hold.
return fuzzerutil::NewTerminatorPreservesDominationRules(
ir_context, message_.from_block(),
fuzzerutil::CreateUnreachableEdgeInstruction(
ir_context, message_.from_block(), continue_block, bool_id));
}
void TransformationAddDeadContinue::Apply(
opt::IRContext* ir_context,
TransformationContext* transformation_context) const {
ApplyImpl(ir_context, *transformation_context);
auto bb_from = ir_context->cfg()->block(message_.from_block());
auto continue_block =
bb_from->IsLoopHeader()
? bb_from->ContinueBlockId()
: ir_context->GetStructuredCFGAnalysis()->LoopContinueBlock(
message_.from_block());
assert(continue_block && "message_.from_block must be in a loop.");
fuzzerutil::AddUnreachableEdgeAndUpdateOpPhis(
ir_context, bb_from, ir_context->cfg()->block(continue_block),
fuzzerutil::MaybeGetBoolConstant(ir_context, *transformation_context,
message_.continue_condition_value(),
false),
message_.phi_id());
// Invalidate all analyses
ir_context->InvalidateAnalysesExceptFor(
opt::IRContext::Analysis::kAnalysisNone);
@ -141,24 +147,6 @@ protobufs::Transformation TransformationAddDeadContinue::ToMessage() const {
return result;
}
void TransformationAddDeadContinue::ApplyImpl(
spvtools::opt::IRContext* ir_context,
const TransformationContext& transformation_context) const {
auto bb_from = ir_context->cfg()->block(message_.from_block());
auto continue_block =
bb_from->IsLoopHeader()
? bb_from->ContinueBlockId()
: ir_context->GetStructuredCFGAnalysis()->LoopContinueBlock(
message_.from_block());
assert(continue_block && "message_.from_block must be in a loop.");
fuzzerutil::AddUnreachableEdgeAndUpdateOpPhis(
ir_context, bb_from, ir_context->cfg()->block(continue_block),
fuzzerutil::MaybeGetBoolConstant(ir_context, transformation_context,
message_.continue_condition_value(),
false),
message_.phi_id());
}
std::unordered_set<uint32_t> TransformationAddDeadContinue::GetFreshIds()
const {
return std::unordered_set<uint32_t>();

View File

@ -68,15 +68,6 @@ class TransformationAddDeadContinue : public Transformation {
protobufs::Transformation ToMessage() const override;
private:
// Used by 'Apply' to actually apply the transformation to the module of
// interest, and by 'IsApplicable' to do a dry-run of the transformation on a
// cloned module, in order to check that the transformation leads to a valid
// module. This is only invoked by 'IsApplicable' after certain basic
// applicability checks have been made, ensuring that the invocation of this
// method is legal.
void ApplyImpl(opt::IRContext* ir_context,
const TransformationContext& transformation_context) const;
protobufs::TransformationAddDeadContinue message_;
};

View File

@ -162,7 +162,10 @@ bool TransformationReplaceBranchFromDeadBlockWithExit::BlockIsSuitable(
if (ir_context->cfg()->preds(successor->id()).size() < 2) {
return false;
}
return true;
// Make sure that domination rules are satisfied when we remove the branch
// from the |block| to its |successor|.
return fuzzerutil::NewTerminatorPreservesDominationRules(
ir_context, block.id(), {ir_context, SpvOpUnreachable});
}
} // namespace fuzz

View File

@ -41,13 +41,17 @@ class TransformationReplaceBranchFromDeadBlockWithExit : public Transformation {
// predecessor
// - |message_.opcode()| must be one of OpKill, OpReturn, OpReturnValue and
// OpUnreachable
// - |message_.opcode()| can only be OpKill the module's entry points all
// - |message_.opcode()| can only be OpKill if the module's entry points all
// have Fragment execution mode
// - |message_.opcode()| can only be OpReturn if the return type of the
// function containing the block is void
// - If |message_.opcode()| is OpReturnValue then |message_.return_value_id|
// must be an id that is available at the block terminator and that matches
// the return type of the enclosing function
// - Domination rules should be preserved when we apply this transformation.
// In particular, if some block appears after the |block_id|'s successor in
// the CFG, then that block cannot dominate |block_id|'s successor when this
// transformation is applied.
bool IsApplicable(
opt::IRContext* ir_context,
const TransformationContext& transformation_context) const override;

View File

@ -566,6 +566,302 @@ TEST(TransformationReplaceBranchFromDeadBlockWithExitTest, OpPhi) {
ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
}
TEST(TransformationReplaceBranchFromDeadBlockWithExitTest,
DominatorAfterDeadBlockSuccessor) {
std::string shader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 320
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeBool
%7 = OpConstantFalse %6
%4 = OpFunction %2 None %3
%5 = OpLabel
OpSelectionMerge %8 None
OpBranchConditional %7 %9 %10
%9 = OpLabel
OpBranch %11
%11 = OpLabel
%12 = OpCopyObject %6 %7
OpBranch %8
%10 = OpLabel
OpBranch %13
%8 = OpLabel
OpReturn
%13 = OpLabel
OpBranch %8
OpFunctionEnd
)";
const auto env = SPV_ENV_UNIVERSAL_1_4;
const auto consumer = nullptr;
const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
spvtools::ValidatorOptions validator_options;
ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
kConsoleMessageConsumer));
TransformationContext transformation_context(
MakeUnique<FactManager>(context.get()), validator_options);
transformation_context.GetFactManager()->AddFactBlockIsDead(9);
transformation_context.GetFactManager()->AddFactBlockIsDead(11);
ASSERT_FALSE(
TransformationReplaceBranchFromDeadBlockWithExit(11, SpvOpUnreachable, 0)
.IsApplicable(context.get(), transformation_context));
}
TEST(TransformationReplaceBranchFromDeadBlockWithExitTest,
UnreachableSuccessor) {
std::string shader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 320
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeBool
%7 = OpConstantFalse %6
%4 = OpFunction %2 None %3
%5 = OpLabel
OpSelectionMerge %8 None
OpBranchConditional %7 %9 %10
%9 = OpLabel
OpBranch %11
%11 = OpLabel
%12 = OpCopyObject %6 %7
OpBranch %8
%10 = OpLabel
OpReturn
%8 = OpLabel
OpReturn
%13 = OpLabel
OpBranch %8
OpFunctionEnd
)";
const auto env = SPV_ENV_UNIVERSAL_1_4;
const auto consumer = nullptr;
const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
spvtools::ValidatorOptions validator_options;
ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
kConsoleMessageConsumer));
TransformationContext transformation_context(
MakeUnique<FactManager>(context.get()), validator_options);
transformation_context.GetFactManager()->AddFactBlockIsDead(9);
transformation_context.GetFactManager()->AddFactBlockIsDead(11);
TransformationReplaceBranchFromDeadBlockWithExit transformation(
11, SpvOpUnreachable, 0);
ASSERT_TRUE(
transformation.IsApplicable(context.get(), transformation_context));
transformation.Apply(context.get(), &transformation_context);
ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
kConsoleMessageConsumer));
std::string after_transformation = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 320
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeBool
%7 = OpConstantFalse %6
%4 = OpFunction %2 None %3
%5 = OpLabel
OpSelectionMerge %8 None
OpBranchConditional %7 %9 %10
%9 = OpLabel
OpBranch %11
%11 = OpLabel
%12 = OpCopyObject %6 %7
OpUnreachable
%10 = OpLabel
OpReturn
%8 = OpLabel
OpReturn
%13 = OpLabel
OpBranch %8
OpFunctionEnd
)";
ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
}
TEST(TransformationReplaceBranchFromDeadBlockWithExitTest,
DeadBlockAfterItsSuccessor) {
std::string shader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 320
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeBool
%7 = OpConstantTrue %6
%4 = OpFunction %2 None %3
%5 = OpLabel
OpSelectionMerge %8 None
OpBranchConditional %7 %9 %10
%9 = OpLabel
OpBranch %11
%11 = OpLabel
%12 = OpCopyObject %6 %7
OpBranch %8
%10 = OpLabel
OpBranch %13
%8 = OpLabel
OpReturn
%13 = OpLabel
OpBranch %8
OpFunctionEnd
)";
const auto env = SPV_ENV_UNIVERSAL_1_4;
const auto consumer = nullptr;
const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
spvtools::ValidatorOptions validator_options;
ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
kConsoleMessageConsumer));
TransformationContext transformation_context(
MakeUnique<FactManager>(context.get()), validator_options);
transformation_context.GetFactManager()->AddFactBlockIsDead(10);
transformation_context.GetFactManager()->AddFactBlockIsDead(13);
TransformationReplaceBranchFromDeadBlockWithExit transformation(
13, SpvOpUnreachable, 0);
ASSERT_TRUE(
transformation.IsApplicable(context.get(), transformation_context));
transformation.Apply(context.get(), &transformation_context);
ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
kConsoleMessageConsumer));
std::string after_transformation = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 320
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeBool
%7 = OpConstantTrue %6
%4 = OpFunction %2 None %3
%5 = OpLabel
OpSelectionMerge %8 None
OpBranchConditional %7 %9 %10
%9 = OpLabel
OpBranch %11
%11 = OpLabel
%12 = OpCopyObject %6 %7
OpBranch %8
%10 = OpLabel
OpBranch %13
%8 = OpLabel
OpReturn
%13 = OpLabel
OpUnreachable
OpFunctionEnd
)";
ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
}
TEST(TransformationReplaceBranchFromDeadBlockWithExitTest,
BranchToOuterMergeBlock) {
std::string shader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 320
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeBool
%7 = OpConstantTrue %6
%15 = OpTypeInt 32 0
%14 = OpUndef %15
%4 = OpFunction %2 None %3
%5 = OpLabel
OpSelectionMerge %8 None
OpSwitch %14 %9 1 %8
%9 = OpLabel
OpSelectionMerge %10 None
OpBranchConditional %7 %11 %10
%8 = OpLabel
OpReturn
%11 = OpLabel
OpBranch %8
%10 = OpLabel
OpBranch %8
OpFunctionEnd
)";
const auto env = SPV_ENV_UNIVERSAL_1_4;
const auto consumer = nullptr;
const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
spvtools::ValidatorOptions validator_options;
ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
kConsoleMessageConsumer));
TransformationContext transformation_context(
MakeUnique<FactManager>(context.get()), validator_options);
transformation_context.GetFactManager()->AddFactBlockIsDead(10);
TransformationReplaceBranchFromDeadBlockWithExit transformation(
10, SpvOpUnreachable, 0);
ASSERT_TRUE(
transformation.IsApplicable(context.get(), transformation_context));
transformation.Apply(context.get(), &transformation_context);
ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
kConsoleMessageConsumer));
std::string after_transformation = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 320
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeBool
%7 = OpConstantTrue %6
%15 = OpTypeInt 32 0
%14 = OpUndef %15
%4 = OpFunction %2 None %3
%5 = OpLabel
OpSelectionMerge %8 None
OpSwitch %14 %9 1 %8
%9 = OpLabel
OpSelectionMerge %10 None
OpBranchConditional %7 %11 %10
%8 = OpLabel
OpReturn
%11 = OpLabel
OpBranch %8
%10 = OpLabel
OpUnreachable
OpFunctionEnd
)";
ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
}
} // namespace
} // namespace fuzz
} // namespace spvtools