spirv-fuzz: TransformationWrapRegionInSelection (#3674)

Fixes #3675.
This commit is contained in:
Vasyl Teliman 2020-10-01 11:54:10 +03:00 committed by GitHub
parent f2b8a4ee51
commit 615fbe6cbc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 775 additions and 1 deletions

View File

@ -114,6 +114,7 @@ if(SPIRV_BUILD_FUZZER)
fuzzer_pass_swap_commutable_operands.h
fuzzer_pass_swap_conditional_branch_operands.h
fuzzer_pass_toggle_access_chain_instruction.h
fuzzer_pass_wrap_regions_in_selections.h
fuzzer_util.h
id_use_descriptor.h
instruction_descriptor.h
@ -211,6 +212,7 @@ if(SPIRV_BUILD_FUZZER)
transformation_swap_conditional_branch_operands.h
transformation_toggle_access_chain_instruction.h
transformation_vector_shuffle.h
transformation_wrap_region_in_selection.h
uniform_buffer_element_descriptor.h
${CMAKE_CURRENT_BINARY_DIR}/protobufs/spvtoolsfuzz.pb.h
@ -290,6 +292,7 @@ if(SPIRV_BUILD_FUZZER)
fuzzer_pass_swap_commutable_operands.cpp
fuzzer_pass_swap_conditional_branch_operands.cpp
fuzzer_pass_toggle_access_chain_instruction.cpp
fuzzer_pass_wrap_regions_in_selections.cpp
fuzzer_util.cpp
id_use_descriptor.cpp
instruction_descriptor.cpp
@ -385,6 +388,7 @@ if(SPIRV_BUILD_FUZZER)
transformation_swap_conditional_branch_operands.cpp
transformation_toggle_access_chain_instruction.cpp
transformation_vector_shuffle.cpp
transformation_wrap_region_in_selection.cpp
uniform_buffer_element_descriptor.cpp
${CMAKE_CURRENT_BINARY_DIR}/protobufs/spvtoolsfuzz.pb.cc
)

View File

@ -83,6 +83,7 @@
#include "source/fuzz/fuzzer_pass_swap_commutable_operands.h"
#include "source/fuzz/fuzzer_pass_swap_conditional_branch_operands.h"
#include "source/fuzz/fuzzer_pass_toggle_access_chain_instruction.h"
#include "source/fuzz/fuzzer_pass_wrap_regions_in_selections.h"
#include "source/fuzz/pass_management/repeated_pass_manager.h"
#include "source/fuzz/pass_management/repeated_pass_manager_looped_with_recommendations.h"
#include "source/fuzz/pass_management/repeated_pass_manager_random_with_recommendations.h"
@ -294,6 +295,7 @@ Fuzzer::FuzzerResult Fuzzer::Run() {
MaybeAddRepeatedPass<FuzzerPassSplitBlocks>(&pass_instances);
MaybeAddRepeatedPass<FuzzerPassSwapBranchConditionalOperands>(
&pass_instances);
MaybeAddRepeatedPass<FuzzerPassWrapRegionsInSelections>(&pass_instances);
// There is a theoretical possibility that no pass instances were created
// until now; loop again if so.
} while (pass_instances.GetPasses().empty());

View File

@ -134,6 +134,8 @@ const std::pair<uint32_t, uint32_t> kChanceOfSwappingConditionalBranchOperands =
{10, 70};
const std::pair<uint32_t, uint32_t> kChanceOfTogglingAccessChainInstruction = {
20, 90};
const std::pair<uint32_t, uint32_t> kChanceOfWrappingRegionInSelection = {70,
90};
// Default limits for various quantities that are chosen during fuzzing.
// Keep them in alphabetical order.
@ -320,6 +322,8 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator,
ChooseBetweenMinAndMax(kChanceOfSwappingConditionalBranchOperands);
chance_of_toggling_access_chain_instruction_ =
ChooseBetweenMinAndMax(kChanceOfTogglingAccessChainInstruction);
chance_of_wrapping_region_in_selection_ =
ChooseBetweenMinAndMax(kChanceOfWrappingRegionInSelection);
}
FuzzerContext::~FuzzerContext() = default;

View File

@ -307,6 +307,9 @@ class FuzzerContext {
uint32_t GetChanceOfTogglingAccessChainInstruction() {
return chance_of_toggling_access_chain_instruction_;
}
uint32_t GetChanceOfWrappingRegionInSelection() {
return chance_of_wrapping_region_in_selection_;
}
// Other functions to control transformations. Keep them in alphabetical
// order.
@ -472,6 +475,7 @@ class FuzzerContext {
uint32_t chance_of_splitting_block_;
uint32_t chance_of_swapping_conditional_branch_operands_;
uint32_t chance_of_toggling_access_chain_instruction_;
uint32_t chance_of_wrapping_region_in_selection_;
// Limits associated with various quantities for which random values are
// chosen during fuzzing.

View File

@ -0,0 +1,140 @@
// Copyright (c) 2020 Vasyl Teliman
//
// 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_wrap_regions_in_selections.h"
#include "source/fuzz/fuzzer_context.h"
#include "source/fuzz/instruction_descriptor.h"
#include "source/fuzz/transformation_split_block.h"
#include "source/fuzz/transformation_wrap_region_in_selection.h"
namespace spvtools {
namespace fuzz {
FuzzerPassWrapRegionsInSelections::FuzzerPassWrapRegionsInSelections(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: FuzzerPass(ir_context, transformation_context, fuzzer_context,
transformations) {}
FuzzerPassWrapRegionsInSelections::~FuzzerPassWrapRegionsInSelections() =
default;
void FuzzerPassWrapRegionsInSelections::Apply() {
for (auto& function : *GetIRContext()->module()) {
if (!GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfWrappingRegionInSelection())) {
continue;
}
// It is easier to select an element at random from a vector than from an
// instruction list.
std::vector<opt::BasicBlock*> header_block_candidates;
for (auto& block : function) {
header_block_candidates.push_back(&block);
}
if (header_block_candidates.empty()) {
continue;
}
// Try to get a header block candidate that will increase the chances of the
// transformation being applicable.
auto* header_block_candidate = MaybeGetHeaderBlockCandidate(
header_block_candidates[GetFuzzerContext()->RandomIndex(
header_block_candidates)]);
if (!header_block_candidate) {
continue;
}
std::vector<opt::BasicBlock*> merge_block_candidates;
for (auto& block : function) {
if (GetIRContext()->GetDominatorAnalysis(&function)->StrictlyDominates(
header_block_candidate, &block) &&
GetIRContext()
->GetPostDominatorAnalysis(&function)
->StrictlyDominates(&block, header_block_candidate)) {
merge_block_candidates.push_back(&block);
}
}
if (merge_block_candidates.empty()) {
continue;
}
// Try to get a merge block candidate that will increase the chances of the
// transformation being applicable.
auto* merge_block_candidate = MaybeGetMergeBlockCandidate(
merge_block_candidates[GetFuzzerContext()->RandomIndex(
merge_block_candidates)]);
if (!merge_block_candidate) {
continue;
}
if (!TransformationWrapRegionInSelection::IsApplicableToBlockRange(
GetIRContext(), header_block_candidate->id(),
merge_block_candidate->id())) {
continue;
}
// This boolean constant will be used as a condition for the
// OpBranchConditional instruction. We mark it as irrelevant to be able to
// replace it with a more interesting value later.
auto branch_condition = GetFuzzerContext()->ChooseEven();
FindOrCreateBoolConstant(branch_condition, true);
ApplyTransformation(TransformationWrapRegionInSelection(
header_block_candidate->id(), merge_block_candidate->id(),
branch_condition));
}
}
opt::BasicBlock*
FuzzerPassWrapRegionsInSelections::MaybeGetHeaderBlockCandidate(
opt::BasicBlock* header_block_candidate) {
// Try to create a preheader if |header_block_candidate| is a loop header.
if (header_block_candidate->IsLoopHeader()) {
// GetOrCreateSimpleLoopPreheader only supports reachable blocks.
return GetIRContext()->cfg()->preds(header_block_candidate->id()).size() ==
1
? nullptr
: GetOrCreateSimpleLoopPreheader(header_block_candidate->id());
}
// Try to split |header_block_candidate| if it's already a header block.
if (header_block_candidate->GetMergeInst()) {
SplitBlockAfterOpPhiOrOpVariable(header_block_candidate->id());
}
return header_block_candidate;
}
opt::BasicBlock* FuzzerPassWrapRegionsInSelections::MaybeGetMergeBlockCandidate(
opt::BasicBlock* merge_block_candidate) {
// If |merge_block_candidate| is a merge block of some construct, try to split
// it and return a newly created block.
if (GetIRContext()->GetStructuredCFGAnalysis()->IsMergeBlock(
merge_block_candidate->id())) {
// We can't split a merge block if it's also a loop header.
return merge_block_candidate->IsLoopHeader()
? nullptr
: SplitBlockAfterOpPhiOrOpVariable(merge_block_candidate->id());
}
return merge_block_candidate;
}
} // namespace fuzz
} // namespace spvtools

View File

@ -0,0 +1,55 @@
// Copyright (c) 2020 Vasyl Teliman
//
// 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_WRAP_REGIONS_IN_SELECTIONS_H_
#define SOURCE_FUZZ_FUZZER_PASS_WRAP_REGIONS_IN_SELECTIONS_H_
#include "source/fuzz/fuzzer_pass.h"
namespace spvtools {
namespace fuzz {
// Randomly wraps a region of blocks in every function into a selection
// construct.
class FuzzerPassWrapRegionsInSelections : public FuzzerPass {
public:
FuzzerPassWrapRegionsInSelections(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations);
~FuzzerPassWrapRegionsInSelections() override;
void Apply() override;
private:
// Tries to adjust |header_block_candidate| such that
// TransformationWrapRegionInSelection has higher chances of being
// applied. In particular, tries to split |header_block_candidate| if it's
// already a header block of some other construct.
opt::BasicBlock* MaybeGetHeaderBlockCandidate(
opt::BasicBlock* header_block_candidate);
// Tries to adjust |merge_block_candidate| such that
// TransformationWrapRegionInSelection has higher chances of being
// applied. In particular, tries to split |merge_block_candidate| if it's
// already a merge block of some other construct.
opt::BasicBlock* MaybeGetMergeBlockCandidate(
opt::BasicBlock* merge_block_candidate);
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FUZZER_PASS_WRAP_REGIONS_IN_SELECTIONS_H_

View File

@ -67,6 +67,7 @@
#include "source/fuzz/fuzzer_pass_replace_params_with_struct.h"
#include "source/fuzz/fuzzer_pass_split_blocks.h"
#include "source/fuzz/fuzzer_pass_swap_conditional_branch_operands.h"
#include "source/fuzz/fuzzer_pass_wrap_regions_in_selections.h"
namespace spvtools {
namespace fuzz {
@ -156,6 +157,7 @@ class RepeatedPassInstances {
REPEATED_PASS_INSTANCE(ReplaceParamsWithStruct);
REPEATED_PASS_INSTANCE(SplitBlocks);
REPEATED_PASS_INSTANCE(SwapBranchConditionalOperands);
REPEATED_PASS_INSTANCE(WrapRegionsInSelections);
#undef REPEATED_PASS_INSTANCE
public:

View File

@ -120,7 +120,8 @@ RepeatedPassRecommenderStandard::GetFuturePassRecommendations(
// outlining functions.
return RandomOrderAndNonNull(
{pass_instances_->GetDuplicateRegionsWithSelections(),
pass_instances_->GetOutlineFunctions()});
pass_instances_->GetOutlineFunctions(),
pass_instances_->GetWrapRegionsInSelections()});
}
if (&pass == pass_instances_->GetAddLoopsToCreateIntConstantSynonyms()) {
// - New synonyms can be applied
@ -308,6 +309,16 @@ RepeatedPassRecommenderStandard::GetFuturePassRecommendations(
// No obvious follow-on passes
return {};
}
if (&pass == pass_instances_->GetWrapRegionsInSelections()) {
// - This pass uses an irrelevant boolean constant - we can replace it with
// something more interesting.
// - We can obfuscate that very constant as well.
// - We can flatten created selection construct.
return RandomOrderAndNonNull(
{pass_instances_->GetObfuscateConstants(),
pass_instances_->GetReplaceIrrelevantIds(),
pass_instances_->GetFlattenConditionalBranches()});
}
assert(false && "Unreachable: every fuzzer pass should be dealt with.");
return {};
}

View File

@ -498,6 +498,7 @@ message Transformation {
TransformationFlattenConditionalBranch flatten_conditional_branch = 76;
TransformationAddBitInstructionSynonym add_bit_instruction_synonym = 77;
TransformationAddLoopToCreateIntConstantSynonym add_loop_to_create_int_constant_synonym = 78;
TransformationWrapRegionInSelection wrap_region_in_selection = 79;
// Add additional option using the next available number.
}
}
@ -2072,3 +2073,33 @@ message TransformationVectorShuffle {
repeated uint32 component = 5;
}
message TransformationWrapRegionInSelection {
// Transforms a single-entry-single-exit region R into
// if (|branch_condition|) { R } else { R }
// The entry block for R becomes a selection header and
// the exit block - a selection merge.
//
// Note that the region R is not duplicated. Thus, the effect of
// this transformation can be represented as follows:
// entry
// entry / \
// | \ /
// R --> R
// | |
// exit exit
// This behaviour is different from TransformationDuplicateRegionWithSelection
// that copies the blocks in R.
// The entry block for the region R.
uint32 region_entry_block_id = 1;
// The exit block for the region R.
uint32 region_exit_block_id = 2;
// Boolean value for the condition expression.
bool branch_condition = 3;
}

View File

@ -95,6 +95,7 @@
#include "source/fuzz/transformation_swap_conditional_branch_operands.h"
#include "source/fuzz/transformation_toggle_access_chain_instruction.h"
#include "source/fuzz/transformation_vector_shuffle.h"
#include "source/fuzz/transformation_wrap_region_in_selection.h"
#include "source/util/make_unique.h"
namespace spvtools {
@ -342,6 +343,9 @@ std::unique_ptr<Transformation> Transformation::FromMessage(
message.toggle_access_chain_instruction());
case protobufs::Transformation::TransformationCase::kVectorShuffle:
return MakeUnique<TransformationVectorShuffle>(message.vector_shuffle());
case protobufs::Transformation::TransformationCase::kWrapRegionInSelection:
return MakeUnique<TransformationWrapRegionInSelection>(
message.wrap_region_in_selection());
case protobufs::Transformation::TRANSFORMATION_NOT_SET:
assert(false && "An unset transformation was encountered.");
return nullptr;

View File

@ -0,0 +1,166 @@
// Copyright (c) 2020 Vasyl Teliman
//
// 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_wrap_region_in_selection.h"
#include "source/fuzz/fuzzer_util.h"
namespace spvtools {
namespace fuzz {
TransformationWrapRegionInSelection::TransformationWrapRegionInSelection(
const protobufs::TransformationWrapRegionInSelection& message)
: message_(message) {}
TransformationWrapRegionInSelection::TransformationWrapRegionInSelection(
uint32_t region_entry_block_id, uint32_t region_exit_block_id,
bool branch_condition) {
message_.set_region_entry_block_id(region_entry_block_id);
message_.set_region_exit_block_id(region_exit_block_id);
message_.set_branch_condition(branch_condition);
}
bool TransformationWrapRegionInSelection::IsApplicable(
opt::IRContext* ir_context,
const TransformationContext& transformation_context) const {
// Check that it is possible to outline a region of blocks without breaking
// domination and structured control flow rules.
if (!IsApplicableToBlockRange(ir_context, message_.region_entry_block_id(),
message_.region_exit_block_id())) {
return false;
}
// There must exist an irrelevant boolean constant to be used as a condition
// in the OpBranchConditional instruction.
return fuzzerutil::MaybeGetBoolConstant(ir_context, transformation_context,
message_.branch_condition(),
true) != 0;
}
void TransformationWrapRegionInSelection::Apply(
opt::IRContext* ir_context,
TransformationContext* transformation_context) const {
auto* new_header_block =
ir_context->cfg()->block(message_.region_entry_block_id());
assert(new_header_block->terminator()->opcode() == SpvOpBranch &&
"This condition should have been checked in the IsApplicable");
const auto successor_id =
new_header_block->terminator()->GetSingleWordInOperand(0);
// Change |entry_block|'s terminator to |OpBranchConditional|.
new_header_block->terminator()->SetOpcode(SpvOpBranchConditional);
new_header_block->terminator()->SetInOperands(
{{SPV_OPERAND_TYPE_ID,
{fuzzerutil::MaybeGetBoolConstant(ir_context, *transformation_context,
message_.branch_condition(), true)}},
{SPV_OPERAND_TYPE_ID, {successor_id}},
{SPV_OPERAND_TYPE_ID, {successor_id}}});
// Insert OpSelectionMerge before the terminator.
new_header_block->terminator()->InsertBefore(MakeUnique<opt::Instruction>(
ir_context, SpvOpSelectionMerge, 0, 0,
opt::Instruction::OperandList{
{SPV_OPERAND_TYPE_ID, {message_.region_exit_block_id()}},
{SPV_OPERAND_TYPE_SELECTION_CONTROL,
{SpvSelectionControlMaskNone}}}));
// We've change the module so we must invalidate analyses.
ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
}
protobufs::Transformation TransformationWrapRegionInSelection::ToMessage()
const {
protobufs::Transformation result;
*result.mutable_wrap_region_in_selection() = message_;
return result;
}
bool TransformationWrapRegionInSelection::IsApplicableToBlockRange(
opt::IRContext* ir_context, uint32_t header_block_candidate_id,
uint32_t merge_block_candidate_id) {
// Check that |header_block_candidate_id| and |merge_block_candidate_id| are
// valid.
const auto* header_block_candidate =
fuzzerutil::MaybeFindBlock(ir_context, header_block_candidate_id);
if (!header_block_candidate) {
return false;
}
const auto* merge_block_candidate =
fuzzerutil::MaybeFindBlock(ir_context, merge_block_candidate_id);
if (!merge_block_candidate) {
return false;
}
// |header_block_candidate| and |merge_block_candidate| must be from the same
// function.
if (header_block_candidate->GetParent() !=
merge_block_candidate->GetParent()) {
return false;
}
const auto* dominator_analysis =
ir_context->GetDominatorAnalysis(header_block_candidate->GetParent());
const auto* postdominator_analysis =
ir_context->GetPostDominatorAnalysis(header_block_candidate->GetParent());
if (!dominator_analysis->StrictlyDominates(header_block_candidate,
merge_block_candidate) ||
!postdominator_analysis->StrictlyDominates(merge_block_candidate,
header_block_candidate)) {
return false;
}
// |header_block_candidate| can't be a header since we are about to make it
// one.
if (header_block_candidate->GetMergeInst()) {
return false;
}
// |header_block_candidate| must have an OpBranch terminator.
if (header_block_candidate->terminator()->opcode() != SpvOpBranch) {
return false;
}
// Every header block must have a unique merge block. Thus,
// |merge_block_candidate| can't be a merge block of some other header.
auto* structured_cfg = ir_context->GetStructuredCFGAnalysis();
if (structured_cfg->IsMergeBlock(merge_block_candidate_id)) {
return false;
}
// |header_block_candidate|'s containing construct must also contain
// |merge_block_candidate|.
//
// ContainingConstruct will return the id of a loop header for a block in the
// loop's continue construct. Thus, we must also check the case when one of
// the candidates is in continue construct and the other one is not.
if (structured_cfg->ContainingConstruct(header_block_candidate_id) !=
structured_cfg->ContainingConstruct(merge_block_candidate_id) ||
structured_cfg->IsInContinueConstruct(header_block_candidate_id) !=
structured_cfg->IsInContinueConstruct(merge_block_candidate_id)) {
return false;
}
return true;
}
std::unordered_set<uint32_t> TransformationWrapRegionInSelection::GetFreshIds()
const {
return std::unordered_set<uint32_t>();
}
} // namespace fuzz
} // namespace spvtools

View File

@ -0,0 +1,88 @@
// Copyright (c) 2020 Vasyl Teliman
//
// 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_WRAP_REGION_IN_SELECTION_H_
#define SOURCE_FUZZ_TRANSFORMATION_WRAP_REGION_IN_SELECTION_H_
#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
#include "source/fuzz/transformation.h"
#include "source/fuzz/transformation_context.h"
#include "source/opt/ir_context.h"
namespace spvtools {
namespace fuzz {
class TransformationWrapRegionInSelection : public Transformation {
public:
explicit TransformationWrapRegionInSelection(
const protobufs::TransformationWrapRegionInSelection& message);
TransformationWrapRegionInSelection(uint32_t region_entry_block_id,
uint32_t region_exit_block_id,
bool branch_condition);
// - It should be possible to apply this transformation to a
// single-exit-single-entry region of blocks dominated by
// |region_entry_block_id| and postdominated by |region_exit_block_id|
// (see IsApplicableToBlockRange method for further details).
//
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3828):
// Consider applying this transformation to non-single-entry-single-exit
// regions of blocks.
// - There must exist an irrelevant boolean constant with value
// |branch_condition|.
bool IsApplicable(
opt::IRContext* ir_context,
const TransformationContext& transformation_context) const override;
// - Transforms |region_entry_block_id| into a selection header with both
// branches pointing to the block's successor.
// - |branch_condition| is used as a condition in the header's
// OpBranchConditional instruction.
// - Transforms |region_exit_block_id| into a merge block of the selection's
// header.
void Apply(opt::IRContext* ir_context,
TransformationContext* transformation_context) const override;
protobufs::Transformation ToMessage() const override;
// Returns true if it's possible to apply this transformation to the
// single-exit-single-entry region of blocks starting with
// |header_block_candidate_id| and ending with |merge_block_candidate_id|.
// Concretely:
// - Both |header_block_candidate_id| and |merge_block_candidate_id| must be
// result ids of some blocks in the module.
// - Both blocks must belong to the same function.
// - |header_block_candidate_id| must strictly dominate
// |merge_block_candidate_id| and |merge_block_candidate_id| must strictly
// postdominate |header_block_candidate_id|.
// - |header_block_candidate_id| can't be a header block of any construct.
// - |header_block_candidate_id|'s terminator must be an OpBranch.
// - |merge_block_candidate_id| can't be a merge block of any other construct.
// - Both |header_block_candidate_id| and |merge_block_candidate_id| must be
// inside the same construct if any.
static bool IsApplicableToBlockRange(opt::IRContext* ir_context,
uint32_t header_block_candidate_id,
uint32_t merge_block_candidate_id);
std::unordered_set<uint32_t> GetFreshIds() const override;
private:
protobufs::TransformationWrapRegionInSelection message_;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_TRANSFORMATION_WRAP_REGION_IN_SELECTION_H_

View File

@ -107,6 +107,7 @@ if (${SPIRV_BUILD_FUZZER})
transformation_toggle_access_chain_instruction_test.cpp
transformation_record_synonymous_constants_test.cpp
transformation_vector_shuffle_test.cpp
transformation_wrap_region_in_selection_test.cpp
uniform_buffer_element_descriptor_test.cpp)
if (${SPIRV_ENABLE_LONG_FUZZER_TESTS})

View File

@ -0,0 +1,262 @@
// Copyright (c) 2020 Vasyl Teliman
//
// 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_wrap_region_in_selection.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/instruction_descriptor.h"
#include "test/fuzz/fuzz_test_util.h"
namespace spvtools {
namespace fuzz {
namespace {
TEST(TransformationWrapRegionInSelectionTest, BasicTest) {
std::string shader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%7 = OpTypeBool
%8 = OpConstantTrue %7
%4 = OpFunction %2 None %3
%5 = OpLabel
OpBranch %6
%6 = OpLabel
OpSelectionMerge %12 None
OpBranchConditional %8 %11 %12
%11 = OpLabel
OpReturn
%12 = OpLabel
OpSelectionMerge %15 None
OpBranchConditional %8 %13 %14
%13 = OpLabel
OpBranch %15
%14 = OpLabel
OpBranch %15
%15 = OpLabel
OpBranch %16
%16 = OpLabel
OpReturn
OpFunctionEnd
%9 = OpFunction %2 None %3
%10 = OpLabel
OpBranch %20
%20 = OpLabel
OpLoopMerge %23 %22 None
OpBranch %21
%21 = OpLabel
OpBranchConditional %8 %24 %23
%24 = OpLabel
OpBranch %22
; continue target
%22 = OpLabel
OpLoopMerge %25 %28 None
OpBranchConditional %8 %27 %25
%27 = OpLabel
OpBranch %28
%28 = OpLabel
OpBranch %22
%25 = OpLabel
OpBranch %20
; merge block
%23 = OpLabel
OpBranch %26
%26 = OpLabel
OpReturn
OpFunctionEnd
)";
const auto env = SPV_ENV_UNIVERSAL_1_3;
const auto consumer = nullptr;
const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
ASSERT_TRUE(IsValid(env, context.get()));
spvtools::ValidatorOptions validator_options;
TransformationContext transformation_context(
MakeUnique<FactManager>(context.get()), validator_options);
// Boolean constant does not exist.
ASSERT_FALSE(TransformationWrapRegionInSelection(5, 6, false)
.IsApplicable(context.get(), transformation_context));
// Irrelevant constant does not exist.
ASSERT_FALSE(TransformationWrapRegionInSelection(5, 6, true)
.IsApplicable(context.get(), transformation_context));
transformation_context.GetFactManager()->AddFactIdIsIrrelevant(8);
// Block ids are invalid.
ASSERT_FALSE(TransformationWrapRegionInSelection(100, 6, true)
.IsApplicable(context.get(), transformation_context));
ASSERT_FALSE(TransformationWrapRegionInSelection(5, 100, true)
.IsApplicable(context.get(), transformation_context));
// Blocks are from different functions.
ASSERT_FALSE(TransformationWrapRegionInSelection(5, 10, true)
.IsApplicable(context.get(), transformation_context));
// Header block candidate does not dominate merge block candidate.
ASSERT_FALSE(TransformationWrapRegionInSelection(13, 16, true)
.IsApplicable(context.get(), transformation_context));
// Header block candidate does not *strictly* dominate merge block candidate.
ASSERT_FALSE(TransformationWrapRegionInSelection(5, 5, true)
.IsApplicable(context.get(), transformation_context));
// Merge block candidate does not postdominate header block candidate.
ASSERT_FALSE(TransformationWrapRegionInSelection(5, 16, true)
.IsApplicable(context.get(), transformation_context));
// Header block candidate is already a header block of some other construct.
ASSERT_FALSE(TransformationWrapRegionInSelection(12, 16, true)
.IsApplicable(context.get(), transformation_context));
// Header block's terminator is not an OpBranch.
ASSERT_FALSE(TransformationWrapRegionInSelection(21, 24, true)
.IsApplicable(context.get(), transformation_context));
// Merge block candidate is already a merge block of some other construct.
ASSERT_FALSE(TransformationWrapRegionInSelection(5, 15, true)
.IsApplicable(context.get(), transformation_context));
// Header block candidate and merge block candidate are in different
// constructs.
ASSERT_FALSE(TransformationWrapRegionInSelection(10, 21, true)
.IsApplicable(context.get(), transformation_context));
ASSERT_FALSE(TransformationWrapRegionInSelection(24, 25, true)
.IsApplicable(context.get(), transformation_context));
ASSERT_FALSE(TransformationWrapRegionInSelection(24, 22, true)
.IsApplicable(context.get(), transformation_context));
ASSERT_FALSE(TransformationWrapRegionInSelection(24, 27, true)
.IsApplicable(context.get(), transformation_context));
{
// Header block candidate can be a merge block of some existing construct.
TransformationWrapRegionInSelection transformation(15, 16, true);
ASSERT_TRUE(
transformation.IsApplicable(context.get(), transformation_context));
ApplyAndCheckFreshIds(transformation, context.get(),
&transformation_context);
ASSERT_TRUE(IsValid(env, context.get()));
}
{
// Merge block candidate can be a header block of some existing construct.
TransformationWrapRegionInSelection transformation(5, 6, true);
ASSERT_TRUE(
transformation.IsApplicable(context.get(), transformation_context));
ApplyAndCheckFreshIds(transformation, context.get(),
&transformation_context);
ASSERT_TRUE(IsValid(env, context.get()));
}
{
// Wrap a loop construct.
TransformationWrapRegionInSelection transformation(10, 26, true);
ASSERT_TRUE(
transformation.IsApplicable(context.get(), transformation_context));
ApplyAndCheckFreshIds(transformation, 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 %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%7 = OpTypeBool
%8 = OpConstantTrue %7
%4 = OpFunction %2 None %3
%5 = OpLabel
OpSelectionMerge %6 None
OpBranchConditional %8 %6 %6
%6 = OpLabel
OpSelectionMerge %12 None
OpBranchConditional %8 %11 %12
%11 = OpLabel
OpReturn
%12 = OpLabel
OpSelectionMerge %15 None
OpBranchConditional %8 %13 %14
%13 = OpLabel
OpBranch %15
%14 = OpLabel
OpBranch %15
%15 = OpLabel
OpSelectionMerge %16 None
OpBranchConditional %8 %16 %16
%16 = OpLabel
OpReturn
OpFunctionEnd
%9 = OpFunction %2 None %3
%10 = OpLabel
OpSelectionMerge %26 None
OpBranchConditional %8 %20 %20
%20 = OpLabel
OpLoopMerge %23 %22 None
OpBranch %21
%21 = OpLabel
OpBranchConditional %8 %24 %23
%24 = OpLabel
OpBranch %22
; continue target
%22 = OpLabel
OpLoopMerge %25 %28 None
OpBranchConditional %8 %27 %25
%27 = OpLabel
OpBranch %28
%28 = OpLabel
OpBranch %22
%25 = OpLabel
OpBranch %20
; merge block
%23 = OpLabel
OpBranch %26
%26 = OpLabel
OpReturn
OpFunctionEnd
)";
ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
}
} // namespace
} // namespace fuzz
} // namespace spvtools