spirv-fuzz: Add FuzzerPassAddCompositeExtract (#3904)

Fixes #3806.
This commit is contained in:
Vasyl Teliman 2020-10-23 16:49:50 +03:00 committed by GitHub
parent 69f07da41b
commit 895dafcc1d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 270 additions and 25 deletions

View File

@ -54,6 +54,7 @@ if(SPIRV_BUILD_FUZZER)
fuzzer_pass.h
fuzzer_pass_add_access_chains.h
fuzzer_pass_add_bit_instruction_synonyms.h
fuzzer_pass_add_composite_extract.h
fuzzer_pass_add_composite_inserts.h
fuzzer_pass_add_composite_types.h
fuzzer_pass_add_copy_memory.h
@ -241,6 +242,7 @@ if(SPIRV_BUILD_FUZZER)
fuzzer_pass.cpp
fuzzer_pass_add_access_chains.cpp
fuzzer_pass_add_bit_instruction_synonyms.cpp
fuzzer_pass_add_composite_extract.cpp
fuzzer_pass_add_composite_inserts.cpp
fuzzer_pass_add_composite_types.cpp
fuzzer_pass_add_copy_memory.cpp

View File

@ -909,6 +909,17 @@ DataSynonymAndIdEquationFacts::GetIdsForWhichSynonymsAreKnown() const {
return result;
}
std::vector<const protobufs::DataDescriptor*>
DataSynonymAndIdEquationFacts::GetAllKnownSynonyms() const {
std::vector<const protobufs::DataDescriptor*> result;
for (const auto* dd : synonymous_.GetAllKnownValues()) {
if (ObjectStillExists(*dd)) {
result.push_back(dd);
}
}
return result;
}
bool DataSynonymAndIdEquationFacts::IsSynonymous(
const protobufs::DataDescriptor& data_descriptor1,
const protobufs::DataDescriptor& data_descriptor2) const {

View File

@ -65,6 +65,9 @@ class DataSynonymAndIdEquationFacts {
// See method in FactManager which delegates to this method.
std::vector<uint32_t> GetIdsForWhichSynonymsAreKnown() const;
// See method in FactManager which delegates to this method.
std::vector<const protobufs::DataDescriptor*> GetAllKnownSynonyms() const;
// See method in FactManager which delegates to this method.
bool IsSynonymous(const protobufs::DataDescriptor& data_descriptor1,
const protobufs::DataDescriptor& data_descriptor2) const;

View File

@ -177,6 +177,11 @@ std::vector<uint32_t> FactManager::GetIdsForWhichSynonymsAreKnown() const {
return data_synonym_and_id_equation_facts_.GetIdsForWhichSynonymsAreKnown();
}
std::vector<const protobufs::DataDescriptor*> FactManager::GetAllSynonyms()
const {
return data_synonym_and_id_equation_facts_.GetAllKnownSynonyms();
}
std::vector<const protobufs::DataDescriptor*>
FactManager::GetSynonymsForDataDescriptor(
const protobufs::DataDescriptor& data_descriptor) const {

View File

@ -149,6 +149,10 @@ class FactManager {
// this piece of data" is known.
std::vector<uint32_t> GetIdsForWhichSynonymsAreKnown() const;
// Returns a vector of all data descriptors that participate in DataSynonym
// facts. All descriptors are guaranteed to exist in the |ir_context_|.
std::vector<const protobufs::DataDescriptor*> GetAllSynonyms() const;
// Returns the equivalence class of all known synonyms of |id|, or an empty
// set if no synonyms are known.
std::vector<const protobufs::DataDescriptor*> GetSynonymsForId(

View File

@ -23,6 +23,7 @@
#include "source/fuzz/fuzzer_context.h"
#include "source/fuzz/fuzzer_pass_add_access_chains.h"
#include "source/fuzz/fuzzer_pass_add_bit_instruction_synonyms.h"
#include "source/fuzz/fuzzer_pass_add_composite_extract.h"
#include "source/fuzz/fuzzer_pass_add_composite_inserts.h"
#include "source/fuzz/fuzzer_pass_add_composite_types.h"
#include "source/fuzz/fuzzer_pass_add_copy_memory.h"
@ -228,6 +229,7 @@ Fuzzer::FuzzerResult Fuzzer::Run() {
// if it is enabled.
MaybeAddRepeatedPass<FuzzerPassAddAccessChains>(&pass_instances);
MaybeAddRepeatedPass<FuzzerPassAddBitInstructionSynonyms>(&pass_instances);
MaybeAddRepeatedPass<FuzzerPassAddCompositeExtract>(&pass_instances);
MaybeAddRepeatedPass<FuzzerPassAddCompositeInserts>(&pass_instances);
MaybeAddRepeatedPass<FuzzerPassAddCompositeTypes>(&pass_instances);
MaybeAddRepeatedPass<FuzzerPassAddCopyMemory>(&pass_instances);

View File

@ -35,6 +35,7 @@ const std::pair<uint32_t, uint32_t> kChanceOfAddingBitInstructionSynonym = {5,
20};
const std::pair<uint32_t, uint32_t>
kChanceOfAddingBothBranchesWhenReplacingOpSelect = {40, 60};
const std::pair<uint32_t, uint32_t> kChanceOfAddingCompositeExtract = {20, 50};
const std::pair<uint32_t, uint32_t> kChanceOfAddingCompositeInsert = {20, 50};
const std::pair<uint32_t, uint32_t> kChanceOfAddingCopyMemory = {20, 50};
const std::pair<uint32_t, uint32_t> kChanceOfAddingDeadBlock = {20, 90};
@ -82,6 +83,8 @@ const std::pair<uint32_t, uint32_t> kChanceOfDuplicatingRegionWithSelection = {
20, 50};
const std::pair<uint32_t, uint32_t> kChanceOfFlatteningConditionalBranch = {45,
95};
const std::pair<uint32_t, uint32_t> kChanceOfGoingDeeperToExtractComposite = {
30, 70};
const std::pair<uint32_t, uint32_t> kChanceOfGoingDeeperToInsertInComposite = {
30, 70};
const std::pair<uint32_t, uint32_t> kChanceOfGoingDeeperWhenMakingAccessChain =
@ -197,6 +200,8 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator,
ChooseBetweenMinAndMax(kChanceOfAddingBitInstructionSynonym);
chance_of_adding_both_branches_when_replacing_opselect_ =
ChooseBetweenMinAndMax(kChanceOfAddingBothBranchesWhenReplacingOpSelect);
chance_of_adding_composite_extract_ =
ChooseBetweenMinAndMax(kChanceOfAddingCompositeExtract);
chance_of_adding_composite_insert_ =
ChooseBetweenMinAndMax(kChanceOfAddingCompositeInsert);
chance_of_adding_copy_memory_ =
@ -263,6 +268,8 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator,
ChooseBetweenMinAndMax(kChanceOfDuplicatingRegionWithSelection);
chance_of_flattening_conditional_branch_ =
ChooseBetweenMinAndMax(kChanceOfFlatteningConditionalBranch);
chance_of_going_deeper_to_extract_composite_ =
ChooseBetweenMinAndMax(kChanceOfGoingDeeperToExtractComposite);
chance_of_going_deeper_to_insert_in_composite_ =
ChooseBetweenMinAndMax(kChanceOfGoingDeeperToInsertInComposite);
chance_of_going_deeper_when_making_access_chain_ =

View File

@ -127,6 +127,9 @@ class FuzzerContext {
uint32_t GetChanceOfAddingBothBranchesWhenReplacingOpSelect() {
return chance_of_adding_both_branches_when_replacing_opselect_;
}
uint32_t GetChanceOfAddingCompositeExtract() {
return chance_of_adding_composite_extract_;
}
uint32_t GetChanceOfAddingCompositeInsert() {
return chance_of_adding_composite_insert_;
}
@ -216,6 +219,9 @@ class FuzzerContext {
uint32_t GetChanceOfFlatteningConditionalBranch() {
return chance_of_flattening_conditional_branch_;
}
uint32_t GetChanceOfGoingDeeperToExtractComposite() {
return chance_of_going_deeper_to_extract_composite_;
}
uint32_t GetChanceOfGoingDeeperToInsertInComposite() {
return chance_of_going_deeper_to_insert_in_composite_;
}
@ -354,6 +360,10 @@ class FuzzerContext {
return components;
}
uint32_t GetRandomCompositeExtractIndex(uint32_t number_of_members) {
assert(number_of_members > 0 && "Composite object must have some members");
return ChooseBetweenMinAndMax({0, number_of_members - 1});
}
uint32_t GetRandomIndexForAccessChain(uint32_t composite_size_bound) {
return random_generator_->RandomUint32(composite_size_bound);
}
@ -416,6 +426,7 @@ class FuzzerContext {
uint32_t chance_of_adding_array_or_struct_type_;
uint32_t chance_of_adding_bit_instruction_synonym_;
uint32_t chance_of_adding_both_branches_when_replacing_opselect_;
uint32_t chance_of_adding_composite_extract_;
uint32_t chance_of_adding_composite_insert_;
uint32_t chance_of_adding_copy_memory_;
uint32_t chance_of_adding_dead_block_;
@ -451,6 +462,7 @@ class FuzzerContext {
uint32_t chance_of_donating_additional_module_;
uint32_t chance_of_duplicating_region_with_selection_;
uint32_t chance_of_flattening_conditional_branch_;
uint32_t chance_of_going_deeper_to_extract_composite_;
uint32_t chance_of_going_deeper_to_insert_in_composite_;
uint32_t chance_of_going_deeper_when_making_access_chain_;
uint32_t chance_of_having_two_blocks_in_loop_to_create_int_synonym_;

View File

@ -0,0 +1,162 @@
// 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_add_composite_extract.h"
#include "source/fuzz/fuzzer_context.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/instruction_descriptor.h"
#include "source/fuzz/transformation_composite_extract.h"
namespace spvtools {
namespace fuzz {
FuzzerPassAddCompositeExtract::FuzzerPassAddCompositeExtract(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: FuzzerPass(ir_context, transformation_context, fuzzer_context,
transformations) {}
FuzzerPassAddCompositeExtract::~FuzzerPassAddCompositeExtract() = default;
void FuzzerPassAddCompositeExtract::Apply() {
std::vector<const protobufs::DataDescriptor*> composite_synonyms;
for (const auto* dd :
GetTransformationContext()->GetFactManager()->GetAllSynonyms()) {
// |dd| must describe a component of a composite.
if (!dd->index().empty()) {
composite_synonyms.push_back(dd);
}
}
// We don't want to invalidate the module every time we apply this
// transformation since rebuilding DominatorAnalysis can be expensive, so we
// collect up the transformations we wish to apply and apply them all later.
std::vector<TransformationCompositeExtract> transformations;
ForEachInstructionWithInstructionDescriptor(
[this, &composite_synonyms, &transformations](
opt::Function* function, opt::BasicBlock* block,
opt::BasicBlock::iterator inst_it,
const protobufs::InstructionDescriptor& instruction_descriptor) {
if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpCompositeExtract,
inst_it)) {
return;
}
if (!GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfAddingCompositeExtract())) {
return;
}
auto available_composites = FindAvailableInstructions(
function, block, inst_it,
[](opt::IRContext* ir_context, opt::Instruction* inst) {
return inst->type_id() && inst->result_id() &&
fuzzerutil::IsCompositeType(
ir_context->get_type_mgr()->GetType(inst->type_id()));
});
std::vector<const protobufs::DataDescriptor*> available_synonyms;
for (const auto* dd : composite_synonyms) {
if (fuzzerutil::IdIsAvailableBeforeInstruction(
GetIRContext(), &*inst_it, dd->object())) {
available_synonyms.push_back(dd);
}
}
if (available_synonyms.empty() && available_composites.empty()) {
return;
}
uint32_t composite_id = 0;
std::vector<uint32_t> indices;
if (available_synonyms.empty() || (!available_composites.empty() &&
GetFuzzerContext()->ChooseEven())) {
const auto* inst =
available_composites[GetFuzzerContext()->RandomIndex(
available_composites)];
composite_id = inst->result_id();
const auto* type =
GetIRContext()->get_type_mgr()->GetType(inst->type_id());
assert(type && "Composite instruction has invalid type id");
do {
uint32_t number_of_members = 0;
if (const auto* array_type = type->AsArray()) {
const auto* type_inst =
GetIRContext()->get_def_use_mgr()->GetDef(inst->type_id());
assert(type_inst && "Type instruction must exist");
number_of_members =
fuzzerutil::GetArraySize(*type_inst, GetIRContext());
type = array_type->element_type();
} else if (const auto* vector_type = type->AsVector()) {
number_of_members = vector_type->element_count();
type = vector_type->element_type();
} else if (const auto* matrix_type = type->AsMatrix()) {
number_of_members = matrix_type->element_count();
type = matrix_type->element_type();
} else if (const auto* struct_type = type->AsStruct()) {
number_of_members =
static_cast<uint32_t>(struct_type->element_types().size());
// The next value of |type| will be assigned when we know the
// index of the OpTypeStruct's operand.
} else {
assert(false && "|inst| is not a composite");
return;
}
if (number_of_members == 0) {
return;
}
indices.push_back(
GetFuzzerContext()->GetRandomCompositeExtractIndex(
number_of_members));
if (const auto* struct_type = type->AsStruct()) {
type = struct_type->element_types()[indices.back()];
}
} while (fuzzerutil::IsCompositeType(type) &&
GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()
->GetChanceOfGoingDeeperToExtractComposite()));
} else {
const auto* dd = available_synonyms[GetFuzzerContext()->RandomIndex(
available_synonyms)];
composite_id = dd->object();
indices.assign(dd->index().begin(), dd->index().end());
}
assert(composite_id != 0 && !indices.empty() &&
"Composite object should have been chosen correctly");
transformations.emplace_back(instruction_descriptor,
GetFuzzerContext()->GetFreshId(),
composite_id, indices);
});
for (const auto& transformation : transformations) {
ApplyTransformation(transformation);
}
}
} // namespace fuzz
} // namespace spvtools

View File

@ -0,0 +1,40 @@
// 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_ADD_COMPOSITE_EXTRACT_H_
#define SOURCE_FUZZ_FUZZER_PASS_ADD_COMPOSITE_EXTRACT_H_
#include "source/fuzz/fuzzer_pass.h"
namespace spvtools {
namespace fuzz {
// Randomly decides whether to add OpCompositeExtract before some instruction
// in the module.
class FuzzerPassAddCompositeExtract : public FuzzerPass {
public:
FuzzerPassAddCompositeExtract(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations);
~FuzzerPassAddCompositeExtract() override;
void Apply() override;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FUZZER_PASS_ADD_COMPOSITE_EXTRACT_H_

View File

@ -17,6 +17,7 @@
#include "source/fuzz/fuzzer_pass_add_access_chains.h"
#include "source/fuzz/fuzzer_pass_add_bit_instruction_synonyms.h"
#include "source/fuzz/fuzzer_pass_add_composite_extract.h"
#include "source/fuzz/fuzzer_pass_add_composite_inserts.h"
#include "source/fuzz/fuzzer_pass_add_composite_types.h"
#include "source/fuzz/fuzzer_pass_add_copy_memory.h"
@ -110,6 +111,7 @@ class RepeatedPassInstances {
REPEATED_PASS_INSTANCE(AddAccessChains);
REPEATED_PASS_INSTANCE(AddBitInstructionSynonyms);
REPEATED_PASS_INSTANCE(AddCompositeExtract);
REPEATED_PASS_INSTANCE(AddCompositeInserts);
REPEATED_PASS_INSTANCE(AddCompositeTypes);
REPEATED_PASS_INSTANCE(AddCopyMemory);

View File

@ -40,6 +40,10 @@ RepeatedPassRecommenderStandard::GetFuturePassRecommendations(
// - 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

View File

@ -55,19 +55,14 @@ bool TransformationCompositeExtract::IsApplicable(
if (!composite_instruction) {
return false;
}
if (auto block = ir_context->get_instr_block(composite_instruction)) {
if (composite_instruction == instruction_to_insert_before ||
!ir_context->GetDominatorAnalysis(block->GetParent())
->Dominates(composite_instruction, instruction_to_insert_before)) {
return false;
}
if (!fuzzerutil::IdIsAvailableBeforeInstruction(
ir_context, instruction_to_insert_before, message_.composite_id())) {
return false;
}
assert(composite_instruction->type_id() &&
"An instruction in a block cannot have a result id but no type id.");
auto composite_type =
ir_context->get_type_mgr()->GetType(composite_instruction->type_id());
if (!composite_type) {
if (!fuzzerutil::IsCompositeType(composite_type)) {
return false;
}
@ -133,19 +128,14 @@ void TransformationCompositeExtract::AddDataSynonymFacts(
// Add the fact that the id storing the extracted element is synonymous with
// the index into the structure.
if (!transformation_context->GetFactManager()->IdIsIrrelevant(
message_.composite_id())) {
std::vector<uint32_t> indices;
for (auto an_index : message_.index()) {
indices.push_back(an_index);
}
protobufs::DataDescriptor data_descriptor_for_extracted_element =
MakeDataDescriptor(message_.composite_id(), indices);
protobufs::DataDescriptor data_descriptor_for_result_id =
MakeDataDescriptor(message_.fresh_id(), {});
transformation_context->GetFactManager()->AddFactDataSynonym(
data_descriptor_for_extracted_element, data_descriptor_for_result_id);
}
std::vector<uint32_t> indices(message_.index().begin(),
message_.index().end());
auto data_descriptor_for_extracted_element =
MakeDataDescriptor(message_.composite_id(), indices);
auto data_descriptor_for_result_id =
MakeDataDescriptor(message_.fresh_id(), {});
transformation_context->GetFactManager()->AddFactDataSynonym(
data_descriptor_for_extracted_element, data_descriptor_for_result_id);
}
} // namespace fuzz

View File

@ -107,9 +107,10 @@ TEST(TransformationCompositeExtractTest, BasicTest) {
.IsApplicable(context.get(), transformation_context));
// Id for composite is not a composite.
ASSERT_FALSE(TransformationCompositeExtract(
MakeInstructionDescriptor(36, SpvOpIAdd, 0), 200, 27, {})
.IsApplicable(context.get(), transformation_context));
ASSERT_FALSE(
TransformationCompositeExtract(
MakeInstructionDescriptor(37, SpvOpAccessChain, 0), 200, 32, {})
.IsApplicable(context.get(), transformation_context));
// Composite does not dominate instruction being inserted before.
ASSERT_FALSE(