spirv-fuzz: TransformationPropagateInstructionDown (#3692)

Fixes #3691.
This commit is contained in:
Vasyl Teliman 2020-10-06 15:38:19 +03:00 committed by GitHub
parent 65b2a9e814
commit 63cc22d645
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 2070 additions and 13 deletions

View File

@ -100,6 +100,7 @@ if(SPIRV_BUILD_FUZZER)
fuzzer_pass_permute_function_parameters.h
fuzzer_pass_permute_instructions.h
fuzzer_pass_permute_phi_operands.h
fuzzer_pass_propagate_instructions_down.h
fuzzer_pass_propagate_instructions_up.h
fuzzer_pass_push_ids_through_variables.h
fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.h
@ -190,6 +191,7 @@ if(SPIRV_BUILD_FUZZER)
transformation_outline_function.h
transformation_permute_function_parameters.h
transformation_permute_phi_operands.h
transformation_propagate_instruction_down.h
transformation_propagate_instruction_up.h
transformation_push_id_through_variable.h
transformation_record_synonymous_constants.h
@ -282,6 +284,7 @@ if(SPIRV_BUILD_FUZZER)
fuzzer_pass_permute_function_parameters.cpp
fuzzer_pass_permute_instructions.cpp
fuzzer_pass_permute_phi_operands.cpp
fuzzer_pass_propagate_instructions_down.cpp
fuzzer_pass_propagate_instructions_up.cpp
fuzzer_pass_push_ids_through_variables.cpp
fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.cpp
@ -370,6 +373,7 @@ if(SPIRV_BUILD_FUZZER)
transformation_outline_function.cpp
transformation_permute_function_parameters.cpp
transformation_permute_phi_operands.cpp
transformation_propagate_instruction_down.cpp
transformation_propagate_instruction_up.cpp
transformation_push_id_through_variable.cpp
transformation_record_synonymous_constants.cpp

View File

@ -68,6 +68,7 @@
#include "source/fuzz/fuzzer_pass_permute_function_parameters.h"
#include "source/fuzz/fuzzer_pass_permute_instructions.h"
#include "source/fuzz/fuzzer_pass_permute_phi_operands.h"
#include "source/fuzz/fuzzer_pass_propagate_instructions_down.h"
#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"
@ -276,6 +277,7 @@ Fuzzer::FuzzerResult Fuzzer::Run() {
MaybeAddRepeatedPass<FuzzerPassPermuteBlocks>(&pass_instances);
MaybeAddRepeatedPass<FuzzerPassPermuteFunctionParameters>(&pass_instances);
MaybeAddRepeatedPass<FuzzerPassPermuteInstructions>(&pass_instances);
MaybeAddRepeatedPass<FuzzerPassPropagateInstructionsDown>(&pass_instances);
MaybeAddRepeatedPass<FuzzerPassPropagateInstructionsUp>(&pass_instances);
MaybeAddRepeatedPass<FuzzerPassPushIdsThroughVariables>(&pass_instances);
MaybeAddRepeatedPass<FuzzerPassReplaceAddsSubsMulsWithCarryingExtended>(

View File

@ -107,6 +107,8 @@ const std::pair<uint32_t, uint32_t> kChanceOfOutliningFunction = {10, 90};
const std::pair<uint32_t, uint32_t> kChanceOfPermutingInstructions = {20, 70};
const std::pair<uint32_t, uint32_t> kChanceOfPermutingParameters = {30, 90};
const std::pair<uint32_t, uint32_t> kChanceOfPermutingPhiOperands = {30, 90};
const std::pair<uint32_t, uint32_t> kChanceOfPropagatingInstructionsDown = {20,
70};
const std::pair<uint32_t, uint32_t> kChanceOfPropagatingInstructionsUp = {20,
70};
const std::pair<uint32_t, uint32_t> kChanceOfPushingIdThroughVariable = {5, 50};
@ -294,6 +296,8 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator,
ChooseBetweenMinAndMax(kChanceOfPermutingParameters);
chance_of_permuting_phi_operands_ =
ChooseBetweenMinAndMax(kChanceOfPermutingPhiOperands);
chance_of_propagating_instructions_down_ =
ChooseBetweenMinAndMax(kChanceOfPropagatingInstructionsDown);
chance_of_propagating_instructions_up_ =
ChooseBetweenMinAndMax(kChanceOfPropagatingInstructionsUp);
chance_of_pushing_id_through_variable_ =

View File

@ -264,6 +264,9 @@ class FuzzerContext {
uint32_t GetChanceOfPermutingPhiOperands() {
return chance_of_permuting_phi_operands_;
}
uint32_t GetChanceOfPropagatingInstructionsDown() {
return chance_of_propagating_instructions_down_;
}
uint32_t GetChanceOfPropagatingInstructionsUp() {
return chance_of_propagating_instructions_up_;
}
@ -463,6 +466,7 @@ class FuzzerContext {
uint32_t chance_of_permuting_instructions_;
uint32_t chance_of_permuting_parameters_;
uint32_t chance_of_permuting_phi_operands_;
uint32_t chance_of_propagating_instructions_down_;
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_;

View File

@ -0,0 +1,68 @@
// 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_propagate_instructions_down.h"
#include "source/fuzz/fuzzer_context.h"
#include "source/fuzz/transformation_propagate_instruction_down.h"
namespace spvtools {
namespace fuzz {
FuzzerPassPropagateInstructionsDown::FuzzerPassPropagateInstructionsDown(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: FuzzerPass(ir_context, transformation_context, fuzzer_context,
transformations) {}
FuzzerPassPropagateInstructionsDown::~FuzzerPassPropagateInstructionsDown() =
default;
void FuzzerPassPropagateInstructionsDown::Apply() {
for (const auto& function : *GetIRContext()->module()) {
std::vector<const opt::BasicBlock*> reachable_blocks;
for (const auto& block : function) {
if (GetIRContext()->GetDominatorAnalysis(&function)->IsReachable(
&block)) {
reachable_blocks.push_back(&block);
}
}
for (const auto* block : reachable_blocks) {
if (!GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfPropagatingInstructionsDown())) {
continue;
}
if (TransformationPropagateInstructionDown::IsApplicableToBlock(
GetIRContext(), block->id())) {
// Record fresh ids for every successor of the |block| that we can
// propagate an instruction into.
std::map<uint32_t, uint32_t> fresh_ids;
for (auto id :
TransformationPropagateInstructionDown::GetAcceptableSuccessors(
GetIRContext(), block->id())) {
fresh_ids[id] = GetFuzzerContext()->GetFreshId();
}
ApplyTransformation(TransformationPropagateInstructionDown(
block->id(), GetFuzzerContext()->GetFreshId(), fresh_ids));
}
}
}
}
} // namespace fuzz
} // namespace spvtools

View File

@ -0,0 +1,39 @@
// 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_PROPAGATE_INSTRUCTIONS_DOWN_H_
#define SOURCE_FUZZ_FUZZER_PASS_PROPAGATE_INSTRUCTIONS_DOWN_H_
#include "source/fuzz/fuzzer_pass.h"
namespace spvtools {
namespace fuzz {
// Randomly propagates instructions from some block into the block's successors.
class FuzzerPassPropagateInstructionsDown : public FuzzerPass {
public:
FuzzerPassPropagateInstructionsDown(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations);
~FuzzerPassPropagateInstructionsDown() override;
void Apply() override;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FUZZER_PASS_PROPAGATE_INSTRUCTIONS_DOWN_H_

View File

@ -54,6 +54,7 @@
#include "source/fuzz/fuzzer_pass_permute_blocks.h"
#include "source/fuzz/fuzzer_pass_permute_function_parameters.h"
#include "source/fuzz/fuzzer_pass_permute_instructions.h"
#include "source/fuzz/fuzzer_pass_propagate_instructions_down.h"
#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"
@ -145,6 +146,7 @@ class RepeatedPassInstances {
REPEATED_PASS_INSTANCE(PermuteBlocks);
REPEATED_PASS_INSTANCE(PermuteFunctionParameters);
REPEATED_PASS_INSTANCE(PermuteInstructions);
REPEATED_PASS_INSTANCE(PropagateInstructionsDown);
REPEATED_PASS_INSTANCE(PropagateInstructionsUp);
REPEATED_PASS_INSTANCE(PushIdsThroughVariables);
REPEATED_PASS_INSTANCE(ReplaceAddsSubsMulsWithCarryingExtended);

View File

@ -255,6 +255,13 @@ RepeatedPassRecommenderStandard::GetFuturePassRecommendations(
// No obvious follow-on passes
return {};
}
if (&pass == pass_instances_->GetPropagateInstructionsDown()) {
// - This fuzzer pass might create new synonyms that can later be applied.
// - This fuzzer pass might create irrelevant ids that can later be
// replaced.
return RandomOrderAndNonNull({pass_instances_->GetApplyIdSynonyms(),
pass_instances_->GetReplaceIrrelevantIds()});
}
if (&pass == pass_instances_->GetPropagateInstructionsUp()) {
// No obvious follow-on passes
return {};

View File

@ -552,6 +552,7 @@ message Transformation {
TransformationAddLoopToCreateIntConstantSynonym add_loop_to_create_int_constant_synonym = 78;
TransformationWrapRegionInSelection wrap_region_in_selection = 79;
TransformationAddEarlyTerminatorWrapper add_early_terminator_wrapper = 80;
TransformationPropagateInstructionDown propagate_instruction_down = 81;
// Add additional option using the next available number.
}
}
@ -1745,6 +1746,38 @@ message TransformationPermutePhiOperands {
}
message TransformationPropagateInstructionDown {
// Propagates an instruction from |block_id| into its successors.
// Concretely, the transformation clones the propagated instruction
// into some of the successors of |block_id| and removes the original
// instruction. Additionally, an OpPhi instruction may be added to make sure
// that the transformation can be applied in various scenarios.
//
// Note that the instruction might not be propagated down into every successor
// of |block_id| since it might make the module invalid.
// Id of the block to propagate an instruction from. The decision on what
// instruction to propagate is made based on whether the instruction interacts
// with memory, whether that instruction is used in its block etc (see the
// transformation class for more details).
uint32 block_id = 1;
// A fresh id for an OpPhi instruction. This might not be used by the
// transformation since an OpPhi instruction is created only if needed
// (e.g. an instruction is propagated into divergent blocks).
uint32 phi_fresh_id = 2;
// A map from the id of some successor of the |block_id| to the fresh id.
// The map contains a fresh id for at least every successor of the |block_id|.
// Every fresh id in the map corresponds to the result id of the clone,
// propagated into the corresponding successor block. This transformation
// might use overflow ids if they are available and this field doesn't account
// for every successor of |block_id|.
repeated UInt32Pair successor_id_to_fresh_id = 3;
}
message TransformationPropagateInstructionUp {
// Propagates an instruction in the block into the block's predecessors.

View File

@ -70,6 +70,7 @@
#include "source/fuzz/transformation_outline_function.h"
#include "source/fuzz/transformation_permute_function_parameters.h"
#include "source/fuzz/transformation_permute_phi_operands.h"
#include "source/fuzz/transformation_propagate_instruction_down.h"
#include "source/fuzz/transformation_propagate_instruction_up.h"
#include "source/fuzz/transformation_push_id_through_variable.h"
#include "source/fuzz/transformation_record_synonymous_constants.h"
@ -259,6 +260,10 @@ std::unique_ptr<Transformation> Transformation::FromMessage(
case protobufs::Transformation::TransformationCase::kPermutePhiOperands:
return MakeUnique<TransformationPermutePhiOperands>(
message.permute_phi_operands());
case protobufs::Transformation::TransformationCase::
kPropagateInstructionDown:
return MakeUnique<TransformationPropagateInstructionDown>(
message.propagate_instruction_down());
case protobufs::Transformation::TransformationCase::kPropagateInstructionUp:
return MakeUnique<TransformationPropagateInstructionUp>(
message.propagate_instruction_up());

View File

@ -0,0 +1,592 @@
// 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_propagate_instruction_down.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/instruction_descriptor.h"
namespace spvtools {
namespace fuzz {
TransformationPropagateInstructionDown::TransformationPropagateInstructionDown(
const protobufs::TransformationPropagateInstructionDown& message)
: message_(message) {}
TransformationPropagateInstructionDown::TransformationPropagateInstructionDown(
uint32_t block_id, uint32_t phi_fresh_id,
const std::map<uint32_t, uint32_t>& successor_id_to_fresh_id) {
message_.set_block_id(block_id);
message_.set_phi_fresh_id(phi_fresh_id);
*message_.mutable_successor_id_to_fresh_id() =
fuzzerutil::MapToRepeatedUInt32Pair(successor_id_to_fresh_id);
}
bool TransformationPropagateInstructionDown::IsApplicable(
opt::IRContext* ir_context,
const TransformationContext& transformation_context) const {
// Check that we can apply this transformation to the |block_id|.
if (!IsApplicableToBlock(ir_context, message_.block_id())) {
return false;
}
const auto successor_id_to_fresh_id =
fuzzerutil::RepeatedUInt32PairToMap(message_.successor_id_to_fresh_id());
for (auto id : GetAcceptableSuccessors(ir_context, message_.block_id())) {
// Each successor must have a fresh id in the |successor_id_to_fresh_id|
// map, unless overflow ids are available.
if (!successor_id_to_fresh_id.count(id) &&
!transformation_context.GetOverflowIdSource()->HasOverflowIds()) {
return false;
}
}
std::vector<uint32_t> maybe_fresh_ids = {message_.phi_fresh_id()};
maybe_fresh_ids.reserve(successor_id_to_fresh_id.size());
for (const auto& entry : successor_id_to_fresh_id) {
maybe_fresh_ids.push_back(entry.second);
}
// All ids must be unique and fresh.
return !fuzzerutil::HasDuplicates(maybe_fresh_ids) &&
std::all_of(maybe_fresh_ids.begin(), maybe_fresh_ids.end(),
[ir_context](uint32_t id) {
return fuzzerutil::IsFreshId(ir_context, id);
});
}
void TransformationPropagateInstructionDown::Apply(
opt::IRContext* ir_context,
TransformationContext* transformation_context) const {
// Get instruction to propagate down. There must be one.
auto* inst_to_propagate =
GetInstructionToPropagate(ir_context, message_.block_id());
assert(inst_to_propagate && "There must be an instruction to propagate");
auto successor_id_to_fresh_id =
fuzzerutil::RepeatedUInt32PairToMap(message_.successor_id_to_fresh_id());
std::vector<uint32_t> created_inst_ids;
auto successor_ids = GetAcceptableSuccessors(ir_context, message_.block_id());
// Clone |inst_to_propagate| into every successor.
for (auto successor_id : successor_ids) {
std::unique_ptr<opt::Instruction> clone(
inst_to_propagate->Clone(ir_context));
uint32_t new_result_id;
if (successor_id_to_fresh_id.count(successor_id)) {
new_result_id = successor_id_to_fresh_id.at(successor_id);
} else {
assert(transformation_context->GetOverflowIdSource()->HasOverflowIds() &&
"Overflow ids must be available");
new_result_id =
transformation_context->GetOverflowIdSource()->GetNextOverflowId();
successor_id_to_fresh_id[successor_id] = new_result_id;
}
clone->SetResultId(new_result_id);
fuzzerutil::UpdateModuleIdBound(ir_context, new_result_id);
auto* insert_before_inst = GetFirstInsertBeforeInstruction(
ir_context, successor_id, clone->opcode());
assert(insert_before_inst && "Can't insert into one of the successors");
insert_before_inst->InsertBefore(std::move(clone));
created_inst_ids.push_back(new_result_id);
}
// Add an OpPhi instruction into the module if possible.
if (auto merge_block_id = GetOpPhiBlockId(
ir_context, message_.block_id(), *inst_to_propagate, successor_ids)) {
opt::Instruction::OperandList in_operands;
std::unordered_set<uint32_t> visited_predecessors;
for (auto predecessor_id : ir_context->cfg()->preds(merge_block_id)) {
if (visited_predecessors.count(predecessor_id)) {
// Merge block might have multiple identical predecessors.
continue;
}
visited_predecessors.insert(predecessor_id);
const auto* dominator_analysis = ir_context->GetDominatorAnalysis(
ir_context->cfg()->block(message_.block_id())->GetParent());
// Find the successor of |source_block| that dominates the predecessor of
// the merge block |predecessor_id|.
auto it = std::find_if(
successor_ids.begin(), successor_ids.end(),
[predecessor_id, dominator_analysis](uint32_t successor_id) {
return dominator_analysis->Dominates(successor_id, predecessor_id);
});
// OpPhi requires a single operand pair for every predecessor of the
// OpPhi's block.
assert(it != successor_ids.end() && "Unable to insert OpPhi");
in_operands.push_back(
{SPV_OPERAND_TYPE_ID, {successor_id_to_fresh_id.at(*it)}});
in_operands.push_back({SPV_OPERAND_TYPE_ID, {predecessor_id}});
}
ir_context->cfg()
->block(merge_block_id)
->begin()
->InsertBefore(MakeUnique<opt::Instruction>(
ir_context, SpvOpPhi, inst_to_propagate->type_id(),
message_.phi_fresh_id(), std::move(in_operands)));
fuzzerutil::UpdateModuleIdBound(ir_context, message_.phi_fresh_id());
created_inst_ids.push_back(message_.phi_fresh_id());
}
// Make sure analyses are updated when we adjust users of |inst_to_propagate|.
ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
// Copy decorations from the original instructions to its propagated copies.
for (auto id : created_inst_ids) {
ir_context->get_decoration_mgr()->CloneDecorations(
inst_to_propagate->result_id(), id);
}
// Remove all decorations from the original instruction.
ir_context->get_decoration_mgr()->RemoveDecorationsFrom(
inst_to_propagate->result_id());
// Update every use of the |inst_to_propagate| with a result id of some of the
// newly created instructions.
ir_context->get_def_use_mgr()->ForEachUse(
inst_to_propagate, [ir_context, &created_inst_ids](
opt::Instruction* user, uint32_t operand_index) {
assert(ir_context->get_instr_block(user) &&
"All decorations should have already been adjusted");
auto in_operand_index =
fuzzerutil::InOperandIndexFromOperandIndex(*user, operand_index);
for (auto id : created_inst_ids) {
if (fuzzerutil::IdIsAvailableAtUse(ir_context, user, in_operand_index,
id)) {
user->SetInOperand(in_operand_index, {id});
return;
}
}
// Every user of |inst_to_propagate| must be updated since we will
// remove that instruction from the module.
assert(false && "Every user of |inst_to_propagate| must be updated");
});
// Add synonyms about newly created instructions.
assert(inst_to_propagate->HasResultId() &&
"Result id is required to add facts");
if (transformation_context->GetFactManager()->IdIsIrrelevant(
inst_to_propagate->result_id())) {
for (auto id : created_inst_ids) {
transformation_context->GetFactManager()->AddFactIdIsIrrelevant(id);
}
} else {
std::vector<uint32_t> non_irrelevant_ids;
for (auto id : created_inst_ids) {
// |id| can be irrelevant implicitly (e.g. if we propagate it into a dead
// block).
if (!transformation_context->GetFactManager()->IdIsIrrelevant(id)) {
non_irrelevant_ids.push_back(id);
}
}
if (transformation_context->GetFactManager()->PointeeValueIsIrrelevant(
inst_to_propagate->result_id())) {
for (auto id : non_irrelevant_ids) {
transformation_context->GetFactManager()
->AddFactValueOfPointeeIsIrrelevant(id);
}
}
for (auto id : non_irrelevant_ids) {
transformation_context->GetFactManager()->AddFactDataSynonym(
MakeDataDescriptor(id, {}),
MakeDataDescriptor(non_irrelevant_ids[0], {}));
}
}
// Remove the propagated instruction from the module.
ir_context->KillInst(inst_to_propagate);
// We've adjusted all users - make sure these changes are analyzed.
ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
}
protobufs::Transformation TransformationPropagateInstructionDown::ToMessage()
const {
protobufs::Transformation result;
*result.mutable_propagate_instruction_down() = message_;
return result;
}
bool TransformationPropagateInstructionDown::IsOpcodeSupported(SpvOp opcode) {
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3605):
// We only support "simple" instructions that don't work with memory.
// We should extend this so that we support the ones that modify the memory
// too.
switch (opcode) {
case SpvOpUndef:
case SpvOpAccessChain:
case SpvOpInBoundsAccessChain:
case SpvOpArrayLength:
case SpvOpVectorExtractDynamic:
case SpvOpVectorInsertDynamic:
case SpvOpVectorShuffle:
case SpvOpCompositeConstruct:
case SpvOpCompositeExtract:
case SpvOpCompositeInsert:
case SpvOpCopyObject:
case SpvOpTranspose:
case SpvOpConvertFToU:
case SpvOpConvertFToS:
case SpvOpConvertSToF:
case SpvOpConvertUToF:
case SpvOpUConvert:
case SpvOpSConvert:
case SpvOpFConvert:
case SpvOpQuantizeToF16:
case SpvOpSatConvertSToU:
case SpvOpSatConvertUToS:
case SpvOpBitcast:
case SpvOpSNegate:
case SpvOpFNegate:
case SpvOpIAdd:
case SpvOpFAdd:
case SpvOpISub:
case SpvOpFSub:
case SpvOpIMul:
case SpvOpFMul:
case SpvOpUDiv:
case SpvOpSDiv:
case SpvOpFDiv:
case SpvOpUMod:
case SpvOpSRem:
case SpvOpSMod:
case SpvOpFRem:
case SpvOpFMod:
case SpvOpVectorTimesScalar:
case SpvOpMatrixTimesScalar:
case SpvOpVectorTimesMatrix:
case SpvOpMatrixTimesVector:
case SpvOpMatrixTimesMatrix:
case SpvOpOuterProduct:
case SpvOpDot:
case SpvOpIAddCarry:
case SpvOpISubBorrow:
case SpvOpUMulExtended:
case SpvOpSMulExtended:
case SpvOpAny:
case SpvOpAll:
case SpvOpIsNan:
case SpvOpIsInf:
case SpvOpIsFinite:
case SpvOpIsNormal:
case SpvOpSignBitSet:
case SpvOpLessOrGreater:
case SpvOpOrdered:
case SpvOpUnordered:
case SpvOpLogicalEqual:
case SpvOpLogicalNotEqual:
case SpvOpLogicalOr:
case SpvOpLogicalAnd:
case SpvOpLogicalNot:
case SpvOpSelect:
case SpvOpIEqual:
case SpvOpINotEqual:
case SpvOpUGreaterThan:
case SpvOpSGreaterThan:
case SpvOpUGreaterThanEqual:
case SpvOpSGreaterThanEqual:
case SpvOpULessThan:
case SpvOpSLessThan:
case SpvOpULessThanEqual:
case SpvOpSLessThanEqual:
case SpvOpFOrdEqual:
case SpvOpFUnordEqual:
case SpvOpFOrdNotEqual:
case SpvOpFUnordNotEqual:
case SpvOpFOrdLessThan:
case SpvOpFUnordLessThan:
case SpvOpFOrdGreaterThan:
case SpvOpFUnordGreaterThan:
case SpvOpFOrdLessThanEqual:
case SpvOpFUnordLessThanEqual:
case SpvOpFOrdGreaterThanEqual:
case SpvOpFUnordGreaterThanEqual:
case SpvOpShiftRightLogical:
case SpvOpShiftRightArithmetic:
case SpvOpShiftLeftLogical:
case SpvOpBitwiseOr:
case SpvOpBitwiseXor:
case SpvOpBitwiseAnd:
case SpvOpNot:
case SpvOpBitFieldInsert:
case SpvOpBitFieldSExtract:
case SpvOpBitFieldUExtract:
case SpvOpBitReverse:
case SpvOpBitCount:
case SpvOpCopyLogical:
case SpvOpPtrEqual:
case SpvOpPtrNotEqual:
return true;
default:
return false;
}
}
opt::Instruction*
TransformationPropagateInstructionDown::GetInstructionToPropagate(
opt::IRContext* ir_context, uint32_t block_id) {
auto* block = ir_context->cfg()->block(block_id);
assert(block && "|block_id| is invalid");
for (auto it = block->rbegin(); it != block->rend(); ++it) {
if (!it->result_id() || !it->type_id() ||
!IsOpcodeSupported(it->opcode())) {
continue;
}
auto all_users_from_different_blocks =
ir_context->get_def_use_mgr()->WhileEachUser(
&*it, [ir_context, block](opt::Instruction* user) {
return ir_context->get_instr_block(user) != block;
});
if (!all_users_from_different_blocks) {
// We can't propagate an instruction if it's used in the same block.
continue;
}
return &*it;
}
return nullptr;
}
bool TransformationPropagateInstructionDown::IsApplicableToBlock(
opt::IRContext* ir_context, uint32_t block_id) {
// Check that |block_id| is valid.
const auto* block = fuzzerutil::MaybeFindBlock(ir_context, block_id);
if (!block) {
return false;
}
const auto* dominator_analysis =
ir_context->GetDominatorAnalysis(block->GetParent());
// |block| must be reachable.
if (!dominator_analysis->IsReachable(block)) {
return false;
}
// The block must have an instruction to propagate.
const auto* inst_to_propagate =
GetInstructionToPropagate(ir_context, block_id);
if (!inst_to_propagate) {
return false;
}
// Check that |block| has successors.
auto successor_ids = GetAcceptableSuccessors(ir_context, block_id);
if (successor_ids.empty()) {
return false;
}
// Check that |successor_block| doesn't have any OpPhi instructions that
// use |inst|.
for (auto successor_id : successor_ids) {
for (const auto& maybe_phi_inst : *ir_context->cfg()->block(successor_id)) {
if (maybe_phi_inst.opcode() != SpvOpPhi) {
// OpPhis can be intermixed with OpLine and OpNoLine.
continue;
}
for (uint32_t i = 0; i < maybe_phi_inst.NumInOperands(); i += 2) {
if (maybe_phi_inst.GetSingleWordInOperand(i) ==
inst_to_propagate->result_id()) {
return false;
}
}
}
}
// Get the result id of the block we will insert OpPhi instruction into.
// This is either 0 or a result id of some merge block in the function.
auto phi_block_id =
GetOpPhiBlockId(ir_context, block_id, *inst_to_propagate, successor_ids);
// Make sure we can adjust all users of the propagated instruction.
return ir_context->get_def_use_mgr()->WhileEachUse(
inst_to_propagate,
[ir_context, &successor_ids, dominator_analysis, phi_block_id](
opt::Instruction* user, uint32_t index) {
const auto* user_block = ir_context->get_instr_block(user);
if (!user_block) {
// |user| might be a global instruction (e.g. OpDecorate).
return true;
}
// Check that at least one of the ids in |successor_ids| or a
// |phi_block_id| dominates |user|'s block (or its predecessor if the
// user is an OpPhi). We can't use fuzzerutil::IdIsAvailableAtUse since
// the id in question hasn't yet been created in the module.
auto block_id_to_dominate = user->opcode() == SpvOpPhi
? user->GetSingleWordOperand(index + 1)
: user_block->id();
if (phi_block_id != 0 &&
dominator_analysis->Dominates(phi_block_id, block_id_to_dominate)) {
return true;
}
return std::any_of(
successor_ids.begin(), successor_ids.end(),
[dominator_analysis, block_id_to_dominate](uint32_t id) {
return dominator_analysis->Dominates(id, block_id_to_dominate);
});
});
}
opt::Instruction*
TransformationPropagateInstructionDown::GetFirstInsertBeforeInstruction(
opt::IRContext* ir_context, uint32_t block_id, SpvOp opcode) {
auto* block = ir_context->cfg()->block(block_id);
auto it = block->begin();
while (it != block->end() &&
!fuzzerutil::CanInsertOpcodeBeforeInstruction(opcode, it)) {
++it;
}
return it == block->end() ? nullptr : &*it;
}
std::unordered_set<uint32_t>
TransformationPropagateInstructionDown::GetAcceptableSuccessors(
opt::IRContext* ir_context, uint32_t block_id) {
const auto* block = ir_context->cfg()->block(block_id);
assert(block && "|block_id| is invalid");
const auto* inst = GetInstructionToPropagate(ir_context, block_id);
assert(inst && "The block must have an instruction to propagate");
std::unordered_set<uint32_t> result;
block->ForEachSuccessorLabel([ir_context, &result,
inst](uint32_t successor_id) {
if (result.count(successor_id)) {
return;
}
auto* successor_block = ir_context->cfg()->block(successor_id);
// We can't propagate |inst| into |successor_block| if the latter is not
// dominated by the |inst|'s dependencies.
if (!inst->WhileEachInId([ir_context, successor_block](const uint32_t* id) {
return fuzzerutil::IdIsAvailableBeforeInstruction(
ir_context, &*successor_block->begin(), *id);
})) {
return;
}
// We don't propagate any "special" instructions (e.g. OpSelectionMerge
// etc), thus, insertion point must always exist if the module is valid.
assert(GetFirstInsertBeforeInstruction(ir_context, successor_id,
inst->opcode()) &&
"There must exist an insertion point.");
result.insert(successor_id);
});
return result;
}
uint32_t TransformationPropagateInstructionDown::GetOpPhiBlockId(
opt::IRContext* ir_context, uint32_t block_id,
const opt::Instruction& inst_to_propagate,
const std::unordered_set<uint32_t>& successor_ids) {
const auto* block = ir_context->cfg()->block(block_id);
// |block_id| must belong to some construct.
auto merge_block_id =
block->GetMergeInst()
? block->GetMergeInst()->GetSingleWordInOperand(0)
: ir_context->GetStructuredCFGAnalysis()->MergeBlock(block_id);
if (!merge_block_id) {
return 0;
}
const auto* dominator_analysis =
ir_context->GetDominatorAnalysis(block->GetParent());
// Check that |merge_block_id| is reachable in the CFG and |block_id|
// dominates |merge_block_id|.
if (!dominator_analysis->IsReachable(merge_block_id) ||
!dominator_analysis->Dominates(block_id, merge_block_id)) {
return 0;
}
// We can't insert an OpPhi into |merge_block_id| if it's an acceptable
// successor of |block_id|.
if (successor_ids.count(merge_block_id)) {
return 0;
}
// All predecessors of the merge block must be dominated by at least one
// successor of the |block_id|.
assert(!ir_context->cfg()->preds(merge_block_id).empty() &&
"Merge block must be reachable");
for (auto predecessor_id : ir_context->cfg()->preds(merge_block_id)) {
if (std::none_of(
successor_ids.begin(), successor_ids.end(),
[dominator_analysis, predecessor_id](uint32_t successor_id) {
return dominator_analysis->Dominates(successor_id,
predecessor_id);
})) {
return 0;
}
}
const auto* propagate_type =
ir_context->get_type_mgr()->GetType(inst_to_propagate.type_id());
assert(propagate_type && "|inst_to_propagate| must have a valid type");
// VariablePointers capability implicitly declares
// VariablePointersStorageBuffer. We need those capabilities since otherwise
// OpPhi instructions cannot have operands of pointer types.
if (propagate_type->AsPointer() &&
!ir_context->get_feature_mgr()->HasCapability(
SpvCapabilityVariablePointersStorageBuffer)) {
return 0;
}
return merge_block_id;
}
std::unordered_set<uint32_t>
TransformationPropagateInstructionDown::GetFreshIds() const {
std::unordered_set<uint32_t> result = {message_.phi_fresh_id()};
for (const auto& pair : message_.successor_id_to_fresh_id()) {
result.insert(pair.second());
}
return result;
}
} // namespace fuzz
} // namespace spvtools

View File

@ -0,0 +1,184 @@
// 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_PROPAGATE_INSTRUCTION_DOWN_H_
#define SOURCE_FUZZ_TRANSFORMATION_PROPAGATE_INSTRUCTION_DOWN_H_
#include <map>
#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 TransformationPropagateInstructionDown : public Transformation {
public:
explicit TransformationPropagateInstructionDown(
const protobufs::TransformationPropagateInstructionDown& message);
TransformationPropagateInstructionDown(
uint32_t block_id, uint32_t phi_fresh_id,
const std::map<uint32_t, uint32_t>& successor_id_to_fresh_id);
// - It should be possible to apply this transformation to |block_id| (see
// IsApplicableToBlock method).
// - Every acceptable successor of |block_id| (see GetAcceptableSuccessors
// method) must have an entry in the |successor_id_to_fresh_id| map unless
// overflow ids are available.
// - All values in |successor_id_to_fresh_id| and |phi_fresh_id| must be
// unique and fresh.
bool IsApplicable(
opt::IRContext* ir_context,
const TransformationContext& transformation_context) const override;
// - Adds a clone of the propagated instruction into every acceptable
// successor of |block_id|.
// - Removes the original instruction.
// - Creates an OpPhi instruction if possible, that tries to group created
// clones.
// - If the original instruction's id was irrelevant - marks created
// instructions as irrelevant. Otherwise, marks the created instructions as
// synonymous to each other if possible (i.e. skips instructions, copied
// into dead blocks).
void Apply(opt::IRContext* ir_context,
TransformationContext* transformation_context) const override;
protobufs::Transformation ToMessage() const override;
// Returns true if this transformation can be applied to the block with id
// |block_id|. Concretely, returns true iff:
// - |block_id| is a result id of some reachable basic block in the module.
// - the block has an instruction to propagate (see
// GetInstructionToPropagate method).
// - the block has at least one acceptable successor (see
// GetAcceptableSuccessors method).
// - none of the acceptable successors have OpPhi instructions that use the
// original instruction.
// - it is possible to replace every use of the original instruction with some
// of the propagated instructions (or an OpPhi if we can create it - see
// GetOpPhiBlockId method).
static bool IsApplicableToBlock(opt::IRContext* ir_context,
uint32_t block_id);
// Returns ids of successors of |block_id|, that can be used to propagate an
// instruction into. Concretely, a successor block is acceptable if all
// dependencies of the propagated instruction dominate it. Note that this
// implies that an acceptable successor must be reachable in the CFG.
// For example:
// %1 = OpLabel
// OpSelectionMerge %2 None
// OpBranchConditional %cond %2 %3
// %3 = OpLabel
// %4 = OpUndef %int
// %5 = OpCopyObject %int %4
// OpBranch %2
// %2 = OpLabel
// ...
// In this example, %2 is not an acceptable successor of %3 since one of the
// dependencies (%4) of the propagated instruction (%5) does not dominate it.
static std::unordered_set<uint32_t> GetAcceptableSuccessors(
opt::IRContext* ir_context, uint32_t block_id);
std::unordered_set<uint32_t> GetFreshIds() const override;
private:
// Returns the last possible instruction in the |block_id| that satisfies the
// following properties:
// - has result id
// - has type id
// - has supported opcode (see IsOpcodeSupported method)
// - has no users in its basic block.
// Returns nullptr if no such an instruction exists. For example:
// %1 = OpLabel
// %2 = OpUndef %int
// %3 = OpUndef %int
// OpStore %var %3
// OpBranch %some_block
// In this example:
// - We cannot propagate OpBranch nor OpStore since they both have unsupported
// opcodes and have neither result ids nor type ids.
// - We cannot propagate %3 either since it is used by OpStore.
// - We can propagate %2 since it satisfies all our conditions.
// The basic idea behind this method it to make sure that the returned
// instruction will not break domination rules in its original block when
// propagated.
static opt::Instruction* GetInstructionToPropagate(opt::IRContext* ir_context,
uint32_t block_id);
// Returns true if |opcode| is supported by this transformation.
static bool IsOpcodeSupported(SpvOp opcode);
// Returns the first instruction in the |block| that allows us to insert
// |opcode| above itself. Returns nullptr is no such instruction exists.
static opt::Instruction* GetFirstInsertBeforeInstruction(
opt::IRContext* ir_context, uint32_t block_id, SpvOp opcode);
// Returns a result id of a basic block, where an OpPhi instruction can be
// inserted. Returns nullptr if it's not possible to create an OpPhi. The
// created OpPhi instruction groups all the propagated clones of the original
// instruction. |block_id| is a result id of the block we propagate the
// instruction from. |successor_ids| contains result ids of the successors we
// propagate the instruction into. Concretely, returns a non-null value if:
// - |block_id| is in some construct.
// - The merge block of that construct is reachable.
// - |block_id| dominates that merge block.
// - That merge block may not be an acceptable successor of |block_id|.
// - There must be at least one |block_id|'s acceptable successor for every
// predecessor of the merge block, dominating that predecessor.
// - We can't create an OpPhi if the module has neither VariablePointers nor
// VariablePointersStorageBuffer capabilities.
// A simple example of when we can insert an OpPhi instruction is:
// - This snippet of code:
// %1 = OpLabel
// %2 = OpUndef %int
// OpSelectionMerge %5 None
// OpBranchConditional %cond %3 %4
// %3 = OpLabel
// OpBranch %5
// %4 = OpLabel
// OpBranch %5
// %5 = OpLabel
// ...
// will be transformed into the following one (if %2 is propagated):
// %1 = OpLabel
// OpSelectionMerge %5 None
// OpBranchConditional %cond %3 %4
// %3 = OpLabel
// %6 = OpUndef %int
// OpBranch %5
// %4 = OpLabel
// %7 = OpUndef %int
// OpBranch %5
// %5 = OpLabel
// %8 = OpPhi %int %6 %3 %7 %4
// ...
// The fact that we introduce an OpPhi allows us to increase the applicability
// of the transformation. Concretely, we wouldn't be able to apply it in the
// example above if %2 were used in %5. Some more complicated examples can be
// found in unit tests.
static uint32_t GetOpPhiBlockId(
opt::IRContext* ir_context, uint32_t block_id,
const opt::Instruction& inst_to_propagate,
const std::unordered_set<uint32_t>& successor_ids);
protobufs::TransformationPropagateInstructionDown message_;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_TRANSFORMATION_PROPAGATE_INSTRUCTION_DOWN_H_

View File

@ -87,6 +87,7 @@ if (${SPIRV_BUILD_FUZZER})
transformation_outline_function_test.cpp
transformation_permute_function_parameters_test.cpp
transformation_permute_phi_operands_test.cpp
transformation_propagate_instruction_down_test.cpp
transformation_propagate_instruction_up_test.cpp
transformation_push_id_through_variable_test.cpp
transformation_replace_add_sub_mul_with_carrying_extended_test.cpp

View File

@ -158,12 +158,5 @@ void ApplyAndCheckFreshIds(
}
}
void ApplyAndCheckFreshIds(const Transformation& transformation,
opt::IRContext* ir_context,
TransformationContext* transformation_context) {
ApplyAndCheckFreshIds(transformation, ir_context, transformation_context,
std::unordered_set<uint32_t>());
}
} // namespace fuzz
} // namespace spvtools

View File

@ -120,12 +120,7 @@ void DumpTransformationsJson(
void ApplyAndCheckFreshIds(
const Transformation& transformation, opt::IRContext* ir_context,
TransformationContext* transformation_context,
const std::unordered_set<uint32_t>& issued_overflow_ids);
// Invokes ApplyAndCheckFreshIds above, with an empty set of overflow ids.
void ApplyAndCheckFreshIds(const Transformation& transformation,
opt::IRContext* ir_context,
TransformationContext* transformation_context);
const std::unordered_set<uint32_t>& issued_overflow_ids = {{}});
} // namespace fuzz
} // namespace spvtools

File diff suppressed because it is too large Load Diff