spirv-fuzz: simplify transformation for replacing an id with a synonym (#3020)

Prior to this change, TransformationReplaceIdWithSynonym was designed
to be able to replace an id with some synonymous data descriptor,
possibly necessitating extracting from a composite into a fresh id in
order to get at the synonymous data.  This change simplifies things so
that TransformationReplaceIdWithSynonym just allows one id to be
replaced by another id.  It is the responsibility of the associated
fuzzer pass - FuzzerPassApplyIdSynonyms - to perform the extraction
operations, using e.g. TransformationCompositeExtract.
This commit is contained in:
Alastair Donaldson 2019-11-07 16:19:06 +00:00 committed by GitHub
parent 528c00c016
commit 041f0a0249
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 2541 additions and 268 deletions

View File

@ -14,7 +14,11 @@
#include "source/fuzz/fuzzer_pass_apply_id_synonyms.h"
#include "source/fuzz/data_descriptor.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/id_use_descriptor.h"
#include "source/fuzz/instruction_descriptor.h"
#include "source/fuzz/transformation_composite_extract.h"
#include "source/fuzz/transformation_replace_id_with_synonym.h"
namespace spvtools {
@ -29,90 +33,113 @@ FuzzerPassApplyIdSynonyms::FuzzerPassApplyIdSynonyms(
FuzzerPassApplyIdSynonyms::~FuzzerPassApplyIdSynonyms() = default;
void FuzzerPassApplyIdSynonyms::Apply() {
std::vector<TransformationReplaceIdWithSynonym> transformations_to_apply;
for (auto id_with_known_synonyms :
GetFactManager()->GetIdsForWhichSynonymsAreKnown(GetIRContext())) {
// Gather up all uses of |id_with_known_synonym|, and then subsequently
// iterate over these uses. We use this separation because, when
// considering a given use, we might apply a transformation that will
// invalidate the def-use manager.
std::vector<std::pair<opt::Instruction*, uint32_t>> uses;
GetIRContext()->get_def_use_mgr()->ForEachUse(
id_with_known_synonyms,
[this, id_with_known_synonyms, &transformations_to_apply](
opt::Instruction* use_inst, uint32_t use_index) -> void {
[&uses](opt::Instruction* use_inst, uint32_t use_index) -> void {
uses.emplace_back(
std::pair<opt::Instruction*, uint32_t>(use_inst, use_index));
});
for (auto& use : uses) {
auto use_inst = use.first;
auto use_index = use.second;
auto block_containing_use = GetIRContext()->get_instr_block(use_inst);
// The use might not be in a block; e.g. it could be a decoration.
if (!block_containing_use) {
return;
continue;
}
if (!GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfReplacingIdWithSynonym())) {
return;
continue;
}
// |use_index| is the absolute index of the operand. We require
// the index of the operand restricted to input operands only, so
// we subtract the number of non-input operands from |use_index|.
uint32_t use_in_operand_index =
use_index - use_inst->NumOperands() + use_inst->NumInOperands();
if (!TransformationReplaceIdWithSynonym::UseCanBeReplacedWithSynonym(
GetIRContext(), use_inst, use_in_operand_index)) {
continue;
}
std::vector<const protobufs::DataDescriptor*> synonyms_to_try;
for (auto& data_descriptor : GetFactManager()->GetSynonymsForId(
id_with_known_synonyms, GetIRContext())) {
protobufs::DataDescriptor descriptor_for_this_id =
MakeDataDescriptor(id_with_known_synonyms, {});
if (DataDescriptorEquals()(data_descriptor, &descriptor_for_this_id)) {
// Exclude the fact that the id is synonymous with itself.
continue;
}
synonyms_to_try.push_back(data_descriptor);
}
while (!synonyms_to_try.empty()) {
auto synonym_index =
GetFuzzerContext()->RandomIndex(synonyms_to_try);
auto synonym_index = GetFuzzerContext()->RandomIndex(synonyms_to_try);
auto synonym_to_try = synonyms_to_try[synonym_index];
synonyms_to_try.erase(synonyms_to_try.begin() + synonym_index);
if (!TransformationReplaceIdWithSynonym::
ReplacingUseWithSynonymIsOk(GetIRContext(), use_inst,
use_in_operand_index,
*synonym_to_try)) {
if (synonym_to_try->index_size() > 0 &&
use_inst->opcode() == SpvOpPhi) {
// We are trying to replace an operand to an OpPhi. This means
// we cannot use a composite synonym, because that requires
// extracting a component from a composite and we cannot insert
// an extract instruction before an OpPhi.
//
// TODO(afd): We could consider inserting the extract instruction
// into the relevant parent block of the OpPhi.
continue;
}
// At present, we generate direct id synonyms (through
// OpCopyObject), which require no indices, and id synonyms that
// require a single index (through OpCompositeConstruct).
assert(synonym_to_try->index_size() <= 1);
if (!TransformationReplaceIdWithSynonym::IdsIsAvailableAtUse(
GetIRContext(), use_inst, use_in_operand_index,
synonym_to_try->object())) {
continue;
}
// If an index is required, then we need to extract an element
// from a composite (e.g. through OpCompositeExtract), and this
// requires a fresh result id.
auto fresh_id_for_temporary =
synonym_to_try->index().empty()
? 0
: GetFuzzerContext()->GetFreshId();
// We either replace the use with an id known to be synonymous, or
// an id that will hold the result of extracting a synonym from a
// composite.
uint32_t id_with_which_to_replace_use;
if (synonym_to_try->index_size() == 0) {
id_with_which_to_replace_use = synonym_to_try->object();
} else {
id_with_which_to_replace_use = GetFuzzerContext()->GetFreshId();
protobufs::InstructionDescriptor instruction_to_insert_before =
MakeInstructionDescriptor(GetIRContext(), use_inst);
TransformationCompositeExtract composite_extract_transformation(
instruction_to_insert_before, id_with_which_to_replace_use,
synonym_to_try->object(),
fuzzerutil::RepeatedFieldToVector(synonym_to_try->index()));
assert(composite_extract_transformation.IsApplicable(
GetIRContext(), *GetFactManager()) &&
"Transformation should be applicable by construction.");
composite_extract_transformation.Apply(GetIRContext(),
GetFactManager());
*GetTransformations()->add_transformation() =
composite_extract_transformation.ToMessage();
}
TransformationReplaceIdWithSynonym replace_id_transformation(
MakeIdUseDescriptorFromUse(GetIRContext(), use_inst,
use_in_operand_index),
*synonym_to_try, fresh_id_for_temporary);
id_with_which_to_replace_use);
// The transformation should be applicable by construction.
assert(replace_id_transformation.IsApplicable(GetIRContext(),
*GetFactManager()));
// We cannot actually apply the transformation here, as this would
// change the analysis results that are being depended on for usage
// iteration. We instead store them up and apply them at the end
// of the method.
transformations_to_apply.push_back(replace_id_transformation);
break;
}
});
}
for (auto& replace_id_transformation : transformations_to_apply) {
// Even though replacing id uses with synonyms may lead to new instructions
// (to compute indices into composites), as these instructions will generate
// ids, their presence should not affect the id use descriptors that were
// computed during the creation of transformations. Thus transformations
// should not disable one another.
assert(replace_id_transformation.IsApplicable(GetIRContext(),
*GetFactManager()));
replace_id_transformation.Apply(GetIRContext(), GetFactManager());
*GetTransformations()->add_transformation() =
replace_id_transformation.ToMessage();
break;
}
}
}
}

View File

@ -53,29 +53,9 @@ protobufs::IdUseDescriptor MakeIdUseDescriptorFromUse(
uint32_t in_operand_index) {
auto in_operand = inst->GetInOperand(in_operand_index);
assert(in_operand.type == SPV_OPERAND_TYPE_ID);
auto id_of_interest = in_operand.words[0];
auto block = context->get_instr_block(inst);
uint32_t base_instruction_result_id = block->id();
uint32_t num_opcodes_to_ignore = 0;
for (auto& inst_in_block : *block) {
if (inst_in_block.HasResultId()) {
base_instruction_result_id = inst_in_block.result_id();
num_opcodes_to_ignore = 0;
}
if (&inst_in_block == inst) {
return MakeIdUseDescriptor(
id_of_interest,
MakeInstructionDescriptor(base_instruction_result_id, inst->opcode(),
num_opcodes_to_ignore),
return MakeIdUseDescriptor(in_operand.words[0],
MakeInstructionDescriptor(context, inst),
in_operand_index);
}
if (inst_in_block.opcode() == inst->opcode()) {
num_opcodes_to_ignore++;
}
}
assert(false && "No matching instruction was found.");
return protobufs::IdUseDescriptor();
}
} // namespace fuzz

View File

@ -101,5 +101,27 @@ protobufs::InstructionDescriptor MakeInstructionDescriptor(
return MakeInstructionDescriptor(block.id(), opcode, skip_count);
}
protobufs::InstructionDescriptor MakeInstructionDescriptor(
opt::IRContext* context, opt::Instruction* inst) {
auto block = context->get_instr_block(inst);
uint32_t base_instruction_result_id = block->id();
uint32_t num_opcodes_to_ignore = 0;
for (auto& inst_in_block : *block) {
if (inst_in_block.HasResultId()) {
base_instruction_result_id = inst_in_block.result_id();
num_opcodes_to_ignore = 0;
}
if (&inst_in_block == inst) {
return MakeInstructionDescriptor(base_instruction_result_id,
inst->opcode(), num_opcodes_to_ignore);
}
if (inst_in_block.opcode() == inst->opcode()) {
num_opcodes_to_ignore++;
}
}
assert(false && "No matching instruction was found.");
return protobufs::InstructionDescriptor();
}
} // namespace fuzz
} // namespace spvtools

View File

@ -43,6 +43,10 @@ protobufs::InstructionDescriptor MakeInstructionDescriptor(
const opt::BasicBlock& block,
const opt::BasicBlock::const_iterator& inst_it);
// Returns an InstructionDescriptor that describes the given instruction |inst|.
protobufs::InstructionDescriptor MakeInstructionDescriptor(
opt::IRContext* context, opt::Instruction* inst);
} // namespace fuzz
} // namespace spvtools

View File

@ -432,19 +432,15 @@ message TransformationReplaceConstantWithUniform {
message TransformationReplaceIdWithSynonym {
// Replaces an id use with something known to be synonymous with that id use,
// e.g. because it was obtained via applying OpCopyObject
// Replaces a use of an id with an id that is known to be synonymous, e.g.
// because it was obtained via applying OpCopyObject
// Identifies the id use that is to be replaced
// The id use that is to be replaced
IdUseDescriptor id_use_descriptor = 1;
// Identifies the data with which the id use is expected to be synonymous
DataDescriptor data_descriptor = 2;
// The synonymous id
uint32 synonymous_id = 2;
// In the case that a temporary is required to express the synonym (e.g. to
// obtain an element of a vector, provides a fresh id for the temporary;
// should be set to 0 if no temporary is required
uint32 fresh_id_for_temporary = 3;
}
message TransformationSetFunctionControl {

View File

@ -31,22 +31,21 @@ TransformationReplaceIdWithSynonym::TransformationReplaceIdWithSynonym(
: message_(message) {}
TransformationReplaceIdWithSynonym::TransformationReplaceIdWithSynonym(
protobufs::IdUseDescriptor id_use_descriptor,
protobufs::DataDescriptor data_descriptor,
uint32_t fresh_id_for_temporary) {
assert((fresh_id_for_temporary == 0) == (data_descriptor.index().empty()));
protobufs::IdUseDescriptor id_use_descriptor, uint32_t synonymous_id) {
*message_.mutable_id_use_descriptor() = std::move(id_use_descriptor);
*message_.mutable_data_descriptor() = std::move(data_descriptor);
message_.set_fresh_id_for_temporary(fresh_id_for_temporary);
message_.set_synonymous_id(synonymous_id);
}
bool TransformationReplaceIdWithSynonym::IsApplicable(
spvtools::opt::IRContext* context,
const spvtools::fuzz::FactManager& fact_manager) const {
auto id_of_interest = message_.id_use_descriptor().id_of_interest();
// Does the fact manager know about the synonym?
if (!fact_manager.IsSynonymous(
MakeDataDescriptor(message_.id_use_descriptor().id_of_interest(), {}),
message_.data_descriptor(), context)) {
auto data_descriptor_for_synonymous_id =
MakeDataDescriptor(message_.synonymous_id(), {});
if (!fact_manager.IsSynonymous(MakeDataDescriptor(id_of_interest, {}),
data_descriptor_for_synonymous_id, context)) {
return false;
}
@ -57,34 +56,18 @@ bool TransformationReplaceIdWithSynonym::IsApplicable(
return false;
}
// Is it legitimate to replace the use identified by the id use descriptor
// with a synonym?
if (!ReplacingUseWithSynonymIsOk(
// Is the use suitable for being replaced in principle?
if (!UseCanBeReplacedWithSynonym(
context, use_instruction,
message_.id_use_descriptor().in_operand_index())) {
return false;
}
// The transformation is applicable if the synonymous id is available at the
// use point.
return IdsIsAvailableAtUse(context, use_instruction,
message_.id_use_descriptor().in_operand_index(),
message_.data_descriptor())) {
return false;
}
if (message_.fresh_id_for_temporary() == 0) {
if (!message_.data_descriptor().index().empty()) {
// If we have no id to use as a temporary variable, we should not have any
// indices to extract from.
return false;
}
} else {
if (!fuzzerutil::IsFreshId(context, message_.fresh_id_for_temporary())) {
// The id to be used as a temporary needs to be fresh.
return false;
}
if (message_.data_descriptor().index_size() != 1) {
// At present we support just a single index to allow extracting directly
// from a composite.
return false;
}
}
return true;
message_.synonymous_id());
}
void TransformationReplaceIdWithSynonym::Apply(
@ -92,77 +75,9 @@ void TransformationReplaceIdWithSynonym::Apply(
spvtools::fuzz::FactManager* /*unused*/) const {
auto instruction_to_change =
FindInstructionContainingUse(message_.id_use_descriptor(), context);
// Ultimately we are going to replace the id use identified in the
// transformation with |replacement_id|, which will either be the synonym's
// id, or the id of a temporary used to extract the synonym from a composite.
uint32_t replacement_id;
if (message_.fresh_id_for_temporary()) {
// The transformation having a temporary variable means that we need to
// extract the synonym from a composite.
uint32_t type_id_of_id_to_be_replaced =
context->get_def_use_mgr()
->GetDef(message_.id_use_descriptor().id_of_interest())
->type_id();
opt::analysis::Type* type_of_id_to_be_replaced =
context->get_type_mgr()->GetType(type_id_of_id_to_be_replaced);
opt::analysis::Type* type_of_composite = context->get_type_mgr()->GetType(
context->get_def_use_mgr()
->GetDef(message_.data_descriptor().object())
->type_id());
// Intuitively we want to make an OpCompositeExtract instruction, to get the
// synonym out of the composite. But in the case of a vector, the synonym
// might involve multiple vector indices; e.g. the y and z components of a
// vec4 might be synonymous with a vec2, and in that case OpCompositeExtract
// doesn't give us what we want; we need to use OpVectorShuffle instead.
std::unique_ptr<opt::Instruction> new_instruction;
if (type_of_composite->AsVector() &&
type_of_composite->AsVector()->element_type() !=
type_of_id_to_be_replaced) {
// We need to extract a vector from inside a vector, so we will need to
// use OpVectorShuffle.
assert(type_of_id_to_be_replaced->AsVector());
assert(type_of_id_to_be_replaced->AsVector()->element_type() ==
type_of_composite->AsVector()->element_type());
opt::Instruction::OperandList shuffle_operands = {
{SPV_OPERAND_TYPE_ID, {message_.data_descriptor().object()}},
{SPV_OPERAND_TYPE_ID, {message_.data_descriptor().object()}}};
for (uint32_t i = 0;
i < type_of_id_to_be_replaced->AsVector()->element_count(); i++) {
shuffle_operands.push_back({SPV_OPERAND_TYPE_LITERAL_INTEGER,
{message_.data_descriptor().index(0) + i}});
}
new_instruction = MakeUnique<opt::Instruction>(
context, SpvOpVectorShuffle, type_id_of_id_to_be_replaced,
message_.fresh_id_for_temporary(), shuffle_operands);
} else {
// We are either extracting from a non-vector, or extracting a scalar from
// a vector, so we can use OpCompositeExtract.
opt::Instruction::OperandList extract_operands = {
{SPV_OPERAND_TYPE_ID, {message_.data_descriptor().object()}},
{SPV_OPERAND_TYPE_LITERAL_INTEGER,
{message_.data_descriptor().index(0)}}};
new_instruction = MakeUnique<opt::Instruction>(
context, SpvOpCompositeExtract, type_id_of_id_to_be_replaced,
message_.fresh_id_for_temporary(), extract_operands);
}
instruction_to_change->InsertBefore(std::move(new_instruction));
// The replacement id is the temporary variable we used to extract the
// synonym from a composite.
replacement_id = message_.fresh_id_for_temporary();
fuzzerutil::UpdateModuleIdBound(context, replacement_id);
} else {
// The replacement id is the synonym's id.
replacement_id = message_.data_descriptor().object();
}
instruction_to_change->SetInOperand(
message_.id_use_descriptor().in_operand_index(), {replacement_id});
message_.id_use_descriptor().in_operand_index(),
{message_.synonymous_id()});
context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone);
}
@ -173,22 +88,33 @@ protobufs::Transformation TransformationReplaceIdWithSynonym::ToMessage()
return result;
}
bool TransformationReplaceIdWithSynonym::ReplacingUseWithSynonymIsOk(
bool TransformationReplaceIdWithSynonym::IdsIsAvailableAtUse(
opt::IRContext* context, opt::Instruction* use_instruction,
uint32_t use_in_operand_index, const protobufs::DataDescriptor& synonym) {
auto defining_instruction =
context->get_def_use_mgr()->GetDef(synonym.object());
if (use_instruction == defining_instruction) {
// If we have an instruction:
// %a = OpCopyObject %t %b
// then we know %a and %b are synonymous, but we do *not* want to turn
// this into:
// %a = OpCopyObject %t %a
// We require this special case because an instruction dominates itself.
uint32_t use_input_operand_index, uint32_t id) {
if (!context->get_instr_block(id)) {
return true;
}
auto defining_instruction = context->get_def_use_mgr()->GetDef(id);
if (defining_instruction == use_instruction) {
return false;
}
auto dominator_analysis = context->GetDominatorAnalysis(
context->get_instr_block(use_instruction)->GetParent());
if (use_instruction->opcode() == SpvOpPhi) {
// In the case where the use is an operand to OpPhi, it is actually the
// *parent* block associated with the operand that must be dominated by
// the synonym.
auto parent_block =
use_instruction->GetSingleWordInOperand(use_input_operand_index + 1);
return dominator_analysis->Dominates(
context->get_instr_block(defining_instruction)->id(), parent_block);
}
return dominator_analysis->Dominates(defining_instruction, use_instruction);
}
bool TransformationReplaceIdWithSynonym::UseCanBeReplacedWithSynonym(
opt::IRContext* context, opt::Instruction* use_instruction,
uint32_t use_in_operand_index) {
if (use_instruction->opcode() == SpvOpAccessChain &&
use_in_operand_index > 0) {
// This is an access chain index. If the (sub-)object being accessed by the
@ -262,31 +188,6 @@ bool TransformationReplaceIdWithSynonym::ReplacingUseWithSynonymIsOk(
return false;
}
}
// We now need to check that replacing the use with the synonym will respect
// dominance rules - i.e. the synonym needs to dominate the use.
// This is only relevant if the defining instruction is in a block; if it is
// not in a block then it is at global scope, and so replacing the use with it
// is fine.
if (context->get_instr_block(defining_instruction)) {
auto dominator_analysis = context->GetDominatorAnalysis(
context->get_instr_block(use_instruction)->GetParent());
if (use_instruction->opcode() == SpvOpPhi) {
// In the case where the use is an operand to OpPhi, it is actually the
// *parent* block associated with the operand that must be dominated by
// the synonym.
auto parent_block =
use_instruction->GetSingleWordInOperand(use_in_operand_index + 1);
if (!dominator_analysis->Dominates(
context->get_instr_block(defining_instruction)->id(),
parent_block)) {
return false;
}
} else if (!dominator_analysis->Dominates(defining_instruction,
use_instruction)) {
return false;
}
}
return true;
}

View File

@ -29,40 +29,46 @@ class TransformationReplaceIdWithSynonym : public Transformation {
const protobufs::TransformationReplaceIdWithSynonym& message);
TransformationReplaceIdWithSynonym(
protobufs::IdUseDescriptor id_use_descriptor,
protobufs::DataDescriptor data_descriptor,
uint32_t fresh_id_for_temporary);
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_.data_descriptor|.
// - Replacing the id in |message_.id_use_descriptor| by the synonym in
// |message_.data_descriptor| must respect SPIR-V's rules about uses being
// |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).
// - |fresh_id_for_temporary| must be 0.
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2855): the
// motivation for the temporary is to support the case where an id is
// synonymous with an element of a composite. Until support for that is
// implemented, 0 records that no temporary is needed.
bool IsApplicable(opt::IRContext* context,
const FactManager& fact_manager) const override;
// Replaces the use identified by |message_.id_use_descriptor| with the
// synonymous id identified by |message_.data_descriptor|.
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2855): in due
// course it will also be necessary to add an additional instruction to pull
// the synonym out of a composite.
// synonymous id identified by |message_.synonymous_id|.
void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
protobufs::Transformation ToMessage() const override;
static bool ReplacingUseWithSynonymIsOk(
opt::IRContext* context, opt::Instruction* use_instruction,
uint32_t use_in_operand_index, const protobufs::DataDescriptor& synonym);
// Checks whether the |id| is available (according to dominance rules) at the
// use point defined by input operand |use_input_operand_index| of
// |use_instruction|.
static bool IdsIsAvailableAtUse(opt::IRContext* context,
opt::Instruction* use_instruction,
uint32_t use_input_operand_index,
uint32_t id);
// 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* context,
opt::Instruction* use_instruction,
uint32_t use_in_operand_index);
private:
protobufs::TransformationReplaceIdWithSynonym message_;

View File

@ -17,6 +17,7 @@ if (${SPIRV_BUILD_FUZZER})
set(SOURCES
fuzz_test_util.h
data_synonym_transformation_test.cpp
equivalence_relation_test.cpp
fact_manager_test.cpp
fuzz_test_util.cpp
@ -37,6 +38,7 @@ if (${SPIRV_BUILD_FUZZER})
transformation_move_block_down_test.cpp
transformation_replace_boolean_constant_with_constant_binary_test.cpp
transformation_replace_constant_with_uniform_test.cpp
transformation_replace_id_with_synonym_test.cpp
transformation_set_function_control_test.cpp
transformation_set_loop_control_test.cpp
transformation_set_memory_operands_mask_test.cpp

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff