mirror of
https://github.com/KhronosGroup/SPIRV-Tools
synced 2024-10-20 20:10:05 +00:00
60ce96e2ff
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.
331 lines
14 KiB
C++
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
|