mirror of
https://gitee.com/openharmony/third_party_spirv-tools
synced 2024-11-23 15:30:36 +00:00
spirv-fuzz: Fix the bug in TransformationReplaceBranchFromDeadBlockWithExit (#4140)
Fixes #4136.
This commit is contained in:
parent
7d514cf1c7
commit
e6a9f4e430
@ -25,6 +25,25 @@ namespace fuzz {
|
|||||||
namespace fuzzerutil {
|
namespace fuzzerutil {
|
||||||
namespace {
|
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,
|
uint32_t MaybeGetOpConstant(opt::IRContext* ir_context,
|
||||||
const TransformationContext& transformation_context,
|
const TransformationContext& transformation_context,
|
||||||
const std::vector<uint32_t>& words,
|
const std::vector<uint32_t>& words,
|
||||||
@ -163,35 +182,46 @@ bool PhiIdsOkForNewEdge(
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddUnreachableEdgeAndUpdateOpPhis(
|
opt::Instruction CreateUnreachableEdgeInstruction(opt::IRContext* ir_context,
|
||||||
opt::IRContext* context, opt::BasicBlock* bb_from, opt::BasicBlock* bb_to,
|
uint32_t bb_from_id,
|
||||||
uint32_t bool_id,
|
uint32_t bb_to_id,
|
||||||
const google::protobuf::RepeatedField<google::protobuf::uint32>& phi_ids) {
|
uint32_t bool_id) {
|
||||||
assert(PhiIdsOkForNewEdge(context, bb_from, bb_to, phi_ids) &&
|
const auto* bb_from = MaybeFindBlock(ir_context, bb_from_id);
|
||||||
"Precondition on phi_ids is not satisfied");
|
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 &&
|
assert(bb_from->terminator()->opcode() == SpvOpBranch &&
|
||||||
"Precondition on terminator of bb_from is not satisfied");
|
"Precondition on terminator of bb_from is not satisfied");
|
||||||
|
|
||||||
// Get the id of the boolean constant to be used as the condition.
|
// 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 &&
|
assert(condition_inst &&
|
||||||
(condition_inst->opcode() == SpvOpConstantTrue ||
|
(condition_inst->opcode() == SpvOpConstantTrue ||
|
||||||
condition_inst->opcode() == SpvOpConstantFalse) &&
|
condition_inst->opcode() == SpvOpConstantFalse) &&
|
||||||
"|bool_id| is invalid");
|
"|bool_id| is invalid");
|
||||||
|
|
||||||
auto condition_value = condition_inst->opcode() == SpvOpConstantTrue;
|
auto condition_value = condition_inst->opcode() == SpvOpConstantTrue;
|
||||||
|
auto successor_id = bb_from->terminator()->GetSingleWordInOperand(0);
|
||||||
const bool from_to_edge_already_exists = bb_from->IsSuccessor(bb_to);
|
|
||||||
auto successor = bb_from->terminator()->GetSingleWordInOperand(0);
|
|
||||||
|
|
||||||
// Add the dead branch, by turning OpBranch into OpBranchConditional, and
|
// Add the dead branch, by turning OpBranch into OpBranchConditional, and
|
||||||
// ordering the targets depending on whether the given boolean corresponds to
|
// ordering the targets depending on whether the given boolean corresponds to
|
||||||
// true or false.
|
// true or false.
|
||||||
bb_from->terminator()->SetOpcode(SpvOpBranchConditional);
|
return opt::Instruction(
|
||||||
bb_from->terminator()->SetInOperands(
|
ir_context, SpvOpBranchConditional, 0, 0,
|
||||||
{{SPV_OPERAND_TYPE_ID, {bool_id}},
|
{{SPV_OPERAND_TYPE_ID, {bool_id}},
|
||||||
{SPV_OPERAND_TYPE_ID, {condition_value ? successor : bb_to->id()}},
|
{SPV_OPERAND_TYPE_ID, {condition_value ? successor_id : bb_to_id}},
|
||||||
{SPV_OPERAND_TYPE_ID, {condition_value ? bb_to->id() : successor}}});
|
{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
|
// Update OpPhi instructions in the target block if this branch adds a
|
||||||
// previously non-existent edge from source to target.
|
// previously non-existent edge from source to target.
|
||||||
@ -1856,6 +1886,104 @@ std::set<uint32_t> GetReachableReturnBlocks(opt::IRContext* ir_context,
|
|||||||
return result;
|
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 fuzzerutil
|
||||||
} // namespace fuzz
|
} // namespace fuzz
|
||||||
} // namespace spvtools
|
} // namespace spvtools
|
||||||
|
@ -68,6 +68,16 @@ bool PhiIdsOkForNewEdge(
|
|||||||
opt::IRContext* context, opt::BasicBlock* bb_from, opt::BasicBlock* bb_to,
|
opt::IRContext* context, opt::BasicBlock* bb_from, opt::BasicBlock* bb_to,
|
||||||
const google::protobuf::RepeatedField<google::protobuf::uint32>& phi_ids);
|
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
|
// Requires that |bool_id| is a valid result id of either OpConstantTrue or
|
||||||
// OpConstantFalse, that PhiIdsOkForNewEdge(context, bb_from, bb_to, phi_ids)
|
// OpConstantFalse, that PhiIdsOkForNewEdge(context, bb_from, bb_to, phi_ids)
|
||||||
// holds, and that bb_from ends with "OpBranch %some_block". Turns OpBranch
|
// 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,
|
std::set<uint32_t> GetReachableReturnBlocks(opt::IRContext* ir_context,
|
||||||
uint32_t function_id);
|
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 fuzzerutil
|
||||||
} // namespace fuzz
|
} // namespace fuzz
|
||||||
} // namespace spvtools
|
} // namespace spvtools
|
||||||
|
@ -112,9 +112,10 @@ bool TransformationAddDeadBreak::IsApplicable(
|
|||||||
const TransformationContext& transformation_context) const {
|
const TransformationContext& transformation_context) const {
|
||||||
// First, we check that a constant with the same value as
|
// First, we check that a constant with the same value as
|
||||||
// |message_.break_condition_value| is present.
|
// |message_.break_condition_value| is present.
|
||||||
if (!fuzzerutil::MaybeGetBoolConstant(ir_context, transformation_context,
|
const auto bool_id =
|
||||||
message_.break_condition_value(),
|
fuzzerutil::MaybeGetBoolConstant(ir_context, transformation_context,
|
||||||
false)) {
|
message_.break_condition_value(), false);
|
||||||
|
if (!bool_id) {
|
||||||
// The required constant is not present, so the transformation cannot be
|
// The required constant is not present, so the transformation cannot be
|
||||||
// applied.
|
// applied.
|
||||||
return false;
|
return false;
|
||||||
@ -171,25 +172,23 @@ bool TransformationAddDeadBreak::IsApplicable(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Adding the dead break is only valid if SPIR-V rules related to dominance
|
// 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
|
// hold.
|
||||||
// validator. We make a clone of the module, apply the transformation to the
|
return fuzzerutil::NewTerminatorPreservesDominationRules(
|
||||||
// clone, and check whether the transformed clone is valid.
|
ir_context, message_.from_block(),
|
||||||
//
|
fuzzerutil::CreateUnreachableEdgeInstruction(
|
||||||
// In principle some of the above checks could be removed, with more reliance
|
ir_context, message_.from_block(), message_.to_block(), bool_id));
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TransformationAddDeadBreak::Apply(
|
void TransformationAddDeadBreak::Apply(
|
||||||
opt::IRContext* ir_context,
|
opt::IRContext* ir_context,
|
||||||
TransformationContext* transformation_context) const {
|
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
|
// Invalidate all analyses
|
||||||
ir_context->InvalidateAnalysesExceptFor(
|
ir_context->InvalidateAnalysesExceptFor(
|
||||||
opt::IRContext::Analysis::kAnalysisNone);
|
opt::IRContext::Analysis::kAnalysisNone);
|
||||||
@ -201,17 +200,6 @@ protobufs::Transformation TransformationAddDeadBreak::ToMessage() const {
|
|||||||
return result;
|
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 {
|
std::unordered_set<uint32_t> TransformationAddDeadBreak::GetFreshIds() const {
|
||||||
return std::unordered_set<uint32_t>();
|
return std::unordered_set<uint32_t>();
|
||||||
}
|
}
|
||||||
|
@ -71,15 +71,6 @@ class TransformationAddDeadBreak : public Transformation {
|
|||||||
bool AddingBreakRespectsStructuredControlFlow(opt::IRContext* ir_context,
|
bool AddingBreakRespectsStructuredControlFlow(opt::IRContext* ir_context,
|
||||||
opt::BasicBlock* bb_from) const;
|
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_;
|
protobufs::TransformationAddDeadBreak message_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -38,9 +38,10 @@ bool TransformationAddDeadContinue::IsApplicable(
|
|||||||
const TransformationContext& transformation_context) const {
|
const TransformationContext& transformation_context) const {
|
||||||
// First, we check that a constant with the same value as
|
// First, we check that a constant with the same value as
|
||||||
// |message_.continue_condition_value| is present.
|
// |message_.continue_condition_value| is present.
|
||||||
if (!fuzzerutil::MaybeGetBoolConstant(ir_context, transformation_context,
|
const auto bool_id = fuzzerutil::MaybeGetBoolConstant(
|
||||||
message_.continue_condition_value(),
|
ir_context, transformation_context, message_.continue_condition_value(),
|
||||||
false)) {
|
false);
|
||||||
|
if (!bool_id) {
|
||||||
// The required constant is not present, so the transformation cannot be
|
// The required constant is not present, so the transformation cannot be
|
||||||
// applied.
|
// applied.
|
||||||
return false;
|
return false;
|
||||||
@ -111,25 +112,30 @@ bool TransformationAddDeadContinue::IsApplicable(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Adding the dead break is only valid if SPIR-V rules related to dominance
|
// 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
|
// hold.
|
||||||
// validator. We make a clone of the module, apply the transformation to the
|
return fuzzerutil::NewTerminatorPreservesDominationRules(
|
||||||
// clone, and check whether the transformed clone is valid.
|
ir_context, message_.from_block(),
|
||||||
//
|
fuzzerutil::CreateUnreachableEdgeInstruction(
|
||||||
// In principle some of the above checks could be removed, with more reliance
|
ir_context, message_.from_block(), continue_block, bool_id));
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TransformationAddDeadContinue::Apply(
|
void TransformationAddDeadContinue::Apply(
|
||||||
opt::IRContext* ir_context,
|
opt::IRContext* ir_context,
|
||||||
TransformationContext* transformation_context) const {
|
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
|
// Invalidate all analyses
|
||||||
ir_context->InvalidateAnalysesExceptFor(
|
ir_context->InvalidateAnalysesExceptFor(
|
||||||
opt::IRContext::Analysis::kAnalysisNone);
|
opt::IRContext::Analysis::kAnalysisNone);
|
||||||
@ -141,24 +147,6 @@ protobufs::Transformation TransformationAddDeadContinue::ToMessage() const {
|
|||||||
return result;
|
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()
|
std::unordered_set<uint32_t> TransformationAddDeadContinue::GetFreshIds()
|
||||||
const {
|
const {
|
||||||
return std::unordered_set<uint32_t>();
|
return std::unordered_set<uint32_t>();
|
||||||
|
@ -68,15 +68,6 @@ class TransformationAddDeadContinue : public Transformation {
|
|||||||
protobufs::Transformation ToMessage() const override;
|
protobufs::Transformation ToMessage() const override;
|
||||||
|
|
||||||
private:
|
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_;
|
protobufs::TransformationAddDeadContinue message_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -162,7 +162,10 @@ bool TransformationReplaceBranchFromDeadBlockWithExit::BlockIsSuitable(
|
|||||||
if (ir_context->cfg()->preds(successor->id()).size() < 2) {
|
if (ir_context->cfg()->preds(successor->id()).size() < 2) {
|
||||||
return false;
|
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
|
} // namespace fuzz
|
||||||
|
@ -41,13 +41,17 @@ class TransformationReplaceBranchFromDeadBlockWithExit : public Transformation {
|
|||||||
// predecessor
|
// predecessor
|
||||||
// - |message_.opcode()| must be one of OpKill, OpReturn, OpReturnValue and
|
// - |message_.opcode()| must be one of OpKill, OpReturn, OpReturnValue and
|
||||||
// OpUnreachable
|
// 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
|
// have Fragment execution mode
|
||||||
// - |message_.opcode()| can only be OpReturn if the return type of the
|
// - |message_.opcode()| can only be OpReturn if the return type of the
|
||||||
// function containing the block is void
|
// function containing the block is void
|
||||||
// - If |message_.opcode()| is OpReturnValue then |message_.return_value_id|
|
// - If |message_.opcode()| is OpReturnValue then |message_.return_value_id|
|
||||||
// must be an id that is available at the block terminator and that matches
|
// must be an id that is available at the block terminator and that matches
|
||||||
// the return type of the enclosing function
|
// 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(
|
bool IsApplicable(
|
||||||
opt::IRContext* ir_context,
|
opt::IRContext* ir_context,
|
||||||
const TransformationContext& transformation_context) const override;
|
const TransformationContext& transformation_context) const override;
|
||||||
|
@ -566,6 +566,302 @@ TEST(TransformationReplaceBranchFromDeadBlockWithExitTest, OpPhi) {
|
|||||||
ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
|
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
|
||||||
} // namespace fuzz
|
} // namespace fuzz
|
||||||
} // namespace spvtools
|
} // namespace spvtools
|
||||||
|
Loading…
Reference in New Issue
Block a user