spirv-fuzz: Replace dead-block terminators with OpKill etc. (#3882)

Fixes #3615.
This commit is contained in:
Alastair Donaldson 2020-10-06 15:59:05 +01:00 committed by GitHub
parent 63cc22d645
commit fc8264854c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 1045 additions and 7 deletions

View File

@ -104,6 +104,7 @@ if(SPIRV_BUILD_FUZZER)
fuzzer_pass_propagate_instructions_up.h
fuzzer_pass_push_ids_through_variables.h
fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.h
fuzzer_pass_replace_branches_from_dead_blocks_with_exits.h
fuzzer_pass_replace_copy_memories_with_loads_stores.h
fuzzer_pass_replace_copy_objects_with_stores_loads.h
fuzzer_pass_replace_irrelevant_ids.h
@ -197,6 +198,7 @@ if(SPIRV_BUILD_FUZZER)
transformation_record_synonymous_constants.h
transformation_replace_add_sub_mul_with_carrying_extended.h
transformation_replace_boolean_constant_with_constant_binary.h
transformation_replace_branch_from_dead_block_with_exit.h
transformation_replace_constant_with_uniform.h
transformation_replace_copy_memory_with_load_store.h
transformation_replace_copy_object_with_store_load.h
@ -288,6 +290,7 @@ if(SPIRV_BUILD_FUZZER)
fuzzer_pass_propagate_instructions_up.cpp
fuzzer_pass_push_ids_through_variables.cpp
fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.cpp
fuzzer_pass_replace_branches_from_dead_blocks_with_exits.cpp
fuzzer_pass_replace_copy_memories_with_loads_stores.cpp
fuzzer_pass_replace_copy_objects_with_stores_loads.cpp
fuzzer_pass_replace_irrelevant_ids.cpp
@ -379,6 +382,7 @@ if(SPIRV_BUILD_FUZZER)
transformation_record_synonymous_constants.cpp
transformation_replace_add_sub_mul_with_carrying_extended.cpp
transformation_replace_boolean_constant_with_constant_binary.cpp
transformation_replace_branch_from_dead_block_with_exit.cpp
transformation_replace_constant_with_uniform.cpp
transformation_replace_copy_memory_with_load_store.cpp
transformation_replace_copy_object_with_store_load.cpp

View File

@ -72,6 +72,7 @@
#include "source/fuzz/fuzzer_pass_propagate_instructions_up.h"
#include "source/fuzz/fuzzer_pass_push_ids_through_variables.h"
#include "source/fuzz/fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.h"
#include "source/fuzz/fuzzer_pass_replace_branches_from_dead_blocks_with_exits.h"
#include "source/fuzz/fuzzer_pass_replace_copy_memories_with_loads_stores.h"
#include "source/fuzz/fuzzer_pass_replace_copy_objects_with_stores_loads.h"
#include "source/fuzz/fuzzer_pass_replace_irrelevant_ids.h"
@ -282,6 +283,8 @@ Fuzzer::FuzzerResult Fuzzer::Run() {
MaybeAddRepeatedPass<FuzzerPassPushIdsThroughVariables>(&pass_instances);
MaybeAddRepeatedPass<FuzzerPassReplaceAddsSubsMulsWithCarryingExtended>(
&pass_instances);
MaybeAddRepeatedPass<FuzzerPassReplaceBranchesFromDeadBlocksWithExits>(
&pass_instances);
MaybeAddRepeatedPass<FuzzerPassReplaceCopyMemoriesWithLoadsStores>(
&pass_instances);
MaybeAddRepeatedPass<FuzzerPassReplaceCopyObjectsWithStoresLoads>(

View File

@ -114,6 +114,8 @@ const std::pair<uint32_t, uint32_t> kChanceOfPropagatingInstructionsUp = {20,
const std::pair<uint32_t, uint32_t> kChanceOfPushingIdThroughVariable = {5, 50};
const std::pair<uint32_t, uint32_t>
kChanceOfReplacingAddSubMulWithCarryingExtended = {20, 70};
const std::pair<uint32_t, uint32_t>
kChanceOfReplacingBranchFromDeadBlockWithExit = {10, 65};
const std::pair<uint32_t, uint32_t> kChanceOfReplacingCopyMemoryWithLoadStore =
{20, 90};
const std::pair<uint32_t, uint32_t> kChanceOfReplacingCopyObjectWithStoreLoad =
@ -304,6 +306,8 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator,
ChooseBetweenMinAndMax(kChanceOfPushingIdThroughVariable);
chance_of_replacing_add_sub_mul_with_carrying_extended_ =
ChooseBetweenMinAndMax(kChanceOfReplacingAddSubMulWithCarryingExtended);
chance_of_replacing_branch_from_dead_block_with_exit_ =
ChooseBetweenMinAndMax(kChanceOfReplacingBranchFromDeadBlockWithExit);
chance_of_replacing_copy_memory_with_load_store_ =
ChooseBetweenMinAndMax(kChanceOfReplacingCopyMemoryWithLoadStore);
chance_of_replacing_copyobject_with_store_load_ =

View File

@ -276,6 +276,9 @@ class FuzzerContext {
uint32_t GetChanceOfReplacingAddSubMulWithCarryingExtended() {
return chance_of_replacing_add_sub_mul_with_carrying_extended_;
}
uint32_t GetChanceOfReplacingBranchFromDeadBlockWithExit() {
return chance_of_replacing_branch_from_dead_block_with_exit_;
}
uint32_t GetChanceOfReplacingCopyMemoryWithLoadStore() {
return chance_of_replacing_copy_memory_with_load_store_;
}
@ -470,6 +473,7 @@ class FuzzerContext {
uint32_t chance_of_propagating_instructions_up_;
uint32_t chance_of_pushing_id_through_variable_;
uint32_t chance_of_replacing_add_sub_mul_with_carrying_extended_;
uint32_t chance_of_replacing_branch_from_dead_block_with_exit_;
uint32_t chance_of_replacing_copy_memory_with_load_store_;
uint32_t chance_of_replacing_copyobject_with_store_load_;
uint32_t chance_of_replacing_id_with_synonym_;

View File

@ -0,0 +1,132 @@
// 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_branches_from_dead_blocks_with_exits.h"
#include <algorithm>
#include <vector>
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/transformation_replace_branch_from_dead_block_with_exit.h"
namespace spvtools {
namespace fuzz {
FuzzerPassReplaceBranchesFromDeadBlocksWithExits::
FuzzerPassReplaceBranchesFromDeadBlocksWithExits(
opt::IRContext* ir_context,
TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: FuzzerPass(ir_context, transformation_context, fuzzer_context,
transformations) {}
FuzzerPassReplaceBranchesFromDeadBlocksWithExits::
~FuzzerPassReplaceBranchesFromDeadBlocksWithExits() = default;
void FuzzerPassReplaceBranchesFromDeadBlocksWithExits::Apply() {
// OpKill can only be used as a terminator in a function that is guaranteed
// to be executed with the Fragment execution model. We conservatively only
// allow OpKill if every entry point in the module has the Fragment execution
// model.
auto fragment_execution_model_guaranteed =
std::all_of(GetIRContext()->module()->entry_points().begin(),
GetIRContext()->module()->entry_points().end(),
[](const opt::Instruction& entry_point) -> bool {
return entry_point.GetSingleWordInOperand(0) ==
SpvExecutionModelFragment;
});
// Transformations of this type can disable one another. To avoid ordering
// bias, we therefore build a set of candidate transformations to apply, and
// subsequently apply them in a random order, skipping any that cease to be
// applicable.
std::vector<TransformationReplaceBranchFromDeadBlockWithExit>
candidate_transformations;
// Consider every block in every function.
for (auto& function : *GetIRContext()->module()) {
for (auto& block : function) {
// Probabilistically decide whether to skip this block.
if (GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()
->GetChanceOfReplacingBranchFromDeadBlockWithExit())) {
continue;
}
// Check whether the block is suitable for having its terminator replaced.
if (!TransformationReplaceBranchFromDeadBlockWithExit::BlockIsSuitable(
GetIRContext(), *GetTransformationContext(), block)) {
continue;
}
// We can always use OpUnreachable to replace a block's terminator.
// Whether we can use OpKill depends on the execution model, and which of
// OpReturn and OpReturnValue we can use depends on the return type of the
// enclosing function.
std::vector<SpvOp> opcodes = {SpvOpUnreachable};
if (fragment_execution_model_guaranteed) {
opcodes.emplace_back(SpvOpKill);
}
auto function_return_type =
GetIRContext()->get_type_mgr()->GetType(function.type_id());
if (function_return_type->AsVoid()) {
opcodes.emplace_back(SpvOpReturn);
} else if (fuzzerutil::CanCreateConstant(*function_return_type)) {
// For simplicity we only allow OpReturnValue if the function return
// type is a type for which we can create a constant. This allows us a
// zero of the given type as a default return value.
opcodes.emplace_back(SpvOpReturnValue);
}
// Choose one of the available terminator opcodes at random and create a
// candidate transformation.
auto opcode = opcodes[GetFuzzerContext()->RandomIndex(opcodes)];
candidate_transformations.emplace_back(
TransformationReplaceBranchFromDeadBlockWithExit(
block.id(), opcode,
opcode == SpvOpReturnValue
? FindOrCreateZeroConstant(function.type_id(), true)
: 0));
}
}
// Process the candidate transformations in a random order.
while (!candidate_transformations.empty()) {
// Transformations of this type can disable one another. For example,
// suppose we have dead blocks A, B, C, D arranged as follows:
//
// A |
// / \ |
// B C |
// \ / |
// D |
//
// Here we can replace the terminator of either B or C with an early exit,
// because D has two predecessors. But if we replace the terminator of B,
// say, we get:
//
// A |
// / \ |
// B C |
// / |
// D |
//
// and now it is no longer OK to replace the terminator of C as D only has
// one predecessor and we do not want to make D unreachable in the control
// flow graph.
MaybeApplyTransformation(
GetFuzzerContext()->RemoveAtRandomIndex(&candidate_transformations));
}
}
} // namespace fuzz
} // namespace spvtools

View File

@ -0,0 +1,41 @@
// 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_BRANCHES_FROM_DEAD_BLOCKS_WITH_EXITS_H_
#define SOURCE_FUZZ_FUZZER_PASS_REPLACE_BRANCHES_FROM_DEAD_BLOCKS_WITH_EXITS_H_
#include "source/fuzz/fuzzer_pass.h"
namespace spvtools {
namespace fuzz {
// Fuzzer pass that, under the right conditions, replaces branch instructions
// from dead blocks with non-branching "exit" terminators, such as OpKill and
// OpReturn.
class FuzzerPassReplaceBranchesFromDeadBlocksWithExits : public FuzzerPass {
public:
FuzzerPassReplaceBranchesFromDeadBlocksWithExits(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations);
~FuzzerPassReplaceBranchesFromDeadBlocksWithExits() override;
void Apply() override;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FUZZER_PASS_REPLACE_BRANCHES_FROM_DEAD_BLOCKS_WITH_EXITS_H_

View File

@ -58,6 +58,7 @@
#include "source/fuzz/fuzzer_pass_propagate_instructions_up.h"
#include "source/fuzz/fuzzer_pass_push_ids_through_variables.h"
#include "source/fuzz/fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.h"
#include "source/fuzz/fuzzer_pass_replace_branches_from_dead_blocks_with_exits.h"
#include "source/fuzz/fuzzer_pass_replace_copy_memories_with_loads_stores.h"
#include "source/fuzz/fuzzer_pass_replace_copy_objects_with_stores_loads.h"
#include "source/fuzz/fuzzer_pass_replace_irrelevant_ids.h"
@ -150,6 +151,7 @@ class RepeatedPassInstances {
REPEATED_PASS_INSTANCE(PropagateInstructionsUp);
REPEATED_PASS_INSTANCE(PushIdsThroughVariables);
REPEATED_PASS_INSTANCE(ReplaceAddsSubsMulsWithCarryingExtended);
REPEATED_PASS_INSTANCE(ReplaceBranchesFromDeadBlocksWithExits);
REPEATED_PASS_INSTANCE(ReplaceCopyMemoriesWithLoadsStores);
REPEATED_PASS_INSTANCE(ReplaceCopyObjectsWithStoresLoads);
REPEATED_PASS_INSTANCE(ReplaceLoadsStoresWithCopyMemories);

View File

@ -63,10 +63,12 @@ RepeatedPassRecommenderStandard::GetFuturePassRecommendations(
// - Dead blocks are great for adding function calls
// - Dead blocks are also great for adding loads and stores
// - The guard associated with a dead block can be obfuscated
return RandomOrderAndNonNull({pass_instances_->GetAddFunctionCalls(),
pass_instances_->GetAddLoads(),
pass_instances_->GetAddStores(),
pass_instances_->GetObfuscateConstants()});
// - Branches from dead blocks may be replaced with exits
return RandomOrderAndNonNull(
{pass_instances_->GetAddFunctionCalls(), pass_instances_->GetAddLoads(),
pass_instances_->GetAddStores(),
pass_instances_->GetObfuscateConstants(),
pass_instances_->GetReplaceBranchesFromDeadBlocksWithExits()});
}
if (&pass == pass_instances_->GetAddDeadBreaks()) {
// - The guard of the dead break is a good candidate for obfuscation
@ -189,9 +191,12 @@ RepeatedPassRecommenderStandard::GetFuturePassRecommendations(
// - New functions in the module can be called
// - Donated dead functions produce irrelevant ids, which can be replaced
// - Donated functions are good candidates for having their returns merged
return RandomOrderAndNonNull({pass_instances_->GetAddFunctionCalls(),
pass_instances_->GetReplaceIrrelevantIds(),
pass_instances_->GetMergeFunctionReturns()});
// - Donated dead functions may allow branches to be replaced with exits
return RandomOrderAndNonNull(
{pass_instances_->GetAddFunctionCalls(),
pass_instances_->GetReplaceIrrelevantIds(),
pass_instances_->GetMergeFunctionReturns(),
pass_instances_->GetReplaceBranchesFromDeadBlocksWithExits()});
}
if (&pass == pass_instances_->GetDuplicateRegionsWithSelections()) {
// - Parts of duplicated regions can be outlined
@ -274,6 +279,11 @@ RepeatedPassRecommenderStandard::GetFuturePassRecommendations(
// No obvious follow-on passes
return {};
}
if (&pass == pass_instances_->GetReplaceBranchesFromDeadBlocksWithExits()) {
// - Changing a branch to OpReturnValue introduces an irrelevant id, which
// can be replaced
return RandomOrderAndNonNull({pass_instances_->GetReplaceIrrelevantIds()});
}
if (&pass == pass_instances_->GetReplaceCopyMemoriesWithLoadsStores()) {
// No obvious follow-on passes
return {};

View File

@ -553,6 +553,7 @@ message Transformation {
TransformationWrapRegionInSelection wrap_region_in_selection = 79;
TransformationAddEarlyTerminatorWrapper add_early_terminator_wrapper = 80;
TransformationPropagateInstructionDown propagate_instruction_down = 81;
TransformationReplaceBranchFromDeadBlockWithExit replace_branch_from_dead_block_with_exit = 82;
// Add additional option using the next available number.
}
}
@ -1863,6 +1864,24 @@ message TransformationReplaceAddSubMulWithCarryingExtended {
}
message TransformationReplaceBranchFromDeadBlockWithExit {
// Given a dead block that ends with OpBranch, replaces OpBranch with an
// "exit" instruction; one of OpReturn/OpReturnValue, OpKill (in a fragment
// shader) or OpUnreachable.
// The dead block whose terminator is to be replaced.
uint32 block_id = 1;
// The opcode of the new terminator.
uint32 opcode = 2;
// Ignored unless opcode is OpReturnValue, in which case this field provides
// a suitable result id to be returned.
uint32 return_value_id = 3;
}
message TransformationReplaceParameterWithGlobal {
// Removes parameter with result id |parameter_id| from its function

View File

@ -76,6 +76,7 @@
#include "source/fuzz/transformation_record_synonymous_constants.h"
#include "source/fuzz/transformation_replace_add_sub_mul_with_carrying_extended.h"
#include "source/fuzz/transformation_replace_boolean_constant_with_constant_binary.h"
#include "source/fuzz/transformation_replace_branch_from_dead_block_with_exit.h"
#include "source/fuzz/transformation_replace_constant_with_uniform.h"
#include "source/fuzz/transformation_replace_copy_memory_with_load_store.h"
#include "source/fuzz/transformation_replace_copy_object_with_store_load.h"
@ -282,6 +283,10 @@ std::unique_ptr<Transformation> Transformation::FromMessage(
kReplaceBooleanConstantWithConstantBinary:
return MakeUnique<TransformationReplaceBooleanConstantWithConstantBinary>(
message.replace_boolean_constant_with_constant_binary());
case protobufs::Transformation::TransformationCase::
kReplaceBranchFromDeadBlockWithExit:
return MakeUnique<TransformationReplaceBranchFromDeadBlockWithExit>(
message.replace_branch_from_dead_block_with_exit());
case protobufs::Transformation::TransformationCase::
kReplaceConstantWithUniform:
return MakeUnique<TransformationReplaceConstantWithUniform>(

View File

@ -0,0 +1,169 @@
// 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_branch_from_dead_block_with_exit.h"
#include "source/fuzz/fuzzer_util.h"
namespace spvtools {
namespace fuzz {
TransformationReplaceBranchFromDeadBlockWithExit::
TransformationReplaceBranchFromDeadBlockWithExit(
const spvtools::fuzz::protobufs::
TransformationReplaceBranchFromDeadBlockWithExit& message)
: message_(message) {}
TransformationReplaceBranchFromDeadBlockWithExit::
TransformationReplaceBranchFromDeadBlockWithExit(uint32_t block_id,
SpvOp opcode,
uint32_t return_value_id) {
message_.set_block_id(block_id);
message_.set_opcode(opcode);
message_.set_return_value_id(return_value_id);
}
bool TransformationReplaceBranchFromDeadBlockWithExit::IsApplicable(
opt::IRContext* ir_context,
const TransformationContext& transformation_context) const {
// The block whose terminator is to be changed must exist.
auto block = ir_context->get_instr_block(message_.block_id());
if (!block) {
return false;
}
if (!BlockIsSuitable(ir_context, transformation_context, *block)) {
return false;
}
auto function_return_type_id = block->GetParent()->type_id();
switch (message_.opcode()) {
case SpvOpKill:
for (auto& entry_point : ir_context->module()->entry_points()) {
if (entry_point.GetSingleWordInOperand(0) !=
SpvExecutionModelFragment) {
// OpKill is only allowed in a fragment shader. This is a
// conservative check: if the module contains a non-fragment entry
// point then adding an OpKill might lead to OpKill being used in a
// non-fragment shader.
return false;
}
}
break;
case SpvOpReturn:
if (ir_context->get_def_use_mgr()
->GetDef(function_return_type_id)
->opcode() != SpvOpTypeVoid) {
// OpReturn is only allowed in a function with void return type.
return false;
}
break;
case SpvOpReturnValue: {
// If the terminator is to be changed to OpReturnValue, with
// |message_.return_value_id| being the value that will be returned, then
// |message_.return_value_id| must have a compatible type and be available
// at the block terminator.
auto return_value =
ir_context->get_def_use_mgr()->GetDef(message_.return_value_id());
if (!return_value || return_value->type_id() != function_return_type_id) {
return false;
}
if (!fuzzerutil::IdIsAvailableBeforeInstruction(
ir_context, block->terminator(), message_.return_value_id())) {
return false;
}
break;
}
default:
assert(message_.opcode() == SpvOpUnreachable &&
"Invalid early exit opcode.");
break;
}
return true;
}
void TransformationReplaceBranchFromDeadBlockWithExit::Apply(
opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
// If the successor block has OpPhi instructions then arguments related to
// |message_.block_id| need to be removed from these instruction.
auto block = ir_context->get_instr_block(message_.block_id());
assert(block->terminator()->opcode() == SpvOpBranch &&
"Precondition: the block must end with OpBranch.");
auto successor = ir_context->get_instr_block(
block->terminator()->GetSingleWordInOperand(0));
successor->ForEachPhiInst([block](opt::Instruction* phi_inst) {
opt::Instruction::OperandList new_phi_in_operands;
for (uint32_t i = 0; i < phi_inst->NumInOperands(); i += 2) {
if (phi_inst->GetSingleWordInOperand(i + 1) == block->id()) {
continue;
}
new_phi_in_operands.emplace_back(phi_inst->GetInOperand(i));
new_phi_in_operands.emplace_back(phi_inst->GetInOperand(i + 1));
}
assert(new_phi_in_operands.size() == phi_inst->NumInOperands() - 2);
phi_inst->SetInOperands(std::move(new_phi_in_operands));
});
// Rewrite the terminator of |message_.block_id|.
opt::Instruction::OperandList new_terminator_in_operands;
if (message_.opcode() == SpvOpReturnValue) {
new_terminator_in_operands.push_back(
{SPV_OPERAND_TYPE_TYPE_ID, {message_.return_value_id()}});
}
auto terminator = block->terminator();
terminator->SetOpcode(static_cast<SpvOp>(message_.opcode()));
terminator->SetInOperands(std::move(new_terminator_in_operands));
ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
}
std::unordered_set<uint32_t>
TransformationReplaceBranchFromDeadBlockWithExit::GetFreshIds() const {
return std::unordered_set<uint32_t>();
}
protobufs::Transformation
TransformationReplaceBranchFromDeadBlockWithExit::ToMessage() const {
protobufs::Transformation result;
*result.mutable_replace_branch_from_dead_block_with_exit() = message_;
return result;
}
bool TransformationReplaceBranchFromDeadBlockWithExit::BlockIsSuitable(
opt::IRContext* ir_context,
const TransformationContext& transformation_context,
const opt::BasicBlock& block) {
// The block must be dead.
if (!transformation_context.GetFactManager()->BlockIsDead(block.id())) {
return false;
}
// The block's terminator must be OpBranch.
if (block.terminator()->opcode() != SpvOpBranch) {
return false;
}
if (ir_context->GetStructuredCFGAnalysis()->IsInContinueConstruct(
block.id())) {
// Early exits from continue constructs are not allowed as they would break
// the SPIR-V structured control flow rules.
return false;
}
// We only allow changing OpBranch to an early terminator if the target of the
// OpBranch has at least one other predecessor.
auto successor = ir_context->get_instr_block(
block.terminator()->GetSingleWordInOperand(0));
if (ir_context->cfg()->preds(successor->id()).size() < 2) {
return false;
}
return true;
}
} // namespace fuzz
} // namespace spvtools

View File

@ -0,0 +1,84 @@
// 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_BRANCH_FROM_DEAD_BLOCK_WITH_EXIT_H_
#define SOURCE_FUZZ_TRANSFORMATION_REPLACE_BRANCH_FROM_DEAD_BLOCK_WITH_EXIT_H_
#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
#include "source/fuzz/transformation.h"
#include "source/fuzz/transformation_context.h"
#include "source/opt/basic_block.h"
#include "source/opt/ir_context.h"
namespace spvtools {
namespace fuzz {
class TransformationReplaceBranchFromDeadBlockWithExit : public Transformation {
public:
explicit TransformationReplaceBranchFromDeadBlockWithExit(
const protobufs::TransformationReplaceBranchFromDeadBlockWithExit&
message);
TransformationReplaceBranchFromDeadBlockWithExit(uint32_t block_id,
SpvOp opcode,
uint32_t return_value_id);
// - |message_.block_id| must be the id of a dead block that is not part of
// a continue construct
// - |message_.block_id| must end with OpBranch
// - The successor of |message_.block_id| must have at least one other
// predecessor
// - |message_.opcode()| must be one of OpKill, OpReturn, OpReturnValue and
// OpUnreachable
// - |message_.opcode()| can only be OpKill the module's entry points all
// have Fragment execution mode
// - |message_.opcode()| can only be OpReturn if the return type of the
// function containing the block is void
// - If |message_.opcode()| is OpReturnValue then |message_.return_value_id|
// must be an id that is available at the block terminator and that matches
// the return type of the enclosing function
bool IsApplicable(
opt::IRContext* ir_context,
const TransformationContext& transformation_context) const override;
// Changes the terminator of |message_.block_id| to have opcode
// |message_.opcode|, additionally with input operand
// |message_.return_value_id| in the case that |message_.opcode| is
// OpReturnValue.
//
// If |message_.block_id|'s successor starts with OpPhi instructions these are
// updated so that they no longer refer to |message_.block_id|.
void Apply(opt::IRContext* ir_context,
TransformationContext* transformation_context) const override;
std::unordered_set<uint32_t> GetFreshIds() const override;
protobufs::Transformation ToMessage() const override;
// Returns true if and only if |block| meets the criteria for having its
// terminator replaced with an early exit (see IsApplicable for details of the
// criteria.)
static bool BlockIsSuitable(
opt::IRContext* ir_context,
const TransformationContext& transformation_context,
const opt::BasicBlock& block);
private:
protobufs::TransformationReplaceBranchFromDeadBlockWithExit message_;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_TRANSFORMATION_REPLACE_BRANCH_FROM_DEAD_BLOCK_WITH_EXIT_H_

View File

@ -92,6 +92,7 @@ if (${SPIRV_BUILD_FUZZER})
transformation_push_id_through_variable_test.cpp
transformation_replace_add_sub_mul_with_carrying_extended_test.cpp
transformation_replace_boolean_constant_with_constant_binary_test.cpp
transformation_replace_branch_from_dead_block_with_exit_test.cpp
transformation_replace_copy_object_with_store_load_test.cpp
transformation_replace_constant_with_uniform_test.cpp
transformation_replace_copy_memory_with_load_store_test.cpp

View File

@ -0,0 +1,560 @@
// 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_branch_from_dead_block_with_exit.h"
#include "test/fuzz/fuzz_test_util.h"
namespace spvtools {
namespace fuzz {
namespace {
TEST(TransformationReplaceBranchFromDeadBlockWithExitTest, BasicTest) {
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
%12 = OpTypeInt 32 1
%13 = OpTypePointer Function %12
%15 = OpConstant %12 1
%17 = OpConstant %12 2
%4 = OpFunction %2 None %3
%5 = OpLabel
%14 = OpVariable %13 Function
OpSelectionMerge %9 None
OpBranchConditional %7 %8 %21
%8 = OpLabel
OpSelectionMerge %11 None
OpBranchConditional %7 %10 %16
%10 = OpLabel
OpStore %14 %15
OpBranch %20
%20 = OpLabel
OpBranch %11
%16 = OpLabel
OpStore %14 %17
OpBranch %11
%11 = OpLabel
OpBranch %9
%21 = OpLabel
OpBranch %9
%9 = OpLabel
OpReturn
OpFunctionEnd
)";
const auto env = SPV_ENV_UNIVERSAL_1_4;
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);
transformation_context.GetFactManager()->AddFactBlockIsDead(8);
transformation_context.GetFactManager()->AddFactBlockIsDead(10);
transformation_context.GetFactManager()->AddFactBlockIsDead(11);
transformation_context.GetFactManager()->AddFactBlockIsDead(16);
transformation_context.GetFactManager()->AddFactBlockIsDead(20);
// Bad: 4 is not a block
ASSERT_FALSE(TransformationReplaceBranchFromDeadBlockWithExit(4, SpvOpKill, 0)
.IsApplicable(context.get(), transformation_context));
// Bad: 200 does not exist
ASSERT_FALSE(
TransformationReplaceBranchFromDeadBlockWithExit(200, SpvOpKill, 0)
.IsApplicable(context.get(), transformation_context));
// Bad: 21 is not a dead block
ASSERT_FALSE(
TransformationReplaceBranchFromDeadBlockWithExit(21, SpvOpKill, 0)
.IsApplicable(context.get(), transformation_context));
// Bad: terminator of 8 is not OpBranch
ASSERT_FALSE(TransformationReplaceBranchFromDeadBlockWithExit(8, SpvOpKill, 0)
.IsApplicable(context.get(), transformation_context));
// Bad: 10's successor only has 10 as a predecessor
ASSERT_FALSE(
TransformationReplaceBranchFromDeadBlockWithExit(10, SpvOpKill, 0)
.IsApplicable(context.get(), transformation_context));
#ifndef NDEBUG
ASSERT_DEATH(
TransformationReplaceBranchFromDeadBlockWithExit(20, SpvOpSwitch, 0)
.IsApplicable(context.get(), transformation_context),
"Invalid early exit opcode.");
#endif
auto transformation1 =
TransformationReplaceBranchFromDeadBlockWithExit(20, SpvOpKill, 0);
auto transformation2 =
TransformationReplaceBranchFromDeadBlockWithExit(16, SpvOpKill, 0);
ASSERT_TRUE(
transformation1.IsApplicable(context.get(), transformation_context));
ASSERT_TRUE(
transformation2.IsApplicable(context.get(), transformation_context));
ApplyAndCheckFreshIds(transformation1, context.get(),
&transformation_context);
// Applying transformation 1 should disable transformation 2
ASSERT_FALSE(
transformation2.IsApplicable(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 320
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeBool
%7 = OpConstantFalse %6
%12 = OpTypeInt 32 1
%13 = OpTypePointer Function %12
%15 = OpConstant %12 1
%17 = OpConstant %12 2
%4 = OpFunction %2 None %3
%5 = OpLabel
%14 = OpVariable %13 Function
OpSelectionMerge %9 None
OpBranchConditional %7 %8 %21
%8 = OpLabel
OpSelectionMerge %11 None
OpBranchConditional %7 %10 %16
%10 = OpLabel
OpStore %14 %15
OpBranch %20
%20 = OpLabel
OpKill
%16 = OpLabel
OpStore %14 %17
OpBranch %11
%11 = OpLabel
OpBranch %9
%21 = OpLabel
OpBranch %9
%9 = OpLabel
OpReturn
OpFunctionEnd
)";
ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
}
TEST(TransformationReplaceBranchFromDeadBlockWithExitTest,
VertexShaderWithLoopInContinueConstruct) {
std::string shader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Vertex %4 "main"
OpSource ESSL 320
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeInt 32 1
%7 = OpTypeFunction %6
%12 = OpTypePointer Function %6
%14 = OpConstant %6 0
%22 = OpConstant %6 10
%23 = OpTypeBool
%26 = OpConstant %6 1
%40 = OpConstant %6 100
%48 = OpConstantFalse %23
%4 = OpFunction %2 None %3
%5 = OpLabel
OpSelectionMerge %50 None
OpBranchConditional %48 %49 %50
%49 = OpLabel
%51 = OpFunctionCall %6 %10
OpBranch %50
%50 = OpLabel
OpReturn
OpFunctionEnd
%10 = OpFunction %6 None %7
%11 = OpLabel
%13 = OpVariable %12 Function
%15 = OpVariable %12 Function
%33 = OpVariable %12 Function
OpStore %33 %14
OpBranch %34
%34 = OpLabel
OpLoopMerge %36 %37 None
OpBranch %38
%38 = OpLabel
%39 = OpLoad %6 %33
%41 = OpSLessThan %23 %39 %40
OpBranchConditional %41 %35 %36
%35 = OpLabel
OpSelectionMerge %202 None
OpBranchConditional %48 %200 %201
%200 = OpLabel
OpBranch %202
%201 = OpLabel
%400 = OpCopyObject %6 %14
OpBranch %202
%202 = OpLabel
OpBranch %37
%37 = OpLabel
OpStore %13 %14
OpStore %15 %14
OpBranch %16
%16 = OpLabel
OpLoopMerge %18 %19 None
OpBranch %20
%20 = OpLabel
OpSelectionMerge %102 None
OpBranchConditional %48 %100 %101
%100 = OpLabel
OpBranch %102
%101 = OpLabel
OpBranch %102
%102 = OpLabel
%21 = OpLoad %6 %15
%24 = OpSLessThan %23 %21 %22
OpBranchConditional %24 %17 %18
%17 = OpLabel
OpSelectionMerge %302 None
OpBranchConditional %48 %300 %301
%300 = OpLabel
OpBranch %302
%301 = OpLabel
OpBranch %302
%302 = OpLabel
%25 = OpLoad %6 %13
%27 = OpIAdd %6 %25 %26
OpStore %13 %27
OpBranch %19
%19 = OpLabel
%28 = OpLoad %6 %15
%29 = OpIAdd %6 %28 %26
OpStore %15 %29
OpBranch %16
%18 = OpLabel
%30 = OpLoad %6 %13
%42 = OpCopyObject %6 %30
%43 = OpLoad %6 %33
%44 = OpIAdd %6 %43 %42
OpStore %33 %44
OpBranch %34
%36 = OpLabel
%45 = OpLoad %6 %33
OpReturnValue %45
OpFunctionEnd
)";
const auto env = SPV_ENV_UNIVERSAL_1_4;
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);
for (auto block : {16, 17, 18, 19, 20, 34, 35, 36, 37, 38,
49, 100, 101, 102, 200, 201, 202, 300, 301, 302}) {
transformation_context.GetFactManager()->AddFactBlockIsDead(block);
}
// Bad: OpKill not allowed in vertex shader
ASSERT_FALSE(
TransformationReplaceBranchFromDeadBlockWithExit(201, SpvOpKill, 0)
.IsApplicable(context.get(), transformation_context));
// Bad: OpReturn is not allowed in function that expects a returned value.
ASSERT_FALSE(
TransformationReplaceBranchFromDeadBlockWithExit(200, SpvOpReturn, 0)
.IsApplicable(context.get(), transformation_context));
// Bad: Return value id does not exist
ASSERT_FALSE(TransformationReplaceBranchFromDeadBlockWithExit(
201, SpvOpReturnValue, 1000)
.IsApplicable(context.get(), transformation_context));
// Bad: Return value id does not have a type
ASSERT_FALSE(
TransformationReplaceBranchFromDeadBlockWithExit(200, SpvOpReturnValue, 6)
.IsApplicable(context.get(), transformation_context));
// Bad: Return value id does not have the right type
ASSERT_FALSE(TransformationReplaceBranchFromDeadBlockWithExit(
201, SpvOpReturnValue, 48)
.IsApplicable(context.get(), transformation_context));
// Bad: Return value id is not available
ASSERT_FALSE(TransformationReplaceBranchFromDeadBlockWithExit(
200, SpvOpReturnValue, 400)
.IsApplicable(context.get(), transformation_context));
// Bad: Early exit now allowed in continue construct
ASSERT_FALSE(
TransformationReplaceBranchFromDeadBlockWithExit(101, SpvOpUnreachable, 0)
.IsApplicable(context.get(), transformation_context));
// Bad: Early exit now allowed in continue construct (again)
ASSERT_FALSE(TransformationReplaceBranchFromDeadBlockWithExit(
300, SpvOpReturnValue, 14)
.IsApplicable(context.get(), transformation_context));
auto transformation1 = TransformationReplaceBranchFromDeadBlockWithExit(
200, SpvOpUnreachable, 0);
auto transformation2 = TransformationReplaceBranchFromDeadBlockWithExit(
201, SpvOpReturnValue, 400);
ASSERT_TRUE(
transformation1.IsApplicable(context.get(), transformation_context));
ASSERT_TRUE(
transformation2.IsApplicable(context.get(), transformation_context));
ApplyAndCheckFreshIds(transformation2, context.get(),
&transformation_context);
ASSERT_FALSE(
transformation1.IsApplicable(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 Vertex %4 "main"
OpSource ESSL 320
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeInt 32 1
%7 = OpTypeFunction %6
%12 = OpTypePointer Function %6
%14 = OpConstant %6 0
%22 = OpConstant %6 10
%23 = OpTypeBool
%26 = OpConstant %6 1
%40 = OpConstant %6 100
%48 = OpConstantFalse %23
%4 = OpFunction %2 None %3
%5 = OpLabel
OpSelectionMerge %50 None
OpBranchConditional %48 %49 %50
%49 = OpLabel
%51 = OpFunctionCall %6 %10
OpBranch %50
%50 = OpLabel
OpReturn
OpFunctionEnd
%10 = OpFunction %6 None %7
%11 = OpLabel
%13 = OpVariable %12 Function
%15 = OpVariable %12 Function
%33 = OpVariable %12 Function
OpStore %33 %14
OpBranch %34
%34 = OpLabel
OpLoopMerge %36 %37 None
OpBranch %38
%38 = OpLabel
%39 = OpLoad %6 %33
%41 = OpSLessThan %23 %39 %40
OpBranchConditional %41 %35 %36
%35 = OpLabel
OpSelectionMerge %202 None
OpBranchConditional %48 %200 %201
%200 = OpLabel
OpBranch %202
%201 = OpLabel
%400 = OpCopyObject %6 %14
OpReturnValue %400
%202 = OpLabel
OpBranch %37
%37 = OpLabel
OpStore %13 %14
OpStore %15 %14
OpBranch %16
%16 = OpLabel
OpLoopMerge %18 %19 None
OpBranch %20
%20 = OpLabel
OpSelectionMerge %102 None
OpBranchConditional %48 %100 %101
%100 = OpLabel
OpBranch %102
%101 = OpLabel
OpBranch %102
%102 = OpLabel
%21 = OpLoad %6 %15
%24 = OpSLessThan %23 %21 %22
OpBranchConditional %24 %17 %18
%17 = OpLabel
OpSelectionMerge %302 None
OpBranchConditional %48 %300 %301
%300 = OpLabel
OpBranch %302
%301 = OpLabel
OpBranch %302
%302 = OpLabel
%25 = OpLoad %6 %13
%27 = OpIAdd %6 %25 %26
OpStore %13 %27
OpBranch %19
%19 = OpLabel
%28 = OpLoad %6 %15
%29 = OpIAdd %6 %28 %26
OpStore %15 %29
OpBranch %16
%18 = OpLabel
%30 = OpLoad %6 %13
%42 = OpCopyObject %6 %30
%43 = OpLoad %6 %33
%44 = OpIAdd %6 %43 %42
OpStore %33 %44
OpBranch %34
%36 = OpLabel
%45 = OpLoad %6 %33
OpReturnValue %45
OpFunctionEnd
)";
ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
}
TEST(TransformationReplaceBranchFromDeadBlockWithExitTest, OpPhi) {
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
%12 = OpTypeInt 32 1
%13 = OpTypePointer Function %12
%15 = OpConstant %12 1
%17 = OpConstant %12 2
%4 = OpFunction %2 None %3
%5 = OpLabel
%14 = OpVariable %13 Function
OpSelectionMerge %9 None
OpBranchConditional %7 %8 %21
%8 = OpLabel
OpSelectionMerge %11 None
OpBranchConditional %7 %10 %16
%10 = OpLabel
OpStore %14 %15
OpBranch %20
%20 = OpLabel
%48 = OpCopyObject %12 %15
OpBranch %11
%16 = OpLabel
OpStore %14 %17
%49 = OpCopyObject %12 %17
OpBranch %11
%11 = OpLabel
%50 = OpPhi %12 %48 %20 %49 %16
OpBranch %9
%21 = OpLabel
OpBranch %9
%9 = OpLabel
OpReturn
OpFunctionEnd
)";
const auto env = SPV_ENV_UNIVERSAL_1_4;
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);
transformation_context.GetFactManager()->AddFactBlockIsDead(8);
transformation_context.GetFactManager()->AddFactBlockIsDead(10);
transformation_context.GetFactManager()->AddFactBlockIsDead(11);
transformation_context.GetFactManager()->AddFactBlockIsDead(16);
transformation_context.GetFactManager()->AddFactBlockIsDead(20);
auto transformation1 =
TransformationReplaceBranchFromDeadBlockWithExit(20, SpvOpKill, 0);
auto transformation2 =
TransformationReplaceBranchFromDeadBlockWithExit(16, SpvOpKill, 0);
ASSERT_TRUE(
transformation1.IsApplicable(context.get(), transformation_context));
ASSERT_TRUE(
transformation2.IsApplicable(context.get(), transformation_context));
ApplyAndCheckFreshIds(transformation1, context.get(),
&transformation_context);
// Applying transformation 1 should disable transformation 2
ASSERT_FALSE(
transformation2.IsApplicable(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 320
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeBool
%7 = OpConstantFalse %6
%12 = OpTypeInt 32 1
%13 = OpTypePointer Function %12
%15 = OpConstant %12 1
%17 = OpConstant %12 2
%4 = OpFunction %2 None %3
%5 = OpLabel
%14 = OpVariable %13 Function
OpSelectionMerge %9 None
OpBranchConditional %7 %8 %21
%8 = OpLabel
OpSelectionMerge %11 None
OpBranchConditional %7 %10 %16
%10 = OpLabel
OpStore %14 %15
OpBranch %20
%20 = OpLabel
%48 = OpCopyObject %12 %15
OpKill
%16 = OpLabel
OpStore %14 %17
%49 = OpCopyObject %12 %17
OpBranch %11
%11 = OpLabel
%50 = OpPhi %12 %49 %16
OpBranch %9
%21 = OpLabel
OpBranch %9
%9 = OpLabel
OpReturn
OpFunctionEnd
)";
ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
}
} // namespace
} // namespace fuzz
} // namespace spvtools