spirv-fuzz: Transformation to convert OpSelect to conditional branch (#3681)

This transformation takes an OpSelect instruction and replaces it with
a conditional branch, selecting the correct value using an OpPhi
instruction.

Fixes part of the issue #3544.
This commit is contained in:
Stefano Milizia 2020-09-03 11:19:02 +02:00 committed by GitHub
parent 2c60d16a64
commit fd05605bef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 871 additions and 21 deletions

View File

@ -95,6 +95,7 @@ if(SPIRV_BUILD_FUZZER)
fuzzer_pass_replace_linear_algebra_instructions.h
fuzzer_pass_replace_loads_stores_with_copy_memories.h
fuzzer_pass_replace_opphi_ids_from_dead_predecessors.h
fuzzer_pass_replace_opselects_with_conditional_branches.h
fuzzer_pass_replace_parameter_with_global.h
fuzzer_pass_replace_params_with_struct.h
fuzzer_pass_split_blocks.h
@ -174,6 +175,7 @@ if(SPIRV_BUILD_FUZZER)
transformation_replace_linear_algebra_instruction.h
transformation_replace_load_store_with_copy_memory.h
transformation_replace_opphi_id_from_dead_predecessor.h
transformation_replace_opselect_with_conditional_branch.h
transformation_replace_parameter_with_global.h
transformation_replace_params_with_struct.h
transformation_set_function_control.h
@ -254,6 +256,7 @@ if(SPIRV_BUILD_FUZZER)
fuzzer_pass_replace_linear_algebra_instructions.cpp
fuzzer_pass_replace_loads_stores_with_copy_memories.cpp
fuzzer_pass_replace_opphi_ids_from_dead_predecessors.cpp
fuzzer_pass_replace_opselects_with_conditional_branches.cpp
fuzzer_pass_replace_parameter_with_global.cpp
fuzzer_pass_replace_params_with_struct.cpp
fuzzer_pass_split_blocks.cpp
@ -306,6 +309,7 @@ if(SPIRV_BUILD_FUZZER)
transformation_composite_insert.cpp
transformation_compute_data_synonym_fact_closure.cpp
transformation_context.cpp
transformation_replace_opselect_with_conditional_branch.cpp
transformation_equation_instruction.cpp
transformation_function_call.cpp
transformation_inline_function.cpp

View File

@ -71,6 +71,7 @@
#include "source/fuzz/fuzzer_pass_replace_linear_algebra_instructions.h"
#include "source/fuzz/fuzzer_pass_replace_loads_stores_with_copy_memories.h"
#include "source/fuzz/fuzzer_pass_replace_opphi_ids_from_dead_predecessors.h"
#include "source/fuzz/fuzzer_pass_replace_opselects_with_conditional_branches.h"
#include "source/fuzz/fuzzer_pass_replace_parameter_with_global.h"
#include "source/fuzz/fuzzer_pass_replace_params_with_struct.h"
#include "source/fuzz/fuzzer_pass_split_blocks.h"
@ -325,6 +326,9 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run(
MaybeAddPass<FuzzerPassReplaceLinearAlgebraInstructions>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassReplaceOpSelectsWithConditionalBranches>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassReplaceParamsWithStruct>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);

View File

@ -27,6 +27,8 @@ const std::pair<uint32_t, uint32_t> kChanceOfAddingAccessChain = {5, 50};
const std::pair<uint32_t, uint32_t> kChanceOfAddingAnotherStructField = {20,
90};
const std::pair<uint32_t, uint32_t> kChanceOfAddingArrayOrStructType = {20, 90};
const std::pair<uint32_t, uint32_t>
kChanceOfAddingBothBranchesWhenReplacingOpSelect = {40, 60};
const std::pair<uint32_t, uint32_t> kChanceOfAddingCompositeInsert = {20, 50};
const std::pair<uint32_t, uint32_t> kChanceOfAddingCopyMemory = {20, 50};
const std::pair<uint32_t, uint32_t> kChanceOfAddingDeadBlock = {20, 90};
@ -48,6 +50,8 @@ const std::pair<uint32_t, uint32_t> kChanceOfAddingParameters = {5, 70};
const std::pair<uint32_t, uint32_t> kChanceOfAddingRelaxedDecoration = {20, 90};
const std::pair<uint32_t, uint32_t> kChanceOfAddingStore = {5, 50};
const std::pair<uint32_t, uint32_t> kChanceOfAddingSynonyms = {20, 50};
const std::pair<uint32_t, uint32_t>
kChanceOfAddingTrueBranchWhenReplacingOpSelect = {40, 60};
const std::pair<uint32_t, uint32_t> kChanceOfAddingVectorType = {20, 70};
const std::pair<uint32_t, uint32_t> kChanceOfAddingVectorShuffle = {20, 70};
const std::pair<uint32_t, uint32_t> kChanceOfAdjustingBranchWeights = {20, 90};
@ -105,6 +109,8 @@ const std::pair<uint32_t, uint32_t> kChanceOfReplacingLoadStoreWithCopyMemory =
{20, 90};
const std::pair<uint32_t, uint32_t>
kChanceOfReplacingOpPhiIdFromDeadPredecessor = {20, 90};
const std::pair<uint32_t, uint32_t>
kChanceOfReplacingOpSelectWithConditionalBranch = {20, 90};
const std::pair<uint32_t, uint32_t> kChanceOfReplacingParametersWithGlobals = {
30, 70};
const std::pair<uint32_t, uint32_t> kChanceOfReplacingParametersWithStruct = {
@ -162,6 +168,8 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator,
ChooseBetweenMinAndMax(kChanceOfAddingAnotherStructField);
chance_of_adding_array_or_struct_type_ =
ChooseBetweenMinAndMax(kChanceOfAddingArrayOrStructType);
chance_of_adding_both_branches_when_replacing_opselect_ =
ChooseBetweenMinAndMax(kChanceOfAddingBothBranchesWhenReplacingOpSelect);
chance_of_adding_composite_insert_ =
ChooseBetweenMinAndMax(kChanceOfAddingCompositeInsert);
chance_of_adding_copy_memory_ =
@ -194,6 +202,8 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator,
chance_of_adding_relaxed_decoration_ =
ChooseBetweenMinAndMax(kChanceOfAddingRelaxedDecoration);
chance_of_adding_store_ = ChooseBetweenMinAndMax(kChanceOfAddingStore);
chance_of_adding_true_branch_when_replacing_opselect_ =
ChooseBetweenMinAndMax(kChanceOfAddingTrueBranchWhenReplacingOpSelect);
chance_of_adding_vector_shuffle_ =
ChooseBetweenMinAndMax(kChanceOfAddingVectorShuffle);
chance_of_adding_vector_type_ =
@ -271,6 +281,8 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator,
ChooseBetweenMinAndMax(kChanceOfReplacingLoadStoreWithCopyMemory);
chance_of_replacing_opphi_id_from_dead_predecessor_ =
ChooseBetweenMinAndMax(kChanceOfReplacingOpPhiIdFromDeadPredecessor);
chance_of_replacing_opselect_with_conditional_branch_ =
ChooseBetweenMinAndMax(kChanceOfReplacingOpSelectWithConditionalBranch);
chance_of_replacing_parameters_with_globals_ =
ChooseBetweenMinAndMax(kChanceOfReplacingParametersWithGlobals);
chance_of_replacing_parameters_with_struct_ =

View File

@ -115,6 +115,9 @@ class FuzzerContext {
uint32_t GetChanceOfAddingArrayOrStructType() {
return chance_of_adding_array_or_struct_type_;
}
uint32_t GetChanceOfAddingBothBranchesWhenReplacingOpSelect() {
return chance_of_adding_both_branches_when_replacing_opselect_;
}
uint32_t GetChanceOfAddingCompositeInsert() {
return chance_of_adding_composite_insert_;
}
@ -157,6 +160,9 @@ class FuzzerContext {
}
uint32_t GetChanceOfAddingStore() { return chance_of_adding_store_; }
uint32_t GetChanceOfAddingSynonyms() { return chance_of_adding_synonyms_; }
uint32_t GetChanceOfAddingTrueBranchWhenReplacingOpSelect() {
return chance_of_adding_true_branch_when_replacing_opselect_;
}
uint32_t GetChanceOfAddingVectorShuffle() {
return chance_of_adding_vector_shuffle_;
}
@ -264,6 +270,9 @@ class FuzzerContext {
uint32_t GetChanceOfReplacingOpPhiIdFromDeadPredecessor() {
return chance_of_replacing_opphi_id_from_dead_predecessor_;
}
uint32_t GetChanceOfReplacingOpselectWithConditionalBranch() {
return chance_of_replacing_opselect_with_conditional_branch_;
}
uint32_t GetChanceOfReplacingParametersWithGlobals() {
return chance_of_replacing_parameters_with_globals_;
}
@ -364,6 +373,7 @@ class FuzzerContext {
uint32_t chance_of_adding_access_chain_;
uint32_t chance_of_adding_another_struct_field_;
uint32_t chance_of_adding_array_or_struct_type_;
uint32_t chance_of_adding_both_branches_when_replacing_opselect_;
uint32_t chance_of_adding_composite_insert_;
uint32_t chance_of_adding_copy_memory_;
uint32_t chance_of_adding_dead_block_;
@ -382,6 +392,7 @@ class FuzzerContext {
uint32_t chance_of_adding_relaxed_decoration_;
uint32_t chance_of_adding_store_;
uint32_t chance_of_adding_synonyms_;
uint32_t chance_of_adding_true_branch_when_replacing_opselect_;
uint32_t chance_of_adding_vector_shuffle_;
uint32_t chance_of_adding_vector_type_;
uint32_t chance_of_adjusting_branch_weights_;
@ -421,6 +432,7 @@ class FuzzerContext {
uint32_t chance_of_replacing_linear_algebra_instructions_;
uint32_t chance_of_replacing_load_store_with_copy_memory_;
uint32_t chance_of_replacing_opphi_id_from_dead_predecessor_;
uint32_t chance_of_replacing_opselect_with_conditional_branch_;
uint32_t chance_of_replacing_parameters_with_globals_;
uint32_t chance_of_replacing_parameters_with_struct_;
uint32_t chance_of_splitting_block_;

View File

@ -0,0 +1,162 @@
// Copyright (c) 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "source/fuzz/fuzzer_pass_replace_opselects_with_conditional_branches.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/instruction_descriptor.h"
#include "source/fuzz/transformation_replace_opselect_with_conditional_branch.h"
#include "source/fuzz/transformation_split_block.h"
namespace spvtools {
namespace fuzz {
FuzzerPassReplaceOpSelectsWithConditionalBranches::
FuzzerPassReplaceOpSelectsWithConditionalBranches(
opt::IRContext* ir_context,
TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: FuzzerPass(ir_context, transformation_context, fuzzer_context,
transformations) {}
FuzzerPassReplaceOpSelectsWithConditionalBranches::
~FuzzerPassReplaceOpSelectsWithConditionalBranches() = default;
void FuzzerPassReplaceOpSelectsWithConditionalBranches::Apply() {
// Keep track of the instructions that we want to replace. We need to collect
// them in a vector, since it's not safe to modify the module while iterating
// over it.
std::vector<uint32_t> replaceable_opselect_instruction_ids;
// Loop over all the instructions in the module.
for (auto& function : *GetIRContext()->module()) {
for (auto& block : function) {
// We cannot split loop headers, so we don't need to consider instructions
// in loop headers that are also merge blocks (since they would need to be
// split).
if (block.IsLoopHeader() &&
GetIRContext()->GetStructuredCFGAnalysis()->IsMergeBlock(
block.id())) {
continue;
}
for (auto& instruction : block) {
// We only care about OpSelect instructions.
if (instruction.opcode() != SpvOpSelect) {
continue;
}
// Randomly choose whether to consider this instruction for replacement.
if (!GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()
->GetChanceOfReplacingOpselectWithConditionalBranch())) {
continue;
}
// If the block is a loop header and we need to split it, the
// transformation cannot be applied because loop headers cannot be
// split. We can break out of this loop because the transformation can
// only be applied to at most the first instruction in a loop header.
if (block.IsLoopHeader() && InstructionNeedsSplitBefore(&instruction)) {
break;
}
// If the instruction separates an OpSampledImage from its use, the
// block cannot be split around it and the instruction cannot be
// replaced.
if (fuzzerutil::
SplittingBeforeInstructionSeparatesOpSampledImageDefinitionFromUse(
&block, &instruction)) {
continue;
}
// We can apply the transformation to this instruction.
replaceable_opselect_instruction_ids.push_back(instruction.result_id());
}
}
}
// Apply the transformations, splitting the blocks containing the
// instructions, if necessary.
for (uint32_t instruction_id : replaceable_opselect_instruction_ids) {
auto instruction =
GetIRContext()->get_def_use_mgr()->GetDef(instruction_id);
// If the instruction requires the block containing it to be split before
// it, split the block.
if (InstructionNeedsSplitBefore(instruction)) {
ApplyTransformation(TransformationSplitBlock(
MakeInstructionDescriptor(GetIRContext(), instruction),
GetFuzzerContext()->GetFreshId()));
}
// Decide whether to have two branches or just one.
bool two_branches = GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()
->GetChanceOfAddingBothBranchesWhenReplacingOpSelect());
// If there will be only one branch, decide whether it will be the true
// branch or the false branch.
bool true_branch_id_zero =
!two_branches &&
GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()
->GetChanceOfAddingTrueBranchWhenReplacingOpSelect());
bool false_branch_id_zero = !two_branches && !true_branch_id_zero;
uint32_t true_branch_id =
true_branch_id_zero ? 0 : GetFuzzerContext()->GetFreshId();
uint32_t false_branch_id =
false_branch_id_zero ? 0 : GetFuzzerContext()->GetFreshId();
ApplyTransformation(TransformationReplaceOpSelectWithConditionalBranch(
instruction_id, true_branch_id, false_branch_id));
}
}
bool FuzzerPassReplaceOpSelectsWithConditionalBranches::
InstructionNeedsSplitBefore(opt::Instruction* instruction) {
assert(instruction && instruction->opcode() == SpvOpSelect &&
"The instruction must be OpSelect.");
auto block = GetIRContext()->get_instr_block(instruction);
assert(block && "The instruction must be contained in a block.");
// We need to split the block if the instruction is not the first in its
// block.
if (instruction->unique_id() != block->begin()->unique_id()) {
return true;
}
// We need to split the block if it is a merge block.
if (GetIRContext()->GetStructuredCFGAnalysis()->IsMergeBlock(block->id())) {
return true;
}
// We need to split the block if it has more than one predecessor.
if (GetIRContext()->cfg()->preds(block->id()).size() != 1) {
return true;
}
// We need to split the block if its predecessor is a header or it does not
// branch unconditionally to the block.
auto predecessor = GetIRContext()->get_instr_block(
GetIRContext()->cfg()->preds(block->id())[0]);
return predecessor->MergeBlockIdIfAny() ||
predecessor->terminator()->opcode() != SpvOpBranch;
}
} // namespace fuzz
} // namespace spvtools

View File

@ -0,0 +1,53 @@
// Copyright (c) 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef SOURCE_FUZZ_FUZZER_PASS_REPLACE_OPSELECTS_WITH_CONDITIONAL_BRANCHES_
#define SOURCE_FUZZ_FUZZER_PASS_REPLACE_OPSELECTS_WITH_CONDITIONAL_BRANCHES_
#include "source/fuzz/fuzzer_pass.h"
namespace spvtools {
namespace fuzz {
// A fuzzer pass to replace OpSelect instructions (where the condition is a
// scalar boolean) with conditional branches and OpPhi instructions.
class FuzzerPassReplaceOpSelectsWithConditionalBranches : public FuzzerPass {
public:
FuzzerPassReplaceOpSelectsWithConditionalBranches(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations);
~FuzzerPassReplaceOpSelectsWithConditionalBranches() override;
void Apply() override;
private:
// Returns true if any of the following holds:
// - the instruction is not the first in its block
// - the block containing it is a merge block
// - the block does not have a unique predecessor
// - the predecessor of the block is the header of a construct
// - the predecessor does not branch unconditionally to the block
// If this function returns true, the block must be split before the
// instruction for TransformationReplaceOpSelectWithConditionalBranch to be
// applicable.
// Assumes that the instruction is OpSelect.
bool InstructionNeedsSplitBefore(opt::Instruction* instruction);
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FUZZER_PASS_REPLACE_OPSELECTS_WITH_CONDITIONAL_BRANCHES_

View File

@ -1486,6 +1486,39 @@ bool MembersHaveBuiltInDecoration(opt::IRContext* ir_context,
return builtin_count != 0;
}
bool SplittingBeforeInstructionSeparatesOpSampledImageDefinitionFromUse(
opt::BasicBlock* block_to_split, opt::Instruction* split_before) {
std::set<uint32_t> sampled_image_result_ids;
bool before_split = true;
// Check all the instructions in the block to split.
for (auto& instruction : *block_to_split) {
if (&instruction == &*split_before) {
before_split = false;
}
if (before_split) {
// If the instruction comes before the split and its opcode is
// OpSampledImage, record its result id.
if (instruction.opcode() == SpvOpSampledImage) {
sampled_image_result_ids.insert(instruction.result_id());
}
} else {
// If the instruction comes after the split, check if ids
// corresponding to OpSampledImage instructions defined before the split
// are used, and return true if they are.
if (!instruction.WhileEachInId(
[&sampled_image_result_ids](uint32_t* id) -> bool {
return !sampled_image_result_ids.count(*id);
})) {
return true;
}
}
}
// No usage that would be separated from the definition has been found.
return false;
}
} // namespace fuzzerutil
} // namespace fuzz
} // namespace spvtools

View File

@ -524,6 +524,11 @@ bool IdUseCanBeReplaced(opt::IRContext* ir_context,
bool MembersHaveBuiltInDecoration(opt::IRContext* ir_context,
uint32_t struct_type_id);
// Returns true iff splitting block |block_to_split| just before the instruction
// |split_before| would separate an OpSampledImage instruction from its usage.
bool SplittingBeforeInstructionSeparatesOpSampledImageDefinitionFromUse(
opt::BasicBlock* block_to_split, opt::Instruction* split_before);
} // namespace fuzzerutil
} // namespace fuzz
} // namespace spvtools

View File

@ -418,6 +418,7 @@ message Transformation {
TransformationMutatePointer mutate_pointer = 71;
TransformationReplaceIrrelevantId replace_irrelevant_id = 72;
TransformationReplaceOpPhiIdFromDeadPredecessor replace_opphi_id_from_dead_predecessor = 73;
TransformationReplaceOpSelectWithConditionalBranch replace_opselect_with_conditional_branch = 74;
// Add additional option using the next available number.
}
}
@ -1564,6 +1565,39 @@ message TransformationReplaceOpPhiIdFromDeadPredecessor {
}
message TransformationReplaceOpSelectWithConditionalBranch {
// A transformation that takes an OpSelect instruction with a
// scalar boolean condition and replaces it with a conditional
// branch and an OpPhi instruction.
// The OpSelect instruction must be the first instruction in its
// block, which must have a unique predecessor. The block will
// become the merge block of a new construct, while its predecessor
// will become the header.
// Given the original OpSelect instruction:
// %id = OpSelect %type %cond %then %else
// The branching instruction of the header will be:
// OpBranchConditional %cond %true_block_id %false_block_id
// and the OpSelect instruction will be turned into:
// %id = OpPhi %type %then %true_block_id %else %false_block_id
// At most one of |true_block_id| and |false_block_id| can be zero. In
// that case, there will be no such block and all references to it
// will be replaced by %merge_block (where %merge_block is the
// block containing the OpSelect instruction).
// The result id of the OpSelect instruction.
uint32 select_id = 1;
// A fresh id for the new block that the predecessor of the block
// containing |select_id| will branch to if the condition holds.
uint32 true_block_id = 2;
// A fresh id for the new block that the predecessor of the block
// containing |select_id| will branch to if the condition does not
// hold.
uint32 false_block_id = 3;
}
message TransformationReplaceParamsWithStruct {
// Replaces parameters of the function with a struct containing

View File

@ -78,6 +78,7 @@
#include "source/fuzz/transformation_replace_linear_algebra_instruction.h"
#include "source/fuzz/transformation_replace_load_store_with_copy_memory.h"
#include "source/fuzz/transformation_replace_opphi_id_from_dead_predecessor.h"
#include "source/fuzz/transformation_replace_opselect_with_conditional_branch.h"
#include "source/fuzz/transformation_replace_parameter_with_global.h"
#include "source/fuzz/transformation_replace_params_with_struct.h"
#include "source/fuzz/transformation_set_function_control.h"
@ -276,6 +277,10 @@ std::unique_ptr<Transformation> Transformation::FromMessage(
kReplaceLoadStoreWithCopyMemory:
return MakeUnique<TransformationReplaceLoadStoreWithCopyMemory>(
message.replace_load_store_with_copy_memory());
case protobufs::Transformation::TransformationCase::
kReplaceOpselectWithConditionalBranch:
return MakeUnique<TransformationReplaceOpSelectWithConditionalBranch>(
message.replace_opselect_with_conditional_branch());
case protobufs::Transformation::TransformationCase::
kReplaceParameterWithGlobal:
return MakeUnique<TransformationReplaceParameterWithGlobal>(

View File

@ -0,0 +1,204 @@
// Copyright (c) 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "source/fuzz/transformation_replace_opselect_with_conditional_branch.h"
#include "source/fuzz/fuzzer_util.h"
namespace spvtools {
namespace fuzz {
TransformationReplaceOpSelectWithConditionalBranch::
TransformationReplaceOpSelectWithConditionalBranch(
const spvtools::fuzz::protobufs::
TransformationReplaceOpSelectWithConditionalBranch& message)
: message_(message) {}
TransformationReplaceOpSelectWithConditionalBranch::
TransformationReplaceOpSelectWithConditionalBranch(
uint32_t select_id, uint32_t true_block_id, uint32_t false_block_id) {
message_.set_select_id(select_id);
message_.set_true_block_id(true_block_id);
message_.set_false_block_id(false_block_id);
}
bool TransformationReplaceOpSelectWithConditionalBranch::IsApplicable(
opt::IRContext* ir_context,
const TransformationContext& /* unused */) const {
assert((message_.true_block_id() || message_.false_block_id()) &&
"At least one of the ids must be non-zero.");
// Check that the non-zero ids are fresh.
std::set<uint32_t> used_ids;
for (uint32_t id : {message_.true_block_id(), message_.false_block_id()}) {
if (id && !CheckIdIsFreshAndNotUsedByThisTransformation(id, ir_context,
&used_ids)) {
return false;
}
}
auto instruction =
ir_context->get_def_use_mgr()->GetDef(message_.select_id());
// The instruction must exist and it must be an OpSelect instruction.
if (!instruction || instruction->opcode() != SpvOpSelect) {
return false;
}
// Check that the condition is a scalar boolean.
auto condition = ir_context->get_def_use_mgr()->GetDef(
instruction->GetSingleWordInOperand(0));
assert(condition && "The condition should always exist in a valid module.");
auto condition_type =
ir_context->get_type_mgr()->GetType(condition->type_id());
if (!condition_type->AsBool()) {
return false;
}
auto block = ir_context->get_instr_block(instruction);
assert(block && "The block containing the instruction must be found");
// The instruction must be the first in its block.
if (instruction->unique_id() != block->begin()->unique_id()) {
return false;
}
// The block must not be a merge block.
if (ir_context->GetStructuredCFGAnalysis()->IsMergeBlock(block->id())) {
return false;
}
// The block must have exactly one predecessor.
auto predecessors = ir_context->cfg()->preds(block->id());
if (predecessors.size() != 1) {
return false;
}
uint32_t pred_id = predecessors[0];
auto predecessor = ir_context->get_instr_block(pred_id);
// The predecessor must not be the header of a construct and it must end with
// OpBranch.
if (predecessor->GetMergeInst() != nullptr ||
predecessor->terminator()->opcode() != SpvOpBranch) {
return false;
}
return true;
}
void TransformationReplaceOpSelectWithConditionalBranch::Apply(
opt::IRContext* ir_context, TransformationContext* /* unused */) const {
auto instruction =
ir_context->get_def_use_mgr()->GetDef(message_.select_id());
auto block = ir_context->get_instr_block(instruction);
auto predecessor =
ir_context->get_instr_block(ir_context->cfg()->preds(block->id())[0]);
// Create a new block for each non-zero id in {|message_.true_branch_id|,
// |message_.false_branch_id|}. Make each newly-created block branch
// unconditionally to the instruction block.
for (uint32_t id : {message_.true_block_id(), message_.false_block_id()}) {
if (id) {
fuzzerutil::UpdateModuleIdBound(ir_context, id);
// Create the new block.
auto new_block = MakeUnique<opt::BasicBlock>(MakeUnique<opt::Instruction>(
ir_context, SpvOpLabel, 0, id, opt::Instruction::OperandList{}));
// Add an unconditional branch from the new block to the instruction
// block.
new_block->AddInstruction(MakeUnique<opt::Instruction>(
ir_context, SpvOpBranch, 0, 0,
opt::Instruction::OperandList{{SPV_OPERAND_TYPE_ID, {block->id()}}}));
// Insert the new block right after the predecessor of the instruction
// block.
block->GetParent()->InsertBasicBlockBefore(std::move(new_block), block);
}
}
// Delete the OpBranch instruction from the predecessor.
ir_context->KillInst(predecessor->terminator());
// Add an OpSelectionMerge instruction to the predecessor block, where the
// merge block is the instruction block.
predecessor->AddInstruction(MakeUnique<opt::Instruction>(
ir_context, SpvOpSelectionMerge, 0, 0,
opt::Instruction::OperandList{{SPV_OPERAND_TYPE_ID, {block->id()}},
{SPV_OPERAND_TYPE_SELECTION_CONTROL,
{SpvSelectionControlMaskNone}}}));
// |if_block| will be the true block, if it has been created, the instruction
// block otherwise.
uint32_t if_block =
message_.true_block_id() ? message_.true_block_id() : block->id();
// |else_block| will be the false block, if it has been created, the
// instruction block otherwise.
uint32_t else_block =
message_.false_block_id() ? message_.false_block_id() : block->id();
assert(if_block != else_block &&
"|if_block| and |else_block| should always be different, if the "
"transformation is applicable.");
// Add a conditional branching instruction to the predecessor, branching to
// |if_block| if the condition is true and to |if_false| otherwise.
predecessor->AddInstruction(MakeUnique<opt::Instruction>(
ir_context, SpvOpBranchConditional, 0, 0,
opt::Instruction::OperandList{
{SPV_OPERAND_TYPE_ID, {instruction->GetSingleWordInOperand(0)}},
{SPV_OPERAND_TYPE_ID, {if_block}},
{SPV_OPERAND_TYPE_ID, {else_block}}}));
// |if_pred| will be the true block, if it has been created, the existing
// predecessor otherwise.
uint32_t if_pred =
message_.true_block_id() ? message_.true_block_id() : predecessor->id();
// |else_pred| will be the false block, if it has been created, the existing
// predecessor otherwise.
uint32_t else_pred =
message_.false_block_id() ? message_.false_block_id() : predecessor->id();
// Replace the OpSelect instruction in the merge block with an OpPhi.
// This: OpSelect %type %cond %if %else
// will become: OpPhi %type %if %if_pred %else %else_pred
instruction->SetOpcode(SpvOpPhi);
std::vector<opt::Operand> operands;
operands.emplace_back(instruction->GetInOperand(1));
operands.emplace_back(opt::Operand{SPV_OPERAND_TYPE_ID, {if_pred}});
operands.emplace_back(instruction->GetInOperand(2));
operands.emplace_back(opt::Operand{SPV_OPERAND_TYPE_ID, {else_pred}});
instruction->SetInOperands(std::move(operands));
// Invalidate all analyses, since the structure of the module was changed.
ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
}
protobufs::Transformation
TransformationReplaceOpSelectWithConditionalBranch::ToMessage() const {
protobufs::Transformation result;
*result.mutable_replace_opselect_with_conditional_branch() = message_;
return result;
}
} // namespace fuzz
} // namespace spvtools

View File

@ -0,0 +1,61 @@
// Copyright (c) 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef SOURCE_FUZZ_TRANSFORMATION_REPLACE_OPSELECT_WITH_CONDITIONAL_BRANCH_H
#define SOURCE_FUZZ_TRANSFORMATION_REPLACE_OPSELECT_WITH_CONDITIONAL_BRANCH_H
#include "source/fuzz/transformation.h"
namespace spvtools {
namespace fuzz {
class TransformationReplaceOpSelectWithConditionalBranch
: public Transformation {
public:
explicit TransformationReplaceOpSelectWithConditionalBranch(
const protobufs::TransformationReplaceOpSelectWithConditionalBranch&
message);
TransformationReplaceOpSelectWithConditionalBranch(uint32_t select_id,
uint32_t true_block_id,
uint32_t false_block_id);
// - |message_.select_id| is the result id of an OpSelect instruction.
// - The condition of the OpSelect must be a scalar boolean.
// - The OpSelect instruction is the first instruction in its block.
// - The block containing the instruction is not a merge block, and it has a
// single predecessor, which is not a header and whose last instruction is
// OpBranch.
// - Each of |message_.true_block_id| and |message_.false_block_id| is either
// 0 or a valid fresh id, and at most one of them is 0. They must be
// distinct.
bool IsApplicable(
opt::IRContext* ir_context,
const TransformationContext& transformation_context) const override;
// Replaces the OpSelect instruction with id |message_.select_id| with a
// conditional branch and an OpPhi instruction.
void Apply(opt::IRContext* ir_context,
TransformationContext* transformation_context) const override;
protobufs::Transformation ToMessage() const override;
private:
protobufs::TransformationReplaceOpSelectWithConditionalBranch message_;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_TRANSFORMATION_REPLACE_OPSELECT_WITH_CONDITIONAL_BRANCH_H

View File

@ -83,27 +83,9 @@ bool TransformationSplitBlock::IsApplicable(
// Splitting the block must not separate the definition of an OpSampledImage
// from its use: the SPIR-V data rules require them to be in the same block.
std::set<uint32_t> sampled_image_result_ids;
bool before_split = true;
for (auto& instruction : *block_to_split) {
if (&instruction == &*split_before) {
before_split = false;
}
if (before_split) {
if (instruction.opcode() == SpvOpSampledImage) {
sampled_image_result_ids.insert(instruction.result_id());
}
} else {
if (!instruction.WhileEachInId(
[&sampled_image_result_ids](uint32_t* id) -> bool {
return !sampled_image_result_ids.count(*id);
})) {
return false;
}
}
}
return true;
return !fuzzerutil::
SplittingBeforeInstructionSeparatesOpSampledImageDefinitionFromUse(
block_to_split, instruction_to_split_before);
}
void TransformationSplitBlock::Apply(

View File

@ -87,6 +87,7 @@ if (${SPIRV_BUILD_FUZZER})
transformation_replace_linear_algebra_instruction_test.cpp
transformation_replace_load_store_with_copy_memory_test.cpp
transformation_replace_opphi_id_from_dead_predecessor_test.cpp
transformation_replace_opselect_with_conditional_branch_test.cpp
transformation_replace_parameter_with_global_test.cpp
transformation_replace_params_with_struct_test.cpp
transformation_set_function_control_test.cpp

View File

@ -0,0 +1,278 @@
// Copyright (c) 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "source/fuzz/transformation_replace_opselect_with_conditional_branch.h"
#include "test/fuzz/fuzz_test_util.h"
#include "source/fuzz/fuzzer_pass_replace_opselects_with_conditional_branches.h"
#include "source/fuzz/pseudo_random_generator.h"
namespace spvtools {
namespace fuzz {
namespace {
TEST(TransformationReplaceOpSelectWithConditionalBranchTest, Inapplicable) {
std::string shader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %2 "main"
OpExecutionMode %2 OriginUpperLeft
OpSource ESSL 310
%3 = OpTypeVoid
%4 = OpTypeFunction %3
%5 = OpTypeInt 32 1
%6 = OpConstant %5 1
%7 = OpConstant %5 2
%8 = OpTypeVector %5 4
%9 = OpConstantNull %8
%10 = OpConstantComposite %8 %6 %6 %7 %7
%11 = OpTypeBool
%12 = OpTypeVector %11 4
%13 = OpConstantTrue %11
%14 = OpConstantFalse %11
%15 = OpConstantComposite %12 %13 %14 %14 %13
%2 = OpFunction %3 None %4
%16 = OpLabel
%17 = OpCopyObject %5 %6
%18 = OpCopyObject %5 %7
OpBranch %19
%19 = OpLabel
%20 = OpCopyObject %5 %17
%21 = OpSelect %5 %13 %17 %18
OpBranch %22
%22 = OpLabel
%23 = OpSelect %8 %15 %9 %10
OpBranch %24
%24 = OpLabel
OpSelectionMerge %25 None
OpBranchConditional %13 %26 %27
%26 = OpLabel
%28 = OpSelect %5 %13 %17 %18
OpBranch %27
%27 = OpLabel
%29 = OpSelect %5 %13 %17 %18
OpBranch %25
%25 = OpLabel
%30 = OpSelect %5 %13 %17 %18
OpBranch %31
%31 = OpLabel
OpLoopMerge %32 %33 None
OpBranch %33
%33 = OpLabel
%34 = OpSelect %5 %13 %17 %18
OpBranchConditional %13 %31 %32
%32 = OpLabel
%35 = OpSelect %5 %13 %17 %18
OpBranch %36
%36 = OpLabel
%37 = OpSelect %5 %13 %17 %18
OpReturn
OpFunctionEnd
)";
const auto env = SPV_ENV_UNIVERSAL_1_5;
const auto consumer = nullptr;
const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
ASSERT_TRUE(IsValid(env, context.get()));
FactManager fact_manager;
spvtools::ValidatorOptions validator_options;
TransformationContext transformation_context(&fact_manager,
validator_options);
// %20 is not an OpSelect instruction.
ASSERT_FALSE(TransformationReplaceOpSelectWithConditionalBranch(20, 100, 101)
.IsApplicable(context.get(), transformation_context));
// %21 is not the first instruction in its block.
ASSERT_FALSE(TransformationReplaceOpSelectWithConditionalBranch(21, 100, 101)
.IsApplicable(context.get(), transformation_context));
// The condition for %23 is not a scalar, but a vector of booleans.
ASSERT_FALSE(TransformationReplaceOpSelectWithConditionalBranch(23, 100, 101)
.IsApplicable(context.get(), transformation_context));
// The predecessor (%24) of the block containing %28 is the header of a
// selection construct and does not branch unconditionally.
ASSERT_FALSE(TransformationReplaceOpSelectWithConditionalBranch(24, 100, 101)
.IsApplicable(context.get(), transformation_context));
// The block containing %29 has two predecessors (%24 and %26).
ASSERT_FALSE(TransformationReplaceOpSelectWithConditionalBranch(29, 100, 101)
.IsApplicable(context.get(), transformation_context));
// The block containing %30 is the merge block for a selection construct.
ASSERT_FALSE(TransformationReplaceOpSelectWithConditionalBranch(30, 100, 101)
.IsApplicable(context.get(), transformation_context));
// The predecessor (%31) of the block containing %34 is a loop header.
ASSERT_FALSE(TransformationReplaceOpSelectWithConditionalBranch(31, 100, 101)
.IsApplicable(context.get(), transformation_context));
// The block containing %35 is the merge block for a loop construct.
ASSERT_FALSE(TransformationReplaceOpSelectWithConditionalBranch(35, 100, 101)
.IsApplicable(context.get(), transformation_context));
#ifndef NDEBUG
// |true_block_id| and |false_block_id| are both 0.
ASSERT_DEATH(
TransformationReplaceOpSelectWithConditionalBranch(37, 0, 0).IsApplicable(
context.get(), transformation_context),
"At least one of the ids must be non-zero.");
#endif
// The fresh ids are not distinct.
ASSERT_FALSE(TransformationReplaceOpSelectWithConditionalBranch(37, 100, 100)
.IsApplicable(context.get(), transformation_context));
// One of the ids is not fresh.
ASSERT_FALSE(TransformationReplaceOpSelectWithConditionalBranch(37, 100, 10)
.IsApplicable(context.get(), transformation_context));
}
TEST(TransformationReplaceOpSelectWithConditionalBranchTest, Simple) {
std::string shader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %2 "main"
OpExecutionMode %2 OriginUpperLeft
OpSource ESSL 310
OpName %2 "main"
%3 = OpTypeVoid
%4 = OpTypeFunction %3
%5 = OpTypeInt 32 1
%6 = OpConstant %5 1
%7 = OpConstant %5 2
%8 = OpTypeVector %5 4
%9 = OpConstantNull %8
%10 = OpConstantComposite %8 %6 %6 %7 %7
%11 = OpTypeBool
%12 = OpTypeVector %11 4
%13 = OpConstantTrue %11
%14 = OpConstantFalse %11
%15 = OpConstantComposite %12 %13 %14 %14 %13
%2 = OpFunction %3 None %4
%16 = OpLabel
%17 = OpCopyObject %5 %6
%18 = OpCopyObject %5 %7
OpBranch %19
%19 = OpLabel
%20 = OpSelect %5 %13 %17 %18
OpSelectionMerge %21 None
OpBranchConditional %13 %22 %21
%22 = OpLabel
OpBranch %23
%23 = OpLabel
%24 = OpSelect %8 %13 %9 %10
OpBranch %21
%21 = OpLabel
OpBranch %25
%25 = OpLabel
%26 = OpSelect %5 %13 %17 %18
OpReturn
OpFunctionEnd
)";
const auto env = SPV_ENV_UNIVERSAL_1_5;
const auto consumer = nullptr;
const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
ASSERT_TRUE(IsValid(env, context.get()));
FactManager fact_manager;
spvtools::ValidatorOptions validator_options;
TransformationContext transformation_context(&fact_manager,
validator_options);
auto transformation =
TransformationReplaceOpSelectWithConditionalBranch(20, 100, 101);
ASSERT_TRUE(
transformation.IsApplicable(context.get(), transformation_context));
transformation.Apply(context.get(), &transformation_context);
auto transformation2 =
TransformationReplaceOpSelectWithConditionalBranch(24, 0, 102);
ASSERT_TRUE(
transformation2.IsApplicable(context.get(), transformation_context));
transformation2.Apply(context.get(), &transformation_context);
auto transformation3 =
TransformationReplaceOpSelectWithConditionalBranch(26, 103, 0);
ASSERT_TRUE(
transformation3.IsApplicable(context.get(), transformation_context));
transformation3.Apply(context.get(), &transformation_context);
ASSERT_TRUE(IsValid(env, context.get()));
std::string after_transformation = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %2 "main"
OpExecutionMode %2 OriginUpperLeft
OpSource ESSL 310
OpName %2 "main"
%3 = OpTypeVoid
%4 = OpTypeFunction %3
%5 = OpTypeInt 32 1
%6 = OpConstant %5 1
%7 = OpConstant %5 2
%8 = OpTypeVector %5 4
%9 = OpConstantNull %8
%10 = OpConstantComposite %8 %6 %6 %7 %7
%11 = OpTypeBool
%12 = OpTypeVector %11 4
%13 = OpConstantTrue %11
%14 = OpConstantFalse %11
%15 = OpConstantComposite %12 %13 %14 %14 %13
%2 = OpFunction %3 None %4
%16 = OpLabel
%17 = OpCopyObject %5 %6
%18 = OpCopyObject %5 %7
OpSelectionMerge %19 None
OpBranchConditional %13 %100 %101
%100 = OpLabel
OpBranch %19
%101 = OpLabel
OpBranch %19
%19 = OpLabel
%20 = OpPhi %5 %17 %100 %18 %101
OpSelectionMerge %21 None
OpBranchConditional %13 %22 %21
%22 = OpLabel
OpSelectionMerge %23 None
OpBranchConditional %13 %23 %102
%102 = OpLabel
OpBranch %23
%23 = OpLabel
%24 = OpPhi %8 %9 %22 %10 %102
OpBranch %21
%21 = OpLabel
OpSelectionMerge %25 None
OpBranchConditional %13 %103 %25
%103 = OpLabel
OpBranch %25
%25 = OpLabel
%26 = OpPhi %5 %17 %103 %18 %21
OpReturn
OpFunctionEnd
)";
ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
}
} // namespace
} // namespace fuzz
} // namespace spvtools