mirror of
https://github.com/KhronosGroup/SPIRV-Tools
synced 2024-12-01 23:40:04 +00:00
033768c24b
Adds a new transformation that rewrites a scalar operation (like OpFAdd, opISub) as an equivalent vector operation, adding a synonym between the scalar result and an appropriate component of the vector result. Fixes #4195.
384 lines
17 KiB
C++
384 lines
17 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_->GetAddCompositeExtract()) {
|
|
// - This transformation can introduce synonyms to the fact manager.
|
|
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
|
|
// - 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
|
|
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(),
|
|
pass_instances_->GetWrapRegionsInSelections()});
|
|
}
|
|
if (&pass == pass_instances_->GetAddLoopsToCreateIntConstantSynonyms()) {
|
|
// - New synonyms can be applied
|
|
return RandomOrderAndNonNull({pass_instances_->GetApplyIdSynonyms()});
|
|
}
|
|
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
|
|
// - Donated functions are good candidates for having their returns merged
|
|
// - 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
|
|
return RandomOrderAndNonNull({pass_instances_->GetOutlineFunctions()});
|
|
}
|
|
if (&pass == pass_instances_->GetExpandVectorReductions()) {
|
|
// - Adding OpAny and OpAll synonyms creates opportunities to apply synonyms
|
|
return RandomOrderAndNonNull({pass_instances_->GetApplyIdSynonyms()});
|
|
}
|
|
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_->GetMergeFunctionReturns()) {
|
|
// - Functions without early returns are more likely to be able to be
|
|
// inlined.
|
|
return RandomOrderAndNonNull({pass_instances_->GetInlineFunctions()});
|
|
}
|
|
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_->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 {};
|
|
}
|
|
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_->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 {};
|
|
}
|
|
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 {};
|
|
}
|
|
if (&pass == pass_instances_->GetWrapRegionsInSelections()) {
|
|
// - This pass uses an irrelevant boolean constant - we can replace it with
|
|
// something more interesting.
|
|
// - We can obfuscate that very constant as well.
|
|
// - We can flatten created selection construct.
|
|
return RandomOrderAndNonNull(
|
|
{pass_instances_->GetObfuscateConstants(),
|
|
pass_instances_->GetReplaceIrrelevantIds(),
|
|
pass_instances_->GetFlattenConditionalBranches()});
|
|
}
|
|
if (&pass == pass_instances_->GetWrapVectorSynonym()) {
|
|
// This transformation introduces synonym facts and irrelevant ids.
|
|
return RandomOrderAndNonNull({pass_instances_->GetApplyIdSynonyms(),
|
|
pass_instances_->GetReplaceIrrelevantIds()});
|
|
}
|
|
|
|
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
|