mirror of
https://github.com/KhronosGroup/SPIRV-Tools
synced 2024-10-18 11:10:05 +00:00
spirv-fuzz: Replace dead-block terminators with OpKill etc. (#3882)
Fixes #3615.
This commit is contained in:
parent
63cc22d645
commit
fc8264854c
@ -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
|
||||
|
@ -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>(
|
||||
|
@ -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_ =
|
||||
|
@ -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_;
|
||||
|
@ -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
|
@ -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_
|
@ -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);
|
||||
|
@ -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 {};
|
||||
|
@ -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
|
||||
|
@ -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>(
|
||||
|
@ -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
|
@ -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_
|
@ -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
|
||||
|
@ -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
|
Loading…
Reference in New Issue
Block a user