SPIRV-Tools/source/fuzz/pass_management/repeated_pass_recommender_standard.cpp
Alastair Donaldson 60ce96e2ff
spirv-fuzz: Add pass recommendations (#3757)
This change introduces various strategies for controlling the manner
in which fuzzer passes are applied repeatedly, including infrastructure
to allow fuzzer passes to be recommended based on which passes ran
previously.
2020-09-18 15:51:35 +01:00

331 lines
14 KiB
C++

// 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/pass_management/repeated_pass_recommender_standard.h"
#include <numeric>
namespace spvtools {
namespace fuzz {
RepeatedPassRecommenderStandard::RepeatedPassRecommenderStandard(
RepeatedPassInstances* pass_instances, FuzzerContext* fuzzer_context)
: pass_instances_(pass_instances), fuzzer_context_(fuzzer_context) {}
RepeatedPassRecommenderStandard::~RepeatedPassRecommenderStandard() = default;
std::vector<FuzzerPass*>
RepeatedPassRecommenderStandard::GetFuturePassRecommendations(
const FuzzerPass& pass) {
if (&pass == pass_instances_->GetAddAccessChains()) {
// - Adding access chains means there is more scope for loading and storing
// - It could be worth making more access chains from the recently-added
// access chains
return RandomOrderAndNonNull({pass_instances_->GetAddLoads(),
pass_instances_->GetAddStores(),
pass_instances_->GetAddAccessChains()});
}
if (&pass == pass_instances_->GetAddBitInstructionSynonyms()) {
// - Adding bit instruction synonyms creates opportunities to apply synonyms
return RandomOrderAndNonNull({pass_instances_->GetApplyIdSynonyms()});
}
if (&pass == pass_instances_->GetAddCompositeInserts()) {
// - Having added inserts we will have more vectors, so there is scope for
// vector shuffling
// - Adding inserts creates synonyms, which we should try to use
// - Vector inserts can be made dynamic
return RandomOrderAndNonNull(
{pass_instances_->GetAddVectorShuffleInstructions(),
pass_instances_->GetApplyIdSynonyms(),
pass_instances_->GetMakeVectorOperationsDynamic()});
}
if (&pass == pass_instances_->GetAddCompositeTypes()) {
// - More composite types gives more scope for constructing composites
return RandomOrderAndNonNull({pass_instances_->GetConstructComposites()});
}
if (&pass == pass_instances_->GetAddCopyMemory()) {
// - Recently-added copy memories could be replace with load-store pairs
return RandomOrderAndNonNull(
{pass_instances_->GetReplaceCopyMemoriesWithLoadsStores()});
}
if (&pass == pass_instances_->GetAddDeadBlocks()) {
// - 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()});
}
if (&pass == pass_instances_->GetAddDeadBreaks()) {
// - The guard of the dead break is a good candidate for obfuscation
return RandomOrderAndNonNull({pass_instances_->GetObfuscateConstants()});
}
if (&pass == pass_instances_->GetAddDeadContinues()) {
// - The guard of the dead continue is a good candidate for obfuscation
return RandomOrderAndNonNull({pass_instances_->GetObfuscateConstants()});
}
if (&pass == pass_instances_->GetAddEquationInstructions()) {
// - Equation instructions can create synonyms, which we can apply
// - Equation instructions collaborate with one another to make synonyms, so
// having added some it is worth adding more
return RandomOrderAndNonNull(
{pass_instances_->GetApplyIdSynonyms(),
pass_instances_->GetAddEquationInstructions()});
}
if (&pass == pass_instances_->GetAddFunctionCalls()) {
// - Called functions can be inlined
// - Irrelevant ids are created, so they can be replaced
return RandomOrderAndNonNull({pass_instances_->GetInlineFunctions(),
pass_instances_->GetReplaceIrrelevantIds()});
}
if (&pass == pass_instances_->GetAddGlobalVariables()) {
// - New globals provide new possibilities for making access chains
// - We can load from and store to new globals
return RandomOrderAndNonNull({pass_instances_->GetAddAccessChains(),
pass_instances_->GetAddLoads(),
pass_instances_->GetAddStores()});
}
if (&pass == pass_instances_->GetAddImageSampleUnusedComponents()) {
// - This introduces an unused component whose id is irrelevant and can be
// replaced
return RandomOrderAndNonNull({pass_instances_->GetReplaceIrrelevantIds()});
}
if (&pass == pass_instances_->GetAddLoads()) {
// - Loads might end up with corresponding stores, so that pairs can be
// replaced with memory copies
return RandomOrderAndNonNull(
{pass_instances_->GetReplaceLoadsStoresWithCopyMemories()});
}
if (&pass == pass_instances_->GetAddLocalVariables()) {
// - New locals provide new possibilities for making access chains
// - We can load from and store to new locals
return RandomOrderAndNonNull({pass_instances_->GetAddAccessChains(),
pass_instances_->GetAddLoads(),
pass_instances_->GetAddStores()});
}
if (&pass == pass_instances_->GetAddLoopPreheaders()) {
// - The loop preheader provides more scope for duplicating regions and
// outlining functions.
return RandomOrderAndNonNull(
{pass_instances_->GetDuplicateRegionsWithSelections(),
pass_instances_->GetOutlineFunctions()});
}
if (&pass == pass_instances_->GetAddOpPhiSynonyms()) {
// - New synonyms can be applied
// - If OpPhi synonyms are introduced for blocks with dead predecessors, the
// values consumed from dead predecessors can be replaced
return RandomOrderAndNonNull(
{pass_instances_->GetApplyIdSynonyms(),
pass_instances_->GetReplaceOpPhiIdsFromDeadPredecessors()});
}
if (&pass == pass_instances_->GetAddParameters()) {
// - We might be able to create interesting synonyms of new parameters.
// - This introduces irrelevant ids, which can be replaced
return RandomOrderAndNonNull({pass_instances_->GetAddSynonyms(),
pass_instances_->GetReplaceIrrelevantIds()});
}
if (&pass == pass_instances_->GetAddRelaxedDecorations()) {
// - No obvious follow-on passes
return {};
}
if (&pass == pass_instances_->GetAddStores()) {
// - Stores might end up with corresponding loads, so that pairs can be
// replaced with memory copies
return RandomOrderAndNonNull(
{pass_instances_->GetReplaceLoadsStoresWithCopyMemories()});
}
if (&pass == pass_instances_->GetAddSynonyms()) {
// - New synonyms can be applied
// - Synonym instructions use constants, which can be obfuscated
// - Synonym instructions use irrelevant ids, which can be replaced
// - Synonym instructions introduce addition/subtraction, which can be
// replaced with carrying/extended versions
return RandomOrderAndNonNull(
{pass_instances_->GetApplyIdSynonyms(),
pass_instances_->GetObfuscateConstants(),
pass_instances_->GetReplaceAddsSubsMulsWithCarryingExtended(),
pass_instances_->GetReplaceIrrelevantIds()});
}
if (&pass == pass_instances_->GetAddVectorShuffleInstructions()) {
// - Vector shuffles create synonyms that can be applied
// - TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3806) Extract
// from composites.
return RandomOrderAndNonNull({pass_instances_->GetApplyIdSynonyms()});
}
if (&pass == pass_instances_->GetApplyIdSynonyms()) {
// - No obvious follow-on passes
return {};
}
if (&pass == pass_instances_->GetConstructComposites()) {
// - TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3806): Extract
// from composites.
return RandomOrderAndNonNull({});
}
if (&pass == pass_instances_->GetCopyObjects()) {
// - Object copies create synonyms that can be applied
// - OpCopyObject can be replaced with a store/load pair
return RandomOrderAndNonNull(
{pass_instances_->GetApplyIdSynonyms(),
pass_instances_->GetReplaceCopyObjectsWithStoresLoads()});
}
if (&pass == pass_instances_->GetDonateModules()) {
// - New functions in the module can be called
// - Donated dead functions produce irrelevant ids, which can be replaced
return RandomOrderAndNonNull({pass_instances_->GetAddFunctionCalls(),
pass_instances_->GetReplaceIrrelevantIds()});
}
if (&pass == pass_instances_->GetDuplicateRegionsWithSelections()) {
// - Parts of duplicated regions can be outlined
return RandomOrderAndNonNull({pass_instances_->GetOutlineFunctions()});
}
if (&pass == pass_instances_->GetFlattenConditionalBranches()) {
// - Parts of flattened selections can be outlined
// - The flattening transformation introduces constants and irrelevant ids
// for enclosing hard-to-flatten operations; these can be obfuscated or
// replaced
return RandomOrderAndNonNull({pass_instances_->GetObfuscateConstants(),
pass_instances_->GetOutlineFunctions(),
pass_instances_->GetReplaceIrrelevantIds()});
}
if (&pass == pass_instances_->GetInlineFunctions()) {
// - Parts of inlined functions can be outlined again
return RandomOrderAndNonNull({pass_instances_->GetOutlineFunctions()});
}
if (&pass == pass_instances_->GetInvertComparisonOperators()) {
// - No obvious follow-on passes
return {};
}
if (&pass == pass_instances_->GetMakeVectorOperationsDynamic()) {
// - No obvious follow-on passes
return {};
}
if (&pass == pass_instances_->GetMergeBlocks()) {
// - Having merged some blocks it may be interesting to split them in a
// different way
return RandomOrderAndNonNull({pass_instances_->GetSplitBlocks()});
}
if (&pass == pass_instances_->GetMutatePointers()) {
// - This creates irrelevant ids, which can be replaced
return RandomOrderAndNonNull({pass_instances_->GetReplaceIrrelevantIds()});
}
if (&pass == pass_instances_->GetObfuscateConstants()) {
// - No obvious follow-on passes
return {};
}
if (&pass == pass_instances_->GetOutlineFunctions()) {
// - This creates more functions, which can be called
// - Inlining the function for the region that was outlined might also be
// fruitful; it will be inlined in a different form
return RandomOrderAndNonNull({pass_instances_->GetAddFunctionCalls(),
pass_instances_->GetInlineFunctions()});
}
if (&pass == pass_instances_->GetPermuteBlocks()) {
// No obvious follow-on passes
return {};
}
if (&pass == pass_instances_->GetPermuteFunctionParameters()) {
// No obvious follow-on passes
return {};
}
if (&pass == pass_instances_->GetPermuteInstructions()) {
// No obvious follow-on passes
return {};
}
if (&pass == pass_instances_->GetPropagateInstructionsUp()) {
// No obvious follow-on passes
return {};
}
if (&pass == pass_instances_->GetPushIdsThroughVariables()) {
// - This pass creates synonyms, so it is worth applying them
return RandomOrderAndNonNull({pass_instances_->GetApplyIdSynonyms()});
}
if (&pass == pass_instances_->GetReplaceAddsSubsMulsWithCarryingExtended()) {
// No obvious follow-on passes
return {};
}
if (&pass == pass_instances_->GetReplaceCopyMemoriesWithLoadsStores()) {
// No obvious follow-on passes
return {};
}
if (&pass == pass_instances_->GetReplaceCopyObjectsWithStoresLoads()) {
// - We may end up with load/store pairs that could be used to create memory
// copies
return RandomOrderAndNonNull(
{pass_instances_->GetReplaceLoadsStoresWithCopyMemories()});
}
if (&pass == pass_instances_->GetReplaceIrrelevantIds()) {
// No obvious follow-on passes
return {};
}
if (&pass == pass_instances_->GetReplaceLinearAlgebraInstructions()) {
// No obvious follow-on passes
return {};
}
if (&pass == pass_instances_->GetReplaceLoadsStoresWithCopyMemories()) {
// No obvious follow-on passes
return {};
}
if (&pass == pass_instances_->GetReplaceOpPhiIdsFromDeadPredecessors()) {
// No obvious follow-on passes
return {};
}
if (&pass == pass_instances_->GetReplaceOpSelectsWithConditionalBranches()) {
// No obvious follow-on passes
return {};
}
if (&pass == pass_instances_->GetReplaceParameterWithGlobal()) {
// No obvious follow-on passes
return {};
}
if (&pass == pass_instances_->GetReplaceParamsWithStruct()) {
// No obvious follow-on passes
return {};
}
if (&pass == pass_instances_->GetSplitBlocks()) {
// - More blocks means more chances for adding dead breaks/continues, and
// for adding dead blocks
return RandomOrderAndNonNull({pass_instances_->GetAddDeadBreaks(),
pass_instances_->GetAddDeadContinues(),
pass_instances_->GetAddDeadBlocks()});
}
if (&pass == pass_instances_->GetSwapBranchConditionalOperands()) {
// No obvious follow-on passes
return {};
}
assert(false && "Unreachable: every fuzzer pass should be dealt with.");
return {};
}
std::vector<FuzzerPass*> RepeatedPassRecommenderStandard::RandomOrderAndNonNull(
const std::vector<FuzzerPass*>& passes) {
std::vector<uint32_t> indices(passes.size());
std::iota(indices.begin(), indices.end(), 0);
std::vector<FuzzerPass*> result;
while (!indices.empty()) {
FuzzerPass* maybe_pass =
passes[fuzzer_context_->RemoveAtRandomIndex(&indices)];
if (maybe_pass != nullptr &&
fuzzer_context_->ChoosePercentage(
fuzzer_context_
->GetChanceOfAcceptingRepeatedPassRecommendation())) {
result.push_back(maybe_pass);
}
}
return result;
}
} // namespace fuzz
} // namespace spvtools