spirv-fuzz: Transformation to replace the use of an irrelevant id (#3697)

A transformation that replaces the use of an irrelevant id with
another id of the same type.

The related fuzzer pass, for every use of an irrelevant id,
checks whether the id can be replaced in that use by another
id of the same type and randomly decides whether to replace
it.

Fixes #3503.
This commit is contained in:
Stefano Milizia 2020-09-01 17:28:04 +02:00 committed by GitHub
parent d7f078f27d
commit 3daabd3212
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 770 additions and 111 deletions

View File

@ -91,6 +91,7 @@ if(SPIRV_BUILD_FUZZER)
fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.h
fuzzer_pass_replace_copy_memories_with_loads_stores.h
fuzzer_pass_replace_copy_objects_with_stores_loads.h
fuzzer_pass_replace_irrelevant_ids.h
fuzzer_pass_replace_linear_algebra_instructions.h
fuzzer_pass_replace_loads_stores_with_copy_memories.h
fuzzer_pass_replace_parameter_with_global.h
@ -168,6 +169,7 @@ if(SPIRV_BUILD_FUZZER)
transformation_replace_copy_memory_with_load_store.h
transformation_replace_copy_object_with_store_load.h
transformation_replace_id_with_synonym.h
transformation_replace_irrelevant_id.h
transformation_replace_linear_algebra_instruction.h
transformation_replace_load_store_with_copy_memory.h
transformation_replace_parameter_with_global.h
@ -246,6 +248,7 @@ if(SPIRV_BUILD_FUZZER)
fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.cpp
fuzzer_pass_replace_copy_memories_with_loads_stores.cpp
fuzzer_pass_replace_copy_objects_with_stores_loads.cpp
fuzzer_pass_replace_irrelevant_ids.cpp
fuzzer_pass_replace_linear_algebra_instructions.cpp
fuzzer_pass_replace_loads_stores_with_copy_memories.cpp
fuzzer_pass_replace_parameter_with_global.cpp
@ -322,6 +325,7 @@ if(SPIRV_BUILD_FUZZER)
transformation_replace_copy_memory_with_load_store.cpp
transformation_replace_copy_object_with_store_load.cpp
transformation_replace_id_with_synonym.cpp
transformation_replace_irrelevant_id.cpp
transformation_replace_linear_algebra_instruction.cpp
transformation_replace_load_store_with_copy_memory.cpp
transformation_replace_parameter_with_global.cpp

View File

@ -210,6 +210,10 @@ bool FactManager::IdIsIrrelevant(uint32_t result_id) const {
return irrelevant_value_facts_.IdIsIrrelevant(result_id);
}
const std::unordered_set<uint32_t>& FactManager::GetIrrelevantIds() const {
return irrelevant_value_facts_.GetIrrelevantIds();
}
void FactManager::AddFactValueOfPointeeIsIrrelevant(uint32_t pointer_id) {
protobufs::FactPointeeValueIsIrrelevant fact;
fact.set_pointer_id(pointer_id);

View File

@ -194,6 +194,10 @@ class FactManager {
// Returns true iff there exists a fact that the |result_id| is irrelevant.
bool IdIsIrrelevant(uint32_t result_id) const;
// Returns an unordered set of all the ids which have been declared
// irrelevant.
const std::unordered_set<uint32_t>& GetIrrelevantIds() const;
// End of irrelevant value facts
//==============================

View File

@ -46,6 +46,11 @@ bool IrrelevantValueFacts::IdIsIrrelevant(uint32_t pointer_id) const {
return irrelevant_ids_.count(pointer_id) != 0;
}
const std::unordered_set<uint32_t>& IrrelevantValueFacts::GetIrrelevantIds()
const {
return irrelevant_ids_;
}
} // namespace fact_manager
} // namespace fuzz
} // namespace spvtools

View File

@ -40,6 +40,9 @@ class IrrelevantValueFacts {
// See method in FactManager which delegates to this method.
bool IdIsIrrelevant(uint32_t pointer_id) const;
// See method in FactManager which delegates to this method.
const std::unordered_set<uint32_t>& GetIrrelevantIds() const;
private:
std::unordered_set<uint32_t> pointers_to_irrelevant_pointees_ids_;
std::unordered_set<uint32_t> irrelevant_ids_;

View File

@ -67,6 +67,7 @@
#include "source/fuzz/fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.h"
#include "source/fuzz/fuzzer_pass_replace_copy_memories_with_loads_stores.h"
#include "source/fuzz/fuzzer_pass_replace_copy_objects_with_stores_loads.h"
#include "source/fuzz/fuzzer_pass_replace_irrelevant_ids.h"
#include "source/fuzz/fuzzer_pass_replace_linear_algebra_instructions.h"
#include "source/fuzz/fuzzer_pass_replace_loads_stores_with_copy_memories.h"
#include "source/fuzz/fuzzer_pass_replace_parameter_with_global.h"
@ -193,6 +194,8 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run(
validator_options_);
// Apply some semantics-preserving passes.
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3764): Enable
// certain passes to run with a higher priority than the others.
std::vector<std::unique_ptr<FuzzerPass>> passes;
while (passes.empty()) {
MaybeAddPass<FuzzerPassAddAccessChains>(
@ -376,6 +379,9 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run(
MaybeAddPass<FuzzerPassPermutePhiOperands>(
&final_passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassReplaceIrrelevantIds>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassSwapCommutableOperands>(
&final_passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);

View File

@ -98,6 +98,7 @@ const std::pair<uint32_t, uint32_t> kChanceOfReplacingCopyMemoryWithLoadStore =
const std::pair<uint32_t, uint32_t> kChanceOfReplacingCopyObjectWithStoreLoad =
{20, 90};
const std::pair<uint32_t, uint32_t> kChanceOfReplacingIdWithSynonym = {10, 90};
const std::pair<uint32_t, uint32_t> kChanceOfReplacingIrrelevantId = {35, 95};
const std::pair<uint32_t, uint32_t>
kChanceOfReplacingLinearAlgebraInstructions = {10, 90};
const std::pair<uint32_t, uint32_t> kChanceOfReplacingLoadStoreWithCopyMemory =
@ -260,6 +261,8 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator,
ChooseBetweenMinAndMax(kChanceOfReplacingCopyObjectWithStoreLoad);
chance_of_replacing_id_with_synonym_ =
ChooseBetweenMinAndMax(kChanceOfReplacingIdWithSynonym);
chance_of_replacing_irrelevant_id_ =
ChooseBetweenMinAndMax(kChanceOfReplacingIrrelevantId);
chance_of_replacing_linear_algebra_instructions_ =
ChooseBetweenMinAndMax(kChanceOfReplacingLinearAlgebraInstructions);
chance_of_replacing_load_store_with_copy_memory_ =

View File

@ -252,6 +252,9 @@ class FuzzerContext {
uint32_t GetChanceOfReplacingIdWithSynonym() {
return chance_of_replacing_id_with_synonym_;
}
uint32_t GetChanceOfReplacingIrrelevantId() {
return chance_of_replacing_irrelevant_id_;
}
uint32_t GetChanceOfReplacingLinearAlgebraInstructions() {
return chance_of_replacing_linear_algebra_instructions_;
}
@ -411,6 +414,7 @@ class FuzzerContext {
uint32_t chance_of_replacing_copy_memory_with_load_store_;
uint32_t chance_of_replacing_copyobject_with_store_load_;
uint32_t chance_of_replacing_id_with_synonym_;
uint32_t chance_of_replacing_irrelevant_id_;
uint32_t chance_of_replacing_linear_algebra_instructions_;
uint32_t chance_of_replacing_load_store_with_copy_memory_;
uint32_t chance_of_replacing_parameters_with_globals_;

View File

@ -76,8 +76,8 @@ void FuzzerPassApplyIdSynonyms::Apply() {
// the index of the operand restricted to input operands only.
uint32_t use_in_operand_index =
fuzzerutil::InOperandIndexFromOperandIndex(*use_inst, use_index);
if (!TransformationReplaceIdWithSynonym::UseCanBeReplacedWithSynonym(
GetIRContext(), use_inst, use_in_operand_index)) {
if (!fuzzerutil::IdUseCanBeReplaced(GetIRContext(), use_inst,
use_in_operand_index)) {
continue;
}

View File

@ -0,0 +1,165 @@
// 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/fuzzer_pass_replace_irrelevant_ids.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/id_use_descriptor.h"
#include "source/fuzz/transformation_replace_irrelevant_id.h"
namespace spvtools {
namespace fuzz {
// A fuzzer pass that, for every use of an id that has been recorded as
// irrelevant, randomly decides whether to replace it with another id of the
// same type.
FuzzerPassReplaceIrrelevantIds::FuzzerPassReplaceIrrelevantIds(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: FuzzerPass(ir_context, transformation_context, fuzzer_context,
transformations) {}
FuzzerPassReplaceIrrelevantIds::~FuzzerPassReplaceIrrelevantIds() = default;
void FuzzerPassReplaceIrrelevantIds::Apply() {
// Keep track of the irrelevant ids. This includes all the ids that are
// irrelevant according to the fact manager and that are still present in the
// module (some of them may have been removed by previously-run
// transformations).
std::vector<uint32_t> irrelevant_ids;
// Keep a map from the type ids of irrelevant ids to all the ids with that
// type.
std::unordered_map<uint32_t, std::vector<uint32_t>> types_to_ids;
// Find all the irrelevant ids that still exist in the module and all the
// types for which irrelevant ids exist.
for (auto id :
GetTransformationContext()->GetFactManager()->GetIrrelevantIds()) {
// Check that the id still exists in the module.
auto declaration = GetIRContext()->get_def_use_mgr()->GetDef(id);
if (!declaration) {
continue;
}
irrelevant_ids.push_back(id);
// If the type of this id has not been seen before, add a mapping from this
// type id to an empty list in |types_to_ids|. The list will be filled later
// on.
if (types_to_ids.count(declaration->type_id()) == 0) {
types_to_ids.insert({declaration->type_id(), {}});
}
}
// If no irrelevant ids were found, return.
if (irrelevant_ids.empty()) {
return;
}
// For every type for which we have at least one irrelevant id, record all ids
// in the module which have that type.
for (const auto& pair : GetIRContext()->get_def_use_mgr()->id_to_defs()) {
uint32_t type_id = pair.second->type_id();
if (type_id && types_to_ids.count(type_id)) {
types_to_ids[type_id].push_back(pair.first);
}
}
// Keep a list of all the transformations to perform. We avoid applying the
// transformations while traversing the uses since applying the transformation
// invalidates all analyses, and we want to avoid invalidating and recomputing
// them every time.
std::vector<TransformationReplaceIrrelevantId> transformations_to_apply;
// Loop through all the uses of irrelevant ids, check that the id can be
// replaced and randomly decide whether to apply the transformation.
for (auto irrelevant_id : irrelevant_ids) {
uint32_t type_id =
GetIRContext()->get_def_use_mgr()->GetDef(irrelevant_id)->type_id();
GetIRContext()->get_def_use_mgr()->ForEachUse(
irrelevant_id, [this, &irrelevant_id, &type_id, &types_to_ids,
&transformations_to_apply](opt::Instruction* use_inst,
uint32_t use_index) {
// Randomly decide whether to consider this use.
if (!GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfReplacingIrrelevantId())) {
return;
}
// The id must be used as an input operand.
if (use_index < use_inst->NumOperands() - use_inst->NumInOperands()) {
// The id is used as an output operand, so we cannot replace this
// usage.
return;
}
// Get the input operand index for this use, from the absolute operand
// index.
uint32_t in_index =
fuzzerutil::InOperandIndexFromOperandIndex(*use_inst, use_index);
// Only go ahead if this id use can be replaced in principle.
if (!fuzzerutil::IdUseCanBeReplaced(GetIRContext(), use_inst,
in_index)) {
return;
}
// Find out which ids could be used to replace this use.
std::vector<uint32_t> available_replacement_ids;
for (auto replacement_id : types_to_ids[type_id]) {
// We cannot replace an id with itself.
if (replacement_id == irrelevant_id) {
continue;
}
// Only consider this replacement if it is available at the id use
// point.
if (fuzzerutil::IdIsAvailableAtUse(GetIRContext(), use_inst,
in_index, replacement_id)) {
available_replacement_ids.push_back(replacement_id);
}
}
// Only go ahead if there is at least one id with which this use can
// be replaced.
if (available_replacement_ids.empty()) {
return;
}
// Choose the replacement id randomly.
uint32_t replacement_id =
available_replacement_ids[GetFuzzerContext()->RandomIndex(
available_replacement_ids)];
// Add this replacement to the list of transformations to apply.
transformations_to_apply.emplace_back(
TransformationReplaceIrrelevantId(
MakeIdUseDescriptorFromUse(GetIRContext(), use_inst,
in_index),
replacement_id));
});
}
// Apply all the transformations.
for (const auto& transformation : transformations_to_apply) {
ApplyTransformation(transformation);
}
}
} // namespace fuzz
} // namespace spvtools

View File

@ -0,0 +1,39 @@
// 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.
#ifndef SOURCE_FUZZ_FUZZER_PASS_REPLACE_IRRELEVANT_IDS_
#define SOURCE_FUZZ_FUZZER_PASS_REPLACE_IRRELEVANT_IDS_
#include "source/fuzz/fuzzer_pass.h"
namespace spvtools {
namespace fuzz {
// A fuzzer pass that, for every use of an irrelevant id, checks if it is
// possible to replace it with other ids of the same type and randomly decides
// whether to do it.
class FuzzerPassReplaceIrrelevantIds : public FuzzerPass {
public:
FuzzerPassReplaceIrrelevantIds(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations);
~FuzzerPassReplaceIrrelevantIds();
void Apply() override;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FUZZER_PASS_REPLACE_IRRELEVANT_IDS_

View File

@ -1361,6 +1361,97 @@ opt::Instruction* GetLastInsertBeforeInstruction(opt::IRContext* ir_context,
return CanInsertOpcodeBeforeInstruction(opcode, &*it) ? &*it : nullptr;
}
bool IdUseCanBeReplaced(opt::IRContext* ir_context,
opt::Instruction* use_instruction,
uint32_t use_in_operand_index) {
if (spvOpcodeIsAccessChain(use_instruction->opcode()) &&
use_in_operand_index > 0) {
// This is an access chain index. If the (sub-)object being accessed by the
// given index has struct type then we cannot replace the use, as it needs
// to be an OpConstant.
// Get the top-level composite type that is being accessed.
auto object_being_accessed = ir_context->get_def_use_mgr()->GetDef(
use_instruction->GetSingleWordInOperand(0));
auto pointer_type =
ir_context->get_type_mgr()->GetType(object_being_accessed->type_id());
assert(pointer_type->AsPointer());
auto composite_type_being_accessed =
pointer_type->AsPointer()->pointee_type();
// Now walk the access chain, tracking the type of each sub-object of the
// composite that is traversed, until the index of interest is reached.
for (uint32_t index_in_operand = 1; index_in_operand < use_in_operand_index;
index_in_operand++) {
// For vectors, matrices and arrays, getting the type of the sub-object is
// trivial. For the struct case, the sub-object type is field-sensitive,
// and depends on the constant index that is used.
if (composite_type_being_accessed->AsVector()) {
composite_type_being_accessed =
composite_type_being_accessed->AsVector()->element_type();
} else if (composite_type_being_accessed->AsMatrix()) {
composite_type_being_accessed =
composite_type_being_accessed->AsMatrix()->element_type();
} else if (composite_type_being_accessed->AsArray()) {
composite_type_being_accessed =
composite_type_being_accessed->AsArray()->element_type();
} else if (composite_type_being_accessed->AsRuntimeArray()) {
composite_type_being_accessed =
composite_type_being_accessed->AsRuntimeArray()->element_type();
} else {
assert(composite_type_being_accessed->AsStruct());
auto constant_index_instruction = ir_context->get_def_use_mgr()->GetDef(
use_instruction->GetSingleWordInOperand(index_in_operand));
assert(constant_index_instruction->opcode() == SpvOpConstant);
uint32_t member_index =
constant_index_instruction->GetSingleWordInOperand(0);
composite_type_being_accessed =
composite_type_being_accessed->AsStruct()
->element_types()[member_index];
}
}
// We have found the composite type being accessed by the index we are
// considering replacing. If it is a struct, then we cannot do the
// replacement as struct indices must be constants.
if (composite_type_being_accessed->AsStruct()) {
return false;
}
}
if (use_instruction->opcode() == SpvOpFunctionCall &&
use_in_operand_index > 0) {
// This is a function call argument. It is not allowed to have pointer
// type.
// Get the definition of the function being called.
auto function = ir_context->get_def_use_mgr()->GetDef(
use_instruction->GetSingleWordInOperand(0));
// From the function definition, get the function type.
auto function_type = ir_context->get_def_use_mgr()->GetDef(
function->GetSingleWordInOperand(1));
// OpTypeFunction's 0-th input operand is the function return type, and the
// function argument types follow. Because the arguments to OpFunctionCall
// start from input operand 1, we can use |use_in_operand_index| to get the
// type associated with this function argument.
auto parameter_type = ir_context->get_type_mgr()->GetType(
function_type->GetSingleWordInOperand(use_in_operand_index));
if (parameter_type->AsPointer()) {
return false;
}
}
if (use_instruction->opcode() == SpvOpImageTexelPointer &&
use_in_operand_index == 2) {
// The OpImageTexelPointer instruction has a Sample parameter that in some
// situations must be an id for the value 0. To guard against disrupting
// that requirement, we do not replace this argument to that instruction.
return false;
}
return true;
}
} // namespace fuzzerutil
} // namespace fuzz
} // namespace spvtools

View File

@ -502,6 +502,20 @@ opt::Instruction* GetLastInsertBeforeInstruction(opt::IRContext* ir_context,
uint32_t block_id,
SpvOp opcode);
// Checks whether various conditions hold related to the acceptability of
// replacing the id use at |use_in_operand_index| of |use_instruction| with a
// synonym or another id of appropriate type if the original id is irrelevant.
// In particular, this checks that:
// - the id use is not an index into a struct field in an OpAccessChain - such
// indices must be constants, so it is dangerous to replace them.
// - the id use is not a pointer function call argument, on which there are
// restrictions that make replacement problematic.
// - the id use is not the Sample parameter of an OpImageTexelPointer
// instruction, as this must satisfy particular requirements.
bool IdUseCanBeReplaced(opt::IRContext* ir_context,
opt::Instruction* use_instruction,
uint32_t use_in_operand_index);
} // namespace fuzzerutil
} // namespace fuzz
} // namespace spvtools

View File

@ -416,6 +416,7 @@ message Transformation {
TransformationInlineFunction inline_function = 69;
TransformationAddOpPhiSynonym add_opphi_synonym = 70;
TransformationMutatePointer mutate_pointer = 71;
TransformationReplaceIrrelevantId replace_irrelevant_id = 72;
// Add additional option using the next available number.
}
}
@ -1506,6 +1507,17 @@ message TransformationReplaceIdWithSynonym {
}
message TransformationReplaceIrrelevantId {
// Replaces an irrelevant id with another id of the same type.
// The id use that is to be replaced
IdUseDescriptor id_use_descriptor = 1;
// The replacement id
uint32 replacement_id = 2;
}
message TransformationReplaceLinearAlgebraInstruction {
// Replaces a linear algebra instruction with its

View File

@ -74,6 +74,7 @@
#include "source/fuzz/transformation_replace_copy_memory_with_load_store.h"
#include "source/fuzz/transformation_replace_copy_object_with_store_load.h"
#include "source/fuzz/transformation_replace_id_with_synonym.h"
#include "source/fuzz/transformation_replace_irrelevant_id.h"
#include "source/fuzz/transformation_replace_linear_algebra_instruction.h"
#include "source/fuzz/transformation_replace_load_store_with_copy_memory.h"
#include "source/fuzz/transformation_replace_parameter_with_global.h"
@ -263,6 +264,9 @@ std::unique_ptr<Transformation> Transformation::FromMessage(
case protobufs::Transformation::TransformationCase::kReplaceIdWithSynonym:
return MakeUnique<TransformationReplaceIdWithSynonym>(
message.replace_id_with_synonym());
case protobufs::Transformation::TransformationCase::kReplaceIrrelevantId:
return MakeUnique<TransformationReplaceIrrelevantId>(
message.replace_irrelevant_id());
case protobufs::Transformation::TransformationCase::
kReplaceLinearAlgebraInstruction:
return MakeUnique<TransformationReplaceLinearAlgebraInstruction>(

View File

@ -73,7 +73,7 @@ bool TransformationReplaceIdWithSynonym::IsApplicable(
}
// Is the use suitable for being replaced in principle?
if (!UseCanBeReplacedWithSynonym(
if (!fuzzerutil::IdUseCanBeReplaced(
ir_context, use_instruction,
message_.id_use_descriptor().in_operand_index())) {
return false;
@ -106,97 +106,6 @@ protobufs::Transformation TransformationReplaceIdWithSynonym::ToMessage()
return result;
}
bool TransformationReplaceIdWithSynonym::UseCanBeReplacedWithSynonym(
opt::IRContext* ir_context, opt::Instruction* use_instruction,
uint32_t use_in_operand_index) {
if (spvOpcodeIsAccessChain(use_instruction->opcode()) &&
use_in_operand_index > 0) {
// This is an access chain index. If the (sub-)object being accessed by the
// given index has struct type then we cannot replace the use with a
// synonym, as the use needs to be an OpConstant.
// Get the top-level composite type that is being accessed.
auto object_being_accessed = ir_context->get_def_use_mgr()->GetDef(
use_instruction->GetSingleWordInOperand(0));
auto pointer_type =
ir_context->get_type_mgr()->GetType(object_being_accessed->type_id());
assert(pointer_type->AsPointer());
auto composite_type_being_accessed =
pointer_type->AsPointer()->pointee_type();
// Now walk the access chain, tracking the type of each sub-object of the
// composite that is traversed, until the index of interest is reached.
for (uint32_t index_in_operand = 1; index_in_operand < use_in_operand_index;
index_in_operand++) {
// For vectors, matrices and arrays, getting the type of the sub-object is
// trivial. For the struct case, the sub-object type is field-sensitive,
// and depends on the constant index that is used.
if (composite_type_being_accessed->AsVector()) {
composite_type_being_accessed =
composite_type_being_accessed->AsVector()->element_type();
} else if (composite_type_being_accessed->AsMatrix()) {
composite_type_being_accessed =
composite_type_being_accessed->AsMatrix()->element_type();
} else if (composite_type_being_accessed->AsArray()) {
composite_type_being_accessed =
composite_type_being_accessed->AsArray()->element_type();
} else if (composite_type_being_accessed->AsRuntimeArray()) {
composite_type_being_accessed =
composite_type_being_accessed->AsRuntimeArray()->element_type();
} else {
assert(composite_type_being_accessed->AsStruct());
auto constant_index_instruction = ir_context->get_def_use_mgr()->GetDef(
use_instruction->GetSingleWordInOperand(index_in_operand));
assert(constant_index_instruction->opcode() == SpvOpConstant);
uint32_t member_index =
constant_index_instruction->GetSingleWordInOperand(0);
composite_type_being_accessed =
composite_type_being_accessed->AsStruct()
->element_types()[member_index];
}
}
// We have found the composite type being accessed by the index we are
// considering replacing. If it is a struct, then we cannot do the
// replacement as struct indices must be constants.
if (composite_type_being_accessed->AsStruct()) {
return false;
}
}
if (use_instruction->opcode() == SpvOpFunctionCall &&
use_in_operand_index > 0) {
// This is a function call argument. It is not allowed to have pointer
// type.
// Get the definition of the function being called.
auto function = ir_context->get_def_use_mgr()->GetDef(
use_instruction->GetSingleWordInOperand(0));
// From the function definition, get the function type.
auto function_type = ir_context->get_def_use_mgr()->GetDef(
function->GetSingleWordInOperand(1));
// OpTypeFunction's 0-th input operand is the function return type, and the
// function argument types follow. Because the arguments to OpFunctionCall
// start from input operand 1, we can use |use_in_operand_index| to get the
// type associated with this function argument.
auto parameter_type = ir_context->get_type_mgr()->GetType(
function_type->GetSingleWordInOperand(use_in_operand_index));
if (parameter_type->AsPointer()) {
return false;
}
}
if (use_instruction->opcode() == SpvOpImageTexelPointer &&
use_in_operand_index == 2) {
// The OpImageTexelPointer instruction has a Sample parameter that in some
// situations must be an id for the value 0. To guard against disrupting
// that requirement, we do not replace this argument to that instruction.
return false;
}
return true;
}
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3582): Add all
// opcodes that are agnostic to signedness of operands to function.
// This is not exhaustive yet.

View File

@ -32,15 +32,12 @@ class TransformationReplaceIdWithSynonym : public Transformation {
protobufs::IdUseDescriptor id_use_descriptor, uint32_t synonymous_id);
// - The fact manager must know that the id identified by
// |message_.id_use_descriptor| is synonomous with
// |message_.synonymous_id|.
// |message_.id_use_descriptor| is synonomous with |message_.synonymous_id|.
// - Replacing the id in |message_.id_use_descriptor| by
// |message_.synonymous_id| must respect SPIR-V's rules about uses being
// dominated by their definitions.
// - The id must not be an index into an access chain whose base object has
// struct type, as such indices must be constants.
// - The id must not be a pointer argument to a function call (because the
// synonym might not be a memory object declaration).
// - The id use must be replaceable in principle. See
// fuzzerutil::IdUseCanBeReplaced for details.
// - |fresh_id_for_temporary| must be 0.
bool IsApplicable(
opt::IRContext* ir_context,
@ -53,17 +50,6 @@ class TransformationReplaceIdWithSynonym : public Transformation {
protobufs::Transformation ToMessage() const override;
// Checks whether various conditions hold related to the acceptability of
// replacing the id use at |use_in_operand_index| of |use_instruction| with
// a synonym. In particular, this checks that:
// - the id use is not an index into a struct field in an OpAccessChain - such
// indices must be constants, so it is dangerous to replace them.
// - the id use is not a pointer function call argument, on which there are
// restrictions that make replacement problematic.
static bool UseCanBeReplacedWithSynonym(opt::IRContext* ir_context,
opt::Instruction* use_instruction,
uint32_t use_in_operand_index);
// Returns true if |type_id_1| and |type_id_2| represent compatible types
// given the context of the instruction with |opcode| (i.e. we can replace
// an operand of |opcode| of the first type with an id of the second type

View File

@ -0,0 +1,113 @@
// 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/transformation_replace_irrelevant_id.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/id_use_descriptor.h"
namespace spvtools {
namespace fuzz {
TransformationReplaceIrrelevantId::TransformationReplaceIrrelevantId(
const protobufs::TransformationReplaceIrrelevantId& message)
: message_(message) {}
TransformationReplaceIrrelevantId::TransformationReplaceIrrelevantId(
const protobufs::IdUseDescriptor& id_use_descriptor,
uint32_t replacement_id) {
*message_.mutable_id_use_descriptor() = id_use_descriptor;
message_.set_replacement_id(replacement_id);
}
bool TransformationReplaceIrrelevantId::IsApplicable(
opt::IRContext* ir_context,
const TransformationContext& transformation_context) const {
auto id_of_interest = message_.id_use_descriptor().id_of_interest();
// The id must be irrelevant.
if (!transformation_context.GetFactManager()->IdIsIrrelevant(
id_of_interest)) {
return false;
}
// Find the instruction containing the id use, which must exist.
auto use_instruction =
FindInstructionContainingUse(message_.id_use_descriptor(), ir_context);
if (!use_instruction) {
return false;
}
// Check that the replacement id exists and retrieve its definition.
auto replacement_id_def =
ir_context->get_def_use_mgr()->GetDef(message_.replacement_id());
if (!replacement_id_def) {
return false;
}
// The type of the id of interest and of the replacement id must be the same.
uint32_t type_id_of_interest =
ir_context->get_def_use_mgr()->GetDef(id_of_interest)->type_id();
uint32_t type_replacement_id = replacement_id_def->type_id();
if (type_id_of_interest != type_replacement_id) {
return false;
}
// Consistency check: an irrelevant id cannot be a pointer.
assert(
!ir_context->get_type_mgr()->GetType(type_id_of_interest)->AsPointer() &&
"An irrelevant id cannot be a pointer");
// The id use must be replaceable with any other id of the same type.
if (!fuzzerutil::IdUseCanBeReplaced(
ir_context, use_instruction,
message_.id_use_descriptor().in_operand_index())) {
return false;
}
// The id must be available to use at the use point.
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3722):
// IdIsAvailable at use always returns false if the instruction is in an
// unreachable block, so it might need to be fixed.
return fuzzerutil::IdIsAvailableAtUse(
ir_context, use_instruction,
message_.id_use_descriptor().in_operand_index(),
message_.replacement_id());
}
void TransformationReplaceIrrelevantId::Apply(
opt::IRContext* ir_context,
TransformationContext* /* transformation_context */) const {
// Find the instruction.
auto instruction_to_change =
FindInstructionContainingUse(message_.id_use_descriptor(), ir_context);
// Replace the instruction.
instruction_to_change->SetInOperand(
message_.id_use_descriptor().in_operand_index(),
{message_.replacement_id()});
// Invalidate the analyses, since the usage of ids has been changed.
ir_context->InvalidateAnalysesExceptFor(
opt::IRContext::Analysis::kAnalysisNone);
}
protobufs::Transformation TransformationReplaceIrrelevantId::ToMessage() const {
protobufs::Transformation result;
*result.mutable_replace_irrelevant_id() = message_;
return result;
}
} // namespace fuzz
} // namespace spvtools

View File

@ -0,0 +1,58 @@
// 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.
#ifndef SOURCE_FUZZ_TRANSFORMATION_REPLACE_IRRELEVANT_ID_H_
#define SOURCE_FUZZ_TRANSFORMATION_REPLACE_IRRELEVANT_ID_H_
#include "source/fuzz/transformation.h"
namespace spvtools {
namespace fuzz {
class TransformationReplaceIrrelevantId : public Transformation {
public:
explicit TransformationReplaceIrrelevantId(
const protobufs::TransformationReplaceIrrelevantId& message);
TransformationReplaceIrrelevantId(
const protobufs::IdUseDescriptor& id_use_descriptor,
uint32_t replacement_id);
// - The id of interest in |message_.id_use_descriptor| is irrelevant
// according to the fact manager.
// - The types of the original id and of the replacement ids are the same.
// - |message_.replacement_id| is available to use at the enclosing
// instruction of |message_.id_use_descriptor|.
// - The original id is in principle replaceable with any other id of the same
// type. See fuzzerutil::IdUseCanBeReplaced for details.
bool IsApplicable(
opt::IRContext* ir_context,
const TransformationContext& transformation_context) const override;
// Replaces the use of an irrelevant id identified by
// |message_.id_use_descriptor| with the id |message_.replacement_id|, which
// has the same type as the id of interest.
void Apply(opt::IRContext* ir_context,
TransformationContext* transformation_context) const override;
protobufs::Transformation ToMessage() const override;
private:
protobufs::TransformationReplaceIrrelevantId message_;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_TRANSFORMATION_REPLACE_IRRELEVANT_ID_H_

View File

@ -83,6 +83,7 @@ if (${SPIRV_BUILD_FUZZER})
transformation_replace_constant_with_uniform_test.cpp
transformation_replace_copy_memory_with_load_store_test.cpp
transformation_replace_id_with_synonym_test.cpp
transformation_replace_irrelevant_id_test.cpp
transformation_replace_linear_algebra_instruction_test.cpp
transformation_replace_load_store_with_copy_memory_test.cpp
transformation_replace_parameter_with_global_test.cpp

View File

@ -1376,6 +1376,47 @@ TEST(FactManagerTest, IdIsIrrelevant) {
ASSERT_FALSE(fact_manager.IdIsIrrelevant(13));
}
TEST(FactManagerTest, GetIrrelevantIds) {
std::string shader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 320
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeInt 32 1
%12 = OpConstant %6 0
%13 = OpConstant %6 1
%14 = OpConstant %6 2
%4 = OpFunction %2 None %3
%5 = OpLabel
OpReturn
OpFunctionEnd
)";
const auto env = SPV_ENV_UNIVERSAL_1_3;
const auto consumer = nullptr;
const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
ASSERT_TRUE(IsValid(env, context.get()));
FactManager fact_manager;
ASSERT_TRUE(fact_manager.GetIrrelevantIds() ==
std::unordered_set<uint32_t>({}));
fact_manager.AddFactIdIsIrrelevant(12);
ASSERT_TRUE(fact_manager.GetIrrelevantIds() ==
std::unordered_set<uint32_t>({12}));
fact_manager.AddFactIdIsIrrelevant(13);
ASSERT_TRUE(fact_manager.GetIrrelevantIds() ==
std::unordered_set<uint32_t>({12, 13}));
}
} // namespace
} // namespace fuzz
} // namespace spvtools

View File

@ -0,0 +1,193 @@
// 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/transformation_replace_irrelevant_id.h"
#include "source/fuzz/id_use_descriptor.h"
#include "source/fuzz/instruction_descriptor.h"
#include "test/fuzz/fuzz_test_util.h"
namespace spvtools {
namespace fuzz {
namespace {
const std::string shader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %2 "main"
OpExecutionMode %2 OriginUpperLeft
OpSource ESSL 310
OpName %2 "main"
OpName %3 "a"
OpName %4 "b"
%5 = OpTypeVoid
%6 = OpTypeFunction %5
%7 = OpTypeBool
%8 = OpConstantTrue %7
%9 = OpTypeInt 32 1
%10 = OpTypePointer Function %9
%11 = OpConstant %9 2
%12 = OpTypeStruct %9
%13 = OpTypeInt 32 0
%14 = OpConstant %13 3
%15 = OpTypeArray %12 %14
%16 = OpTypePointer Function %15
%17 = OpConstant %9 0
%2 = OpFunction %5 None %6
%18 = OpLabel
%3 = OpVariable %10 Function
%4 = OpVariable %10 Function
%19 = OpVariable %16 Function
OpStore %3 %11
%20 = OpLoad %9 %3
%21 = OpAccessChain %10 %19 %20 %17
%22 = OpLoad %9 %21
OpStore %4 %22
%23 = OpLoad %9 %4
%24 = OpIAdd %9 %20 %23
%25 = OpISub %9 %23 %20
OpReturn
OpFunctionEnd
)";
void SetUpIrrelevantIdFacts(FactManager* fact_manager) {
fact_manager->AddFactIdIsIrrelevant(17);
fact_manager->AddFactIdIsIrrelevant(23);
fact_manager->AddFactIdIsIrrelevant(24);
fact_manager->AddFactIdIsIrrelevant(25);
}
TEST(TransformationReplaceIrrelevantIdTest, Inapplicable) {
const auto env = SPV_ENV_UNIVERSAL_1_5;
const auto consumer = nullptr;
const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
ASSERT_TRUE(IsValid(env, context.get()));
FactManager fact_manager;
spvtools::ValidatorOptions validator_options;
TransformationContext transformation_context(&fact_manager,
validator_options);
SetUpIrrelevantIdFacts(transformation_context.GetFactManager());
auto instruction_21_descriptor =
MakeInstructionDescriptor(21, SpvOpAccessChain, 0);
auto instruction_24_descriptor = MakeInstructionDescriptor(24, SpvOpIAdd, 0);
// %20 has not been declared as irrelevant.
ASSERT_FALSE(TransformationReplaceIrrelevantId(
MakeIdUseDescriptor(20, instruction_24_descriptor, 0), 23)
.IsApplicable(context.get(), transformation_context));
// %22 is not used in %24.
ASSERT_FALSE(TransformationReplaceIrrelevantId(
MakeIdUseDescriptor(22, instruction_24_descriptor, 1), 20)
.IsApplicable(context.get(), transformation_context));
// Replacement id %50 does not exist.
ASSERT_FALSE(TransformationReplaceIrrelevantId(
MakeIdUseDescriptor(23, instruction_24_descriptor, 1), 50)
.IsApplicable(context.get(), transformation_context));
// %25 is not available to use at %24.
ASSERT_FALSE(TransformationReplaceIrrelevantId(
MakeIdUseDescriptor(23, instruction_24_descriptor, 1), 25)
.IsApplicable(context.get(), transformation_context));
// %24 is not available to use at %24.
ASSERT_FALSE(TransformationReplaceIrrelevantId(
MakeIdUseDescriptor(23, instruction_24_descriptor, 1), 24)
.IsApplicable(context.get(), transformation_context));
// %8 has not the same type as %23.
ASSERT_FALSE(TransformationReplaceIrrelevantId(
MakeIdUseDescriptor(23, instruction_24_descriptor, 1), 8)
.IsApplicable(context.get(), transformation_context));
// %17 is an index to a struct in an access chain, so it can't be replaced.
ASSERT_FALSE(TransformationReplaceIrrelevantId(
MakeIdUseDescriptor(17, instruction_21_descriptor, 2), 20)
.IsApplicable(context.get(), transformation_context));
}
TEST(TransformationReplaceIrrelevantIdTest, Apply) {
const auto env = SPV_ENV_UNIVERSAL_1_5;
const auto consumer = nullptr;
const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
ASSERT_TRUE(IsValid(env, context.get()));
FactManager fact_manager;
spvtools::ValidatorOptions validator_options;
TransformationContext transformation_context(&fact_manager,
validator_options);
SetUpIrrelevantIdFacts(transformation_context.GetFactManager());
auto instruction_24_descriptor = MakeInstructionDescriptor(24, SpvOpIAdd, 0);
// Replace the use of %23 in %24 with %22.
auto transformation = TransformationReplaceIrrelevantId(
MakeIdUseDescriptor(23, instruction_24_descriptor, 1), 22);
ASSERT_TRUE(
transformation.IsApplicable(context.get(), transformation_context));
transformation.Apply(context.get(), &transformation_context);
ASSERT_TRUE(IsValid(env, context.get()));
std::string after_transformation = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %2 "main"
OpExecutionMode %2 OriginUpperLeft
OpSource ESSL 310
OpName %2 "main"
OpName %3 "a"
OpName %4 "b"
%5 = OpTypeVoid
%6 = OpTypeFunction %5
%7 = OpTypeBool
%8 = OpConstantTrue %7
%9 = OpTypeInt 32 1
%10 = OpTypePointer Function %9
%11 = OpConstant %9 2
%12 = OpTypeStruct %9
%13 = OpTypeInt 32 0
%14 = OpConstant %13 3
%15 = OpTypeArray %12 %14
%16 = OpTypePointer Function %15
%17 = OpConstant %9 0
%2 = OpFunction %5 None %6
%18 = OpLabel
%3 = OpVariable %10 Function
%4 = OpVariable %10 Function
%19 = OpVariable %16 Function
OpStore %3 %11
%20 = OpLoad %9 %3
%21 = OpAccessChain %10 %19 %20 %17
%22 = OpLoad %9 %21
OpStore %4 %22
%23 = OpLoad %9 %4
%24 = OpIAdd %9 %20 %22
%25 = OpISub %9 %23 %20
OpReturn
OpFunctionEnd
)";
ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
}
} // namespace
} // namespace fuzz
} // namespace spvtools