spirv-fuzz: Create synonym via OpPhi and existing synonyms (#3701)

A transformation that adds new OpPhi instructions to blocks with >=1
predecessors, so that its value depends on previously-defined ids of
the right type, which are all synonymous. This instruction is also
recorded as synonymous to the others.

The related fuzzer pass still needs to be implemented.

Fixes #3592 .
This commit is contained in:
Stefano Milizia 2020-08-27 16:59:54 +02:00 committed by GitHub
parent 7e4948b2a5
commit 08291a3a9e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 1275 additions and 0 deletions

View File

@ -59,6 +59,7 @@ if(SPIRV_BUILD_FUZZER)
fuzzer_pass_add_local_variables.h
fuzzer_pass_add_loop_preheaders.h
fuzzer_pass_add_no_contraction_decorations.h
fuzzer_pass_add_opphi_synonyms.h
fuzzer_pass_add_parameters.h
fuzzer_pass_add_relaxed_decorations.h
fuzzer_pass_add_stores.h
@ -124,6 +125,7 @@ if(SPIRV_BUILD_FUZZER)
transformation_add_local_variable.h
transformation_add_loop_preheader.h
transformation_add_no_contraction_decoration.h
transformation_add_opphi_synonym.h
transformation_add_parameter.h
transformation_add_relaxed_decoration.h
transformation_add_spec_constant_op.h
@ -210,6 +212,7 @@ if(SPIRV_BUILD_FUZZER)
fuzzer_pass_add_local_variables.cpp
fuzzer_pass_add_loop_preheaders.cpp
fuzzer_pass_add_no_contraction_decorations.cpp
fuzzer_pass_add_opphi_synonyms.cpp
fuzzer_pass_add_parameters.cpp
fuzzer_pass_add_relaxed_decorations.cpp
fuzzer_pass_add_stores.cpp
@ -274,6 +277,7 @@ if(SPIRV_BUILD_FUZZER)
transformation_add_local_variable.cpp
transformation_add_loop_preheader.cpp
transformation_add_no_contraction_decoration.cpp
transformation_add_opphi_synonym.cpp
transformation_add_parameter.cpp
transformation_add_relaxed_decoration.cpp
transformation_add_spec_constant_op.cpp

View File

@ -35,6 +35,7 @@
#include "source/fuzz/fuzzer_pass_add_local_variables.h"
#include "source/fuzz/fuzzer_pass_add_loop_preheaders.h"
#include "source/fuzz/fuzzer_pass_add_no_contraction_decorations.h"
#include "source/fuzz/fuzzer_pass_add_opphi_synonyms.h"
#include "source/fuzz/fuzzer_pass_add_parameters.h"
#include "source/fuzz/fuzzer_pass_add_relaxed_decorations.h"
#include "source/fuzz/fuzzer_pass_add_stores.h"
@ -256,6 +257,9 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run(
MaybeAddPass<FuzzerPassAddLoopPreheaders>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassAddOpPhiSynonyms>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassAddParameters>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);

View File

@ -43,6 +43,7 @@ const std::pair<uint32_t, uint32_t> kChanceOfAddingLoopPreheader = {20, 90};
const std::pair<uint32_t, uint32_t> kChanceOfAddingMatrixType = {20, 70};
const std::pair<uint32_t, uint32_t> kChanceOfAddingNoContractionDecoration = {
5, 70};
const std::pair<uint32_t, uint32_t> kChanceOfAddingOpPhiSynonym = {5, 70};
const std::pair<uint32_t, uint32_t> kChanceOfAddingParameters = {5, 70};
const std::pair<uint32_t, uint32_t> kChanceOfAddingRelaxedDecoration = {20, 90};
const std::pair<uint32_t, uint32_t> kChanceOfAddingStore = {5, 50};
@ -182,6 +183,8 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator,
ChooseBetweenMinAndMax(kChanceOfAddingMatrixType);
chance_of_adding_no_contraction_decoration_ =
ChooseBetweenMinAndMax(kChanceOfAddingNoContractionDecoration);
chance_of_adding_opphi_synonym_ =
ChooseBetweenMinAndMax(kChanceOfAddingOpPhiSynonym);
chance_of_adding_parameters =
ChooseBetweenMinAndMax(kChanceOfAddingParameters);
chance_of_adding_relaxed_decoration_ =

View File

@ -148,6 +148,9 @@ class FuzzerContext {
uint32_t GetChanceOfAddingNoContractionDecoration() {
return chance_of_adding_no_contraction_decoration_;
}
uint32_t GetChanceOfAddingOpPhiSynonym() {
return chance_of_adding_opphi_synonym_;
}
uint32_t GetChanceOfAddingParameters() { return chance_of_adding_parameters; }
uint32_t GetChanceOfAddingRelaxedDecoration() {
return chance_of_adding_relaxed_decoration_;
@ -367,6 +370,7 @@ class FuzzerContext {
uint32_t chance_of_adding_loop_preheader_;
uint32_t chance_of_adding_matrix_type_;
uint32_t chance_of_adding_no_contraction_decoration_;
uint32_t chance_of_adding_opphi_synonym_;
uint32_t chance_of_adding_parameters;
uint32_t chance_of_adding_relaxed_decoration_;
uint32_t chance_of_adding_store_;

View File

@ -0,0 +1,297 @@
// 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_add_opphi_synonyms.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/transformation_add_opphi_synonym.h"
namespace spvtools {
namespace fuzz {
FuzzerPassAddOpPhiSynonyms::FuzzerPassAddOpPhiSynonyms(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: FuzzerPass(ir_context, transformation_context, fuzzer_context,
transformations) {}
FuzzerPassAddOpPhiSynonyms::~FuzzerPassAddOpPhiSynonyms() = default;
void FuzzerPassAddOpPhiSynonyms::Apply() {
// Get a list of synonymous ids with the same type that can be used in the
// same OpPhi instruction.
auto equivalence_classes = GetIdEquivalenceClasses();
// Make a list of references, to avoid copying sets unnecessarily.
std::vector<std::set<uint32_t>*> equivalence_class_pointers;
for (auto& set : equivalence_classes) {
equivalence_class_pointers.push_back(&set);
}
// Keep a list of transformations to apply at the end.
std::vector<TransformationAddOpPhiSynonym> transformations_to_apply;
for (auto& function : *GetIRContext()->module()) {
for (auto& block : function) {
// Randomly decide whether to consider this block.
if (!GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfAddingOpPhiSynonym())) {
continue;
}
// The block must have at least one predecessor.
size_t num_preds = GetIRContext()->cfg()->preds(block.id()).size();
if (num_preds == 0) {
continue;
}
std::set<uint32_t>* chosen_equivalence_class = nullptr;
if (num_preds > 1) {
// If the block has more than one predecessor, prioritise sets with at
// least 2 ids available at some predecessor.
chosen_equivalence_class = MaybeFindSuitableEquivalenceClassRandomly(
equivalence_class_pointers, block.id(), 2);
}
// If a set was not already chosen, choose one with at least one available
// id.
if (!chosen_equivalence_class) {
chosen_equivalence_class = MaybeFindSuitableEquivalenceClassRandomly(
equivalence_class_pointers, block.id(), 1);
}
// If no suitable set was found, we cannot apply the transformation to
// this block.
if (!chosen_equivalence_class) {
continue;
}
// Initialise the map from predecessor labels to ids.
std::map<uint32_t, uint32_t> preds_to_ids;
// Keep track of the ids used and of the id of a predecessor with at least
// two ids to choose from. This is to ensure that, if possible, at least
// two distinct ids will be used.
std::set<uint32_t> ids_chosen;
uint32_t pred_with_alternatives = 0;
// Choose an id for each predecessor.
for (uint32_t pred_id : GetIRContext()->cfg()->preds(block.id())) {
auto suitable_ids = GetSuitableIds(*chosen_equivalence_class, pred_id);
assert(!suitable_ids.empty() &&
"We must be able to find at least one suitable id because the "
"equivalence class was chosen among suitable ones.");
// If this predecessor has more than one id to choose from and it is the
// first one of this kind that we found, remember its id.
if (suitable_ids.size() > 1 && !pred_with_alternatives) {
pred_with_alternatives = pred_id;
}
uint32_t chosen_id =
suitable_ids[GetFuzzerContext()->RandomIndex(suitable_ids)];
// Add this id to the set of ids chosen.
ids_chosen.emplace(chosen_id);
// Add the pair (predecessor, chosen id) to the map.
preds_to_ids[pred_id] = chosen_id;
}
// If:
// - the block has more than one predecessor
// - at least one predecessor has more than one alternative
// - the same id has been chosen by all the predecessors
// then choose another one for the predecessor with more than one
// alternative.
if (num_preds > 1 && pred_with_alternatives != 0 &&
ids_chosen.size() == 1) {
auto suitable_ids =
GetSuitableIds(*chosen_equivalence_class, pred_with_alternatives);
uint32_t chosen_id =
GetFuzzerContext()->RemoveAtRandomIndex(&suitable_ids);
if (chosen_id == preds_to_ids[pred_with_alternatives]) {
chosen_id = GetFuzzerContext()->RemoveAtRandomIndex(&suitable_ids);
}
preds_to_ids[pred_with_alternatives] = chosen_id;
}
// Add the transformation to the list of transformations to apply.
transformations_to_apply.emplace_back(block.id(), preds_to_ids,
GetFuzzerContext()->GetFreshId());
}
}
// Apply the transformations.
for (const auto& transformation : transformations_to_apply) {
ApplyTransformation(transformation);
}
}
std::vector<std::set<uint32_t>>
FuzzerPassAddOpPhiSynonyms::GetIdEquivalenceClasses() {
std::vector<std::set<uint32_t>> id_equivalence_classes;
// Keep track of all the ids that have already be assigned to a class.
std::set<uint32_t> already_in_a_class;
for (const auto& pair : GetIRContext()->get_def_use_mgr()->id_to_defs()) {
// Exclude ids that have already been assigned to a class.
if (already_in_a_class.count(pair.first)) {
continue;
}
// Exclude irrelevant ids.
if (GetTransformationContext()->GetFactManager()->IdIsIrrelevant(
pair.first)) {
continue;
}
// Exclude ids having a type that is not allowed by the transformation.
if (!TransformationAddOpPhiSynonym::CheckTypeIsAllowed(
GetIRContext(), pair.second->type_id())) {
continue;
}
// We need a new equivalence class for this id.
std::set<uint32_t> new_equivalence_class;
// Add this id to the class.
new_equivalence_class.emplace(pair.first);
already_in_a_class.emplace(pair.first);
// Add all the synonyms with the same type to this class.
for (auto synonym :
GetTransformationContext()->GetFactManager()->GetSynonymsForId(
pair.first)) {
// The synonym must be a plain id - it cannot be an indexed access into a
// composite.
if (synonym->index_size() > 0) {
continue;
}
// The synonym must not be irrelevant.
if (GetTransformationContext()->GetFactManager()->IdIsIrrelevant(
synonym->object())) {
continue;
}
auto synonym_def =
GetIRContext()->get_def_use_mgr()->GetDef(synonym->object());
// The synonym must exist and have the same type as the id we are
// considering.
if (!synonym_def || synonym_def->type_id() != pair.second->type_id()) {
continue;
}
// We can add this synonym to the new equivalence class.
new_equivalence_class.emplace(synonym->object());
already_in_a_class.emplace(synonym->object());
}
// Add the new equivalence class to the list of equivalence classes.
id_equivalence_classes.emplace_back(std::move(new_equivalence_class));
}
return id_equivalence_classes;
}
bool FuzzerPassAddOpPhiSynonyms::EquivalenceClassIsSuitableForBlock(
const std::set<uint32_t>& equivalence_class, uint32_t block_id,
uint32_t distinct_ids_required) {
bool at_least_one_id_for_each_pred = true;
// Keep a set of the suitable ids found.
std::set<uint32_t> suitable_ids_found;
// Loop through all the predecessors of the block.
for (auto pred_id : GetIRContext()->cfg()->preds(block_id)) {
// Find the last instruction in the predecessor block.
auto last_instruction =
GetIRContext()->get_instr_block(pred_id)->terminator();
// Initially assume that there is not a suitable id for this predecessor.
bool at_least_one_suitable_id_found = false;
for (uint32_t id : equivalence_class) {
if (fuzzerutil::IdIsAvailableBeforeInstruction(GetIRContext(),
last_instruction, id)) {
// We have found a suitable id.
at_least_one_suitable_id_found = true;
suitable_ids_found.emplace(id);
// If we have already found enough distinct suitable ids, we don't need
// to check the remaining ones for this predecessor.
if (suitable_ids_found.size() >= distinct_ids_required) {
break;
}
}
}
// If no suitable id was found for this predecessor, this equivalence class
// is not suitable and we don't need to check the other predecessors.
if (!at_least_one_suitable_id_found) {
at_least_one_id_for_each_pred = false;
break;
}
}
// The equivalence class is suitable if at least one suitable id was found for
// each predecessor and we have found at least |distinct_ids_required|
// distinct suitable ids in general.
return at_least_one_id_for_each_pred &&
suitable_ids_found.size() >= distinct_ids_required;
}
std::vector<uint32_t> FuzzerPassAddOpPhiSynonyms::GetSuitableIds(
const std::set<uint32_t>& ids, uint32_t pred_id) {
// Initialise an empty vector of suitable ids.
std::vector<uint32_t> suitable_ids;
// Get the predecessor block.
auto predecessor = fuzzerutil::MaybeFindBlock(GetIRContext(), pred_id);
// Loop through the ids to find the suitable ones.
for (uint32_t id : ids) {
if (fuzzerutil::IdIsAvailableBeforeInstruction(
GetIRContext(), predecessor->terminator(), id)) {
suitable_ids.push_back(id);
}
}
return suitable_ids;
}
std::set<uint32_t>*
FuzzerPassAddOpPhiSynonyms::MaybeFindSuitableEquivalenceClassRandomly(
const std::vector<std::set<uint32_t>*>& candidates, uint32_t block_id,
uint32_t distinct_ids_required) {
auto remaining_candidates = candidates;
while (!remaining_candidates.empty()) {
// Choose one set randomly and return it if it is suitable.
auto chosen =
GetFuzzerContext()->RemoveAtRandomIndex(&remaining_candidates);
if (EquivalenceClassIsSuitableForBlock(*chosen, block_id,
distinct_ids_required)) {
return chosen;
}
}
// No suitable sets were found.
return nullptr;
}
} // namespace fuzz
} // namespace spvtools

View File

@ -0,0 +1,73 @@
// 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_ADD_OPPHI_SYNONYMS_
#define SOURCE_FUZZ_FUZZER_PASS_ADD_OPPHI_SYNONYMS_
#include "source/fuzz/fuzzer_pass.h"
namespace spvtools {
namespace fuzz {
// A fuzzer pass to add OpPhi instructions which can take the values of ids that
// have been marked as synonymous. This instruction will itself be marked as
// synonymous with the others.
class FuzzerPassAddOpPhiSynonyms : public FuzzerPass {
public:
FuzzerPassAddOpPhiSynonyms(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations);
~FuzzerPassAddOpPhiSynonyms() override;
void Apply() override;
// Computes the equivalence classes for the non-pointer and non-irrelevant ids
// in the module, where two ids are considered equivalent iff they have been
// declared synonymous and they have the same type.
std::vector<std::set<uint32_t>> GetIdEquivalenceClasses();
// Returns true iff |equivalence_class| contains at least
// |distinct_ids_required| ids so that all of these ids are available at the
// end of at least one predecessor of the block with label |block_id|.
// Assumes that the block has at least one predecessor.
bool EquivalenceClassIsSuitableForBlock(
const std::set<uint32_t>& equivalence_class, uint32_t block_id,
uint32_t distinct_ids_required);
// Returns a vector with the ids that are available to use at the end of the
// block with id |pred_id|, selected among the given |ids|. Assumes that
// |pred_id| is the label of a block and all ids in |ids| exist in the module.
std::vector<uint32_t> GetSuitableIds(const std::set<uint32_t>& ids,
uint32_t pred_id);
private:
// Randomly chooses one of the equivalence classes in |candidates|, so that it
// satisfies all of the following conditions:
// - For each of the predecessors of the |block_id| block, there is at least
// one id in the chosen equivalence class that is available at the end of
// it.
// - There are at least |distinct_ids_required| ids available at the end of
// some predecessor.
// Returns nullptr if no equivalence class in |candidates| satisfies the
// requirements.
std::set<uint32_t>* MaybeFindSuitableEquivalenceClassRandomly(
const std::vector<std::set<uint32_t>*>& candidates, uint32_t block_id,
uint32_t distinct_ids_required);
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FUZZER_PASS_ADD_OPPHI_SYNONYMS_

View File

@ -414,6 +414,7 @@ message Transformation {
TransformationPropagateInstructionUp propagate_instruction_up = 67;
TransformationCompositeInsert composite_insert = 68;
TransformationInlineFunction inline_function = 69;
TransformationAddOpPhiSynonym add_opphi_synonym = 70;
// Add additional option using the next available number.
}
}
@ -746,6 +747,24 @@ message TransformationAddNoContractionDecoration {
}
message TransformationAddOpPhiSynonym {
// Adds an OpPhi instruction at the start of a block with n predecessors (pred_1, pred_2, ..., pred_n)
// and n related ids (id_1, id_2, ..., id_n) which are pairwise synonymous.
// The instruction will be of the form:
// %fresh_id = OpPhi %type %id_1 %pred_1 %id_2 %pred_2 ... %id_n %pred_n
// and fresh_id will be recorded as being synonymous with all the other ids.
// Label id of the block
uint32 block_id = 1;
// Pairs (pred_i, id_i)
repeated UInt32Pair pred_to_id = 2;
// Fresh id for the new instruction
uint32 fresh_id = 3;
}
message TransformationAddParameter {
// Adds a new parameter into the function.

View File

@ -33,6 +33,7 @@
#include "source/fuzz/transformation_add_local_variable.h"
#include "source/fuzz/transformation_add_loop_preheader.h"
#include "source/fuzz/transformation_add_no_contraction_decoration.h"
#include "source/fuzz/transformation_add_opphi_synonym.h"
#include "source/fuzz/transformation_add_parameter.h"
#include "source/fuzz/transformation_add_relaxed_decoration.h"
#include "source/fuzz/transformation_add_spec_constant_op.h"
@ -141,6 +142,9 @@ std::unique_ptr<Transformation> Transformation::FromMessage(
kAddNoContractionDecoration:
return MakeUnique<TransformationAddNoContractionDecoration>(
message.add_no_contraction_decoration());
case protobufs::Transformation::TransformationCase::kAddOpphiSynonym:
return MakeUnique<TransformationAddOpPhiSynonym>(
message.add_opphi_synonym());
case protobufs::Transformation::TransformationCase::kAddParameter:
return MakeUnique<TransformationAddParameter>(message.add_parameter());
case protobufs::Transformation::TransformationCase::kAddRelaxedDecoration:

View File

@ -0,0 +1,200 @@
// 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_add_opphi_synonym.h"
#include "source/fuzz/fuzzer_util.h"
namespace spvtools {
namespace fuzz {
TransformationAddOpPhiSynonym::TransformationAddOpPhiSynonym(
const protobufs::TransformationAddOpPhiSynonym& message)
: message_(message) {}
TransformationAddOpPhiSynonym::TransformationAddOpPhiSynonym(
uint32_t block_id, const std::map<uint32_t, uint32_t>& preds_to_ids,
uint32_t fresh_id) {
message_.set_block_id(block_id);
*message_.mutable_pred_to_id() =
fuzzerutil::MapToRepeatedUInt32Pair(preds_to_ids);
message_.set_fresh_id(fresh_id);
}
bool TransformationAddOpPhiSynonym::IsApplicable(
opt::IRContext* ir_context,
const TransformationContext& transformation_context) const {
// Check that |message_.block_id| is a block label id.
auto block = fuzzerutil::MaybeFindBlock(ir_context, message_.block_id());
if (!block) {
return false;
}
// Check that |message_.fresh_id| is actually fresh.
if (!fuzzerutil::IsFreshId(ir_context, message_.fresh_id())) {
return false;
}
// Check that |message_.pred_to_id| contains a mapping for all of the block's
// predecessors.
std::vector<uint32_t> predecessors = ir_context->cfg()->preds(block->id());
// There must be at least one predecessor.
if (predecessors.empty()) {
return false;
}
std::map<uint32_t, uint32_t> preds_to_ids =
fuzzerutil::RepeatedUInt32PairToMap(message_.pred_to_id());
// There must not be repeated key values in |message_.pred_to_id|.
if (preds_to_ids.size() != static_cast<size_t>(message_.pred_to_id_size())) {
return false;
}
// Check that each predecessor has a corresponding mapping and all of the
// corresponding ids exist.
for (uint32_t pred : predecessors) {
if (preds_to_ids.count(pred) == 0) {
return false;
}
// Check that the id exists in the module.
if (!ir_context->get_def_use_mgr()->GetDef(preds_to_ids[pred])) {
return false;
}
}
// Get the first id and its type (which should be the same as all the other
// ones) and check that the transformation supports this type.
uint32_t first_id = preds_to_ids[predecessors[0]];
uint32_t type_id = ir_context->get_def_use_mgr()->GetDef(first_id)->type_id();
if (!CheckTypeIsAllowed(ir_context, type_id)) {
return false;
}
// Check that the ids corresponding to predecessors are all synonymous, have
// the same type and are available to use at the end of the predecessor.
for (uint32_t pred : predecessors) {
auto id = preds_to_ids[pred];
// Check that the id has the same type as the other ones.
if (ir_context->get_def_use_mgr()->GetDef(id)->type_id() != type_id) {
return false;
}
// Check that the id is synonymous with the others by checking that it is
// synonymous with the first one (or it is the same id).
if (id != first_id &&
!transformation_context.GetFactManager()->IsSynonymous(
MakeDataDescriptor(id, {}), MakeDataDescriptor(first_id, {}))) {
return false;
}
// Check that the id is available at the end of the corresponding
// predecessor block.
auto pred_block = ir_context->get_instr_block(pred);
// We should always be able to find the predecessor block, since it is in
// the predecessors list of |block|.
assert(pred_block && "Could not find one of the predecessor blocks.");
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3722): This
// function always returns false if the block is unreachable, so it may
// need to be refactored.
if (!fuzzerutil::IdIsAvailableBeforeInstruction(
ir_context, pred_block->terminator(), id)) {
return false;
}
}
return true;
}
void TransformationAddOpPhiSynonym::Apply(
opt::IRContext* ir_context,
TransformationContext* transformation_context) const {
// Get the type id from one of the ids.
uint32_t first_id = message_.pred_to_id(0).second();
uint32_t type_id = ir_context->get_def_use_mgr()->GetDef(first_id)->type_id();
// Define the operand list.
opt::Instruction::OperandList operand_list;
// For each predecessor, add the corresponding operands.
for (auto& pair : message_.pred_to_id()) {
operand_list.emplace_back(
opt::Operand{SPV_OPERAND_TYPE_ID, {pair.second()}});
operand_list.emplace_back(
opt::Operand{SPV_OPERAND_TYPE_ID, {pair.first()}});
}
// Add a new OpPhi instructions at the beginning of the block.
ir_context->get_instr_block(message_.block_id())
->begin()
.InsertBefore(MakeUnique<opt::Instruction>(ir_context, SpvOpPhi, type_id,
message_.fresh_id(),
std::move(operand_list)));
// Update the module id bound.
fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
// Invalidate all analyses, since we added an instruction to the module.
ir_context->InvalidateAnalysesExceptFor(
opt::IRContext::Analysis::kAnalysisNone);
// Record the fact that the new id is synonym with the other ones by declaring
// that it is a synonym of the first one.
transformation_context->GetFactManager()->AddFactDataSynonym(
MakeDataDescriptor(message_.fresh_id(), {}),
MakeDataDescriptor(first_id, {}), ir_context);
}
protobufs::Transformation TransformationAddOpPhiSynonym::ToMessage() const {
protobufs::Transformation result;
*result.mutable_add_opphi_synonym() = message_;
return result;
}
bool TransformationAddOpPhiSynonym::CheckTypeIsAllowed(
opt::IRContext* ir_context, uint32_t type_id) {
auto type = ir_context->get_type_mgr()->GetType(type_id);
if (!type) {
return false;
}
// We allow the following types: Bool, Integer, Float, Vector, Matrix, Array,
// Struct.
if (type->AsBool() || type->AsInteger() || type->AsFloat() ||
type->AsVector() || type->AsMatrix() || type->AsArray() ||
type->AsStruct()) {
return true;
}
// We allow pointer types if the VariablePointers capability is enabled and
// the pointer has the correct storage class (Workgroup or StorageBuffer).
if (type->AsPointer()) {
auto storage_class = type->AsPointer()->storage_class();
return ir_context->get_feature_mgr()->HasCapability(
SpvCapabilityVariablePointers) &&
(storage_class == SpvStorageClassWorkgroup ||
storage_class == SpvStorageClassStorageBuffer);
}
// We do not allow other types.
return false;
}
} // namespace fuzz
} // namespace spvtools

View File

@ -0,0 +1,72 @@
// 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_ADD_OPPHI_SYNONYM_H_
#define SOURCE_FUZZ_TRANSFORMATION_ADD_OPPHI_SYNONYM_H_
#include "source/fuzz/transformation.h"
namespace spvtools {
namespace fuzz {
class TransformationAddOpPhiSynonym : public Transformation {
public:
explicit TransformationAddOpPhiSynonym(
const protobufs::TransformationAddOpPhiSynonym& message);
TransformationAddOpPhiSynonym(
uint32_t block_id, const std::map<uint32_t, uint32_t>& preds_to_ids,
uint32_t fresh_id);
// - |message_.block_id| is the label of a block with at least one
// predecessor.
// - |message_.pred_to_id| contains a mapping from each of the predecessors of
// the block to an id that is available at the end of the predecessor.
// - All the ids corresponding to a predecessor in |message_.pred_to_id|:
// - have been recorded as synonymous and all have the same type.
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3726): if a
// predecessor is a dead block, any id of the right type could be used,
// even if it is not synonym with the others.
// - have one of the following types: Bool, Integer, Float, Vector, Matrix,
// Array, Struct. Pointer types are also allowed if the VariablePointers
// capability is enabled and the storage class is Workgroup or
// StorageBuffer.
// - |message_.fresh_id| is a fresh id.
bool IsApplicable(
opt::IRContext* ir_context,
const TransformationContext& transformation_context) const override;
// Given a block with n predecessors, with n >= 1, and n corresponding
// synonymous ids of the same type, each available to use at the end of the
// corresponding predecessor, adds an OpPhi instruction at the beginning of
// the block of the form:
// %fresh_id = OpPhi %type %id_1 %pred_1 %id_2 %pred_2 ... %id_n %pred_n
// This instruction is then marked as synonymous with the ids.
void Apply(opt::IRContext* ir_context,
TransformationContext* transformation_context) const override;
// Returns true if |type_id| is the id of a type in the module, which is one
// of the following: Bool, Integer, Float, Vector, Matrix, Array, Struct.
// Pointer types are also allowed if the VariablePointers capability is
// enabled and the storage class is Workgroup or StorageBuffer.
static bool CheckTypeIsAllowed(opt::IRContext* ir_context, uint32_t type_id);
protobufs::Transformation ToMessage() const override;
private:
protobufs::TransformationAddOpPhiSynonym message_;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_TRANSFORMATION_ADD_OPPHI_SYNONYM_H_

View File

@ -21,6 +21,7 @@ if (${SPIRV_BUILD_FUZZER})
equivalence_relation_test.cpp
fact_manager_test.cpp
fuzz_test_util.cpp
fuzzer_pass_add_opphi_synonyms_test.cpp
fuzzer_pass_construct_composites_test.cpp
fuzzer_pass_donate_modules_test.cpp
fuzzer_pass_outline_functions_test.cpp
@ -42,6 +43,7 @@ if (${SPIRV_BUILD_FUZZER})
transformation_add_local_variable_test.cpp
transformation_add_loop_preheader_test.cpp
transformation_add_no_contraction_decoration_test.cpp
transformation_add_opphi_synonym_test.cpp
transformation_add_parameter_test.cpp
transformation_add_relaxed_decoration_test.cpp
transformation_add_synonym_test.cpp

View File

@ -0,0 +1,170 @@
// 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_add_opphi_synonyms.h"
#include "source/fuzz/pseudo_random_generator.h"
#include "test/fuzz/fuzz_test_util.h"
namespace spvtools {
namespace fuzz {
namespace {
protobufs::Fact MakeSynonymFact(uint32_t first, uint32_t second) {
protobufs::FactDataSynonym data_synonym_fact;
*data_synonym_fact.mutable_data1() = MakeDataDescriptor(first, {});
*data_synonym_fact.mutable_data2() = MakeDataDescriptor(second, {});
protobufs::Fact result;
*result.mutable_data_synonym_fact() = data_synonym_fact;
return result;
}
// Adds synonym facts to the fact manager.
void SetUpIdSynonyms(FactManager* fact_manager, opt::IRContext* context) {
// Synonyms {9, 11, 15, 16, 21, 22}
fact_manager->AddFact(MakeSynonymFact(11, 9), context);
fact_manager->AddFact(MakeSynonymFact(15, 9), context);
fact_manager->AddFact(MakeSynonymFact(16, 9), context);
fact_manager->AddFact(MakeSynonymFact(21, 9), context);
fact_manager->AddFact(MakeSynonymFact(22, 9), context);
// Synonyms {10, 23}
fact_manager->AddFact(MakeSynonymFact(10, 23), context);
// Synonyms {14, 27}
fact_manager->AddFact(MakeSynonymFact(14, 27), context);
// Synonyms {24, 26, 30}
fact_manager->AddFact(MakeSynonymFact(26, 24), context);
fact_manager->AddFact(MakeSynonymFact(30, 24), context);
}
// Returns true if the given lists have the same elements, regardless of their
// order.
template <typename T>
bool ListsHaveTheSameElements(const std::vector<T>& list1,
const std::vector<T>& list2) {
auto sorted1 = list1;
std::sort(sorted1.begin(), sorted1.end());
auto sorted2 = list2;
std::sort(sorted2.begin(), sorted2.end());
return sorted1 == sorted2;
}
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"
%3 = OpTypeVoid
%4 = OpTypeFunction %3
%5 = OpTypeBool
%6 = OpConstantTrue %5
%7 = OpTypeInt 32 1
%8 = OpTypeInt 32 0
%9 = OpConstant %7 1
%10 = OpConstant %7 2
%11 = OpConstant %8 1
%12 = OpTypePointer Function %7
%2 = OpFunction %3 None %4
%13 = OpLabel
%14 = OpVariable %12 Function
%15 = OpCopyObject %7 %9
%16 = OpCopyObject %8 %11
OpBranch %17
%17 = OpLabel
OpSelectionMerge %18 None
OpBranchConditional %6 %19 %20
%19 = OpLabel
%21 = OpCopyObject %7 %15
%22 = OpCopyObject %8 %16
%23 = OpCopyObject %7 %10
%24 = OpIAdd %7 %9 %10
OpBranch %18
%20 = OpLabel
OpBranch %18
%18 = OpLabel
%26 = OpIAdd %7 %15 %10
%27 = OpCopyObject %12 %14
OpSelectionMerge %28 None
OpBranchConditional %6 %29 %28
%29 = OpLabel
%30 = OpCopyObject %7 %26
OpBranch %28
%28 = OpLabel
OpReturn
OpFunctionEnd
)";
TEST(FuzzerPassAddOpPhiSynonymsTest, HelperFunctions) {
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);
PseudoRandomGenerator prng(0);
FuzzerContext fuzzer_context(&prng, 100);
protobufs::TransformationSequence transformation_sequence;
FuzzerPassAddOpPhiSynonyms fuzzer_pass(context.get(), &transformation_context,
&fuzzer_context,
&transformation_sequence);
SetUpIdSynonyms(&fact_manager, context.get());
std::vector<std::set<uint32_t>> expected_equivalence_classes = {
{9, 15, 21}, {11, 16, 22}, {10, 23}, {6}, {24, 26, 30}};
ASSERT_TRUE(ListsHaveTheSameElements<std::set<uint32_t>>(
fuzzer_pass.GetIdEquivalenceClasses(), expected_equivalence_classes));
// The set {24, 26, 30} is not suitable for 18 (none if the ids is available
// for predecessor 20).
ASSERT_FALSE(
fuzzer_pass.EquivalenceClassIsSuitableForBlock({24, 26, 30}, 18, 1));
// The set {6} is not suitable for 18 if we require at least 2 distinct
// available ids.
ASSERT_FALSE(fuzzer_pass.EquivalenceClassIsSuitableForBlock({6}, 18, 2));
// Only id 26 from the set {24, 26, 30} is available to use for the
// transformation at block 29, so the set is not suitable if we want at least
// 2 available ids.
ASSERT_FALSE(
fuzzer_pass.EquivalenceClassIsSuitableForBlock({24, 26, 30}, 29, 2));
ASSERT_TRUE(
fuzzer_pass.EquivalenceClassIsSuitableForBlock({24, 26, 30}, 29, 1));
// %21 is not available at the end of block 20.
ASSERT_TRUE(ListsHaveTheSameElements<uint32_t>(
fuzzer_pass.GetSuitableIds({9, 15, 21}, 20), {9, 15}));
// %24 and %30 are not available at the end of block 18.
ASSERT_TRUE(ListsHaveTheSameElements<uint32_t>(
fuzzer_pass.GetSuitableIds({24, 26, 30}, 18), {26}));
}
} // namespace
} // namespace fuzz
} // namespace spvtools

View File

@ -0,0 +1,423 @@
// 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_add_opphi_synonym.h"
#include "test/fuzz/fuzz_test_util.h"
namespace spvtools {
namespace fuzz {
namespace {
protobufs::Fact MakeSynonymFact(uint32_t first, uint32_t second) {
protobufs::FactDataSynonym data_synonym_fact;
*data_synonym_fact.mutable_data1() = MakeDataDescriptor(first, {});
*data_synonym_fact.mutable_data2() = MakeDataDescriptor(second, {});
protobufs::Fact result;
*result.mutable_data_synonym_fact() = data_synonym_fact;
return result;
}
// Adds synonym facts to the fact manager.
void SetUpIdSynonyms(FactManager* fact_manager, opt::IRContext* context) {
fact_manager->AddFact(MakeSynonymFact(11, 9), context);
fact_manager->AddFact(MakeSynonymFact(13, 9), context);
fact_manager->AddFact(MakeSynonymFact(14, 9), context);
fact_manager->AddFact(MakeSynonymFact(19, 9), context);
fact_manager->AddFact(MakeSynonymFact(20, 9), context);
fact_manager->AddFact(MakeSynonymFact(10, 21), context);
}
TEST(TransformationAddOpPhiSynonymTest, Inapplicable) {
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"
%3 = OpTypeVoid
%4 = OpTypeFunction %3
%5 = OpTypeBool
%6 = OpConstantTrue %5
%7 = OpTypeInt 32 1
%8 = OpTypeInt 32 0
%22 = OpTypePointer Function %7
%9 = OpConstant %7 1
%10 = OpConstant %7 2
%11 = OpConstant %8 1
%2 = OpFunction %3 None %4
%12 = OpLabel
%23 = OpVariable %22 Function
%13 = OpCopyObject %7 %9
%14 = OpCopyObject %8 %11
OpBranch %15
%15 = OpLabel
OpSelectionMerge %16 None
OpBranchConditional %6 %17 %18
%17 = OpLabel
%19 = OpCopyObject %7 %13
%20 = OpCopyObject %8 %14
%21 = OpCopyObject %7 %10
OpBranch %16
%18 = OpLabel
%24 = OpCopyObject %22 %23
OpBranch %16
%16 = OpLabel
OpReturn
OpFunctionEnd
)";
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);
SetUpIdSynonyms(&fact_manager, context.get());
fact_manager.AddFact(MakeSynonymFact(23, 24), context.get());
// %13 is not a block label.
ASSERT_FALSE(TransformationAddOpPhiSynonym(13, {{}}, 100)
.IsApplicable(context.get(), transformation_context));
// Block %12 does not have a predecessor.
ASSERT_FALSE(TransformationAddOpPhiSynonym(12, {{}}, 100)
.IsApplicable(context.get(), transformation_context));
// Not all predecessors of %16 (%17 and %18) are considered in the map.
ASSERT_FALSE(TransformationAddOpPhiSynonym(16, {{{17, 19}}}, 100)
.IsApplicable(context.get(), transformation_context));
// %30 does not exist in the module.
ASSERT_FALSE(TransformationAddOpPhiSynonym(16, {{{30, 19}}}, 100)
.IsApplicable(context.get(), transformation_context));
// %20 is not a block label.
ASSERT_FALSE(TransformationAddOpPhiSynonym(16, {{{20, 19}}}, 100)
.IsApplicable(context.get(), transformation_context));
// %15 is not the id of one of the predecessors of the block.
ASSERT_FALSE(TransformationAddOpPhiSynonym(16, {{{15, 19}}}, 100)
.IsApplicable(context.get(), transformation_context));
// %30 does not exist in the module.
ASSERT_FALSE(TransformationAddOpPhiSynonym(16, {{{17, 30}, {18, 13}}}, 100)
.IsApplicable(context.get(), transformation_context));
// %19 and %10 are not synonymous.
ASSERT_FALSE(TransformationAddOpPhiSynonym(16, {{{17, 19}, {18, 10}}}, 100)
.IsApplicable(context.get(), transformation_context));
// %19 and %14 do not have the same type.
ASSERT_FALSE(TransformationAddOpPhiSynonym(16, {{{17, 19}, {18, 14}}}, 100)
.IsApplicable(context.get(), transformation_context));
// %19 is not available at the end of %18.
ASSERT_FALSE(TransformationAddOpPhiSynonym(16, {{{17, 9}, {18, 19}}}, 100)
.IsApplicable(context.get(), transformation_context));
// %21 is not a fresh id.
ASSERT_FALSE(TransformationAddOpPhiSynonym(16, {{{17, 9}, {18, 9}}}, 21)
.IsApplicable(context.get(), transformation_context));
// %23 and %24 have pointer id.
ASSERT_FALSE(TransformationAddOpPhiSynonym(16, {{{17, 23}, {18, 24}}}, 100)
.IsApplicable(context.get(), transformation_context));
}
TEST(TransformationAddOpPhiSynonymTest, Apply) {
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"
%3 = OpTypeVoid
%4 = OpTypeFunction %3
%5 = OpTypeBool
%6 = OpConstantTrue %5
%7 = OpTypeInt 32 1
%8 = OpTypeInt 32 0
%9 = OpConstant %7 1
%10 = OpConstant %7 2
%11 = OpConstant %8 1
%2 = OpFunction %3 None %4
%12 = OpLabel
%13 = OpCopyObject %7 %9
%14 = OpCopyObject %8 %11
OpBranch %15
%15 = OpLabel
OpSelectionMerge %16 None
OpBranchConditional %6 %17 %18
%17 = OpLabel
%19 = OpCopyObject %7 %13
%20 = OpCopyObject %8 %14
%21 = OpCopyObject %7 %10
OpBranch %16
%18 = OpLabel
OpBranch %16
%16 = OpLabel
OpBranch %22
%22 = OpLabel
OpLoopMerge %23 %24 None
OpBranchConditional %6 %25 %23
%25 = OpLabel
OpSelectionMerge %26 None
OpBranchConditional %6 %27 %26
%27 = OpLabel
%28 = OpCopyObject %7 %13
OpBranch %23
%26 = OpLabel
OpSelectionMerge %29 None
OpBranchConditional %6 %29 %24
%29 = OpLabel
%30 = OpCopyObject %7 %13
OpBranch %23
%24 = OpLabel
OpBranch %22
%23 = OpLabel
OpSelectionMerge %31 None
OpBranchConditional %6 %31 %31
%31 = OpLabel
OpReturn
OpFunctionEnd
)";
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);
SetUpIdSynonyms(&fact_manager, context.get());
// Add some further synonym facts.
fact_manager.AddFact(MakeSynonymFact(28, 9), context.get());
fact_manager.AddFact(MakeSynonymFact(30, 9), context.get());
auto transformation1 = TransformationAddOpPhiSynonym(17, {{{15, 13}}}, 100);
ASSERT_TRUE(
transformation1.IsApplicable(context.get(), transformation_context));
transformation1.Apply(context.get(), &transformation_context);
ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(100, {}),
MakeDataDescriptor(9, {})));
auto transformation2 =
TransformationAddOpPhiSynonym(16, {{{17, 19}, {18, 13}}}, 101);
ASSERT_TRUE(
transformation2.IsApplicable(context.get(), transformation_context));
transformation2.Apply(context.get(), &transformation_context);
ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(101, {}),
MakeDataDescriptor(9, {})));
auto transformation3 =
TransformationAddOpPhiSynonym(23, {{{22, 13}, {27, 28}, {29, 30}}}, 102);
ASSERT_TRUE(
transformation3.IsApplicable(context.get(), transformation_context));
transformation3.Apply(context.get(), &transformation_context);
ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(102, {}),
MakeDataDescriptor(9, {})));
auto transformation4 = TransformationAddOpPhiSynonym(31, {{{23, 13}}}, 103);
ASSERT_TRUE(
transformation4.IsApplicable(context.get(), transformation_context));
transformation4.Apply(context.get(), &transformation_context);
ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(103, {}),
MakeDataDescriptor(9, {})));
ASSERT_TRUE(IsValid(env, context.get()));
std::string after_transformations = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %2 "main"
OpExecutionMode %2 OriginUpperLeft
OpSource ESSL 310
OpName %2 "main"
%3 = OpTypeVoid
%4 = OpTypeFunction %3
%5 = OpTypeBool
%6 = OpConstantTrue %5
%7 = OpTypeInt 32 1
%8 = OpTypeInt 32 0
%9 = OpConstant %7 1
%10 = OpConstant %7 2
%11 = OpConstant %8 1
%2 = OpFunction %3 None %4
%12 = OpLabel
%13 = OpCopyObject %7 %9
%14 = OpCopyObject %8 %11
OpBranch %15
%15 = OpLabel
OpSelectionMerge %16 None
OpBranchConditional %6 %17 %18
%17 = OpLabel
%100 = OpPhi %7 %13 %15
%19 = OpCopyObject %7 %13
%20 = OpCopyObject %8 %14
%21 = OpCopyObject %7 %10
OpBranch %16
%18 = OpLabel
OpBranch %16
%16 = OpLabel
%101 = OpPhi %7 %19 %17 %13 %18
OpBranch %22
%22 = OpLabel
OpLoopMerge %23 %24 None
OpBranchConditional %6 %25 %23
%25 = OpLabel
OpSelectionMerge %26 None
OpBranchConditional %6 %27 %26
%27 = OpLabel
%28 = OpCopyObject %7 %13
OpBranch %23
%26 = OpLabel
OpSelectionMerge %29 None
OpBranchConditional %6 %29 %24
%29 = OpLabel
%30 = OpCopyObject %7 %13
OpBranch %23
%24 = OpLabel
OpBranch %22
%23 = OpLabel
%102 = OpPhi %7 %13 %22 %28 %27 %30 %29
OpSelectionMerge %31 None
OpBranchConditional %6 %31 %31
%31 = OpLabel
%103 = OpPhi %7 %13 %23
OpReturn
OpFunctionEnd
)";
ASSERT_TRUE(IsEqual(env, after_transformations, context.get()));
}
TEST(TransformationAddOpPhiSynonymTest, VariablePointers) {
std::string shader = R"(
OpCapability Shader
OpCapability VariablePointers
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %2 "main" %3
OpExecutionMode %2 OriginUpperLeft
OpSource ESSL 310
%4 = OpTypeVoid
%5 = OpTypeFunction %4
%6 = OpTypeBool
%7 = OpConstantTrue %6
%8 = OpTypeInt 32 1
%9 = OpTypePointer Function %8
%10 = OpTypePointer Workgroup %8
%3 = OpVariable %10 Workgroup
%2 = OpFunction %4 None %5
%11 = OpLabel
%12 = OpVariable %9 Function
OpSelectionMerge %13 None
OpBranchConditional %7 %14 %13
%14 = OpLabel
%15 = OpCopyObject %10 %3
%16 = OpCopyObject %9 %12
OpBranch %13
%13 = OpLabel
OpReturn
OpFunctionEnd
)";
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);
// Declare synonyms
fact_manager.AddFact(MakeSynonymFact(3, 15), context.get());
fact_manager.AddFact(MakeSynonymFact(12, 16), context.get());
// Remove the VariablePointers capability.
context.get()->get_feature_mgr()->RemoveCapability(
SpvCapabilityVariablePointers);
// The VariablePointers capability is required to add an OpPhi instruction of
// pointer type.
ASSERT_FALSE(TransformationAddOpPhiSynonym(13, {{{11, 3}, {14, 15}}}, 100)
.IsApplicable(context.get(), transformation_context));
// Add the VariablePointers capability back.
context.get()->get_feature_mgr()->AddCapability(
SpvCapabilityVariablePointers);
// If the ids have pointer type, the storage class must be Workgroup or
// StorageBuffer, but it is Function in this case.
ASSERT_FALSE(TransformationAddOpPhiSynonym(13, {{{11, 12}, {14, 16}}}, 100)
.IsApplicable(context.get(), transformation_context));
auto transformation =
TransformationAddOpPhiSynonym(13, {{{11, 3}, {14, 15}}}, 100);
ASSERT_TRUE(
transformation.IsApplicable(context.get(), transformation_context));
transformation.Apply(context.get(), &transformation_context);
std::string after_transformation = R"(
OpCapability Shader
OpCapability VariablePointers
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %2 "main" %3
OpExecutionMode %2 OriginUpperLeft
OpSource ESSL 310
%4 = OpTypeVoid
%5 = OpTypeFunction %4
%6 = OpTypeBool
%7 = OpConstantTrue %6
%8 = OpTypeInt 32 1
%9 = OpTypePointer Function %8
%10 = OpTypePointer Workgroup %8
%3 = OpVariable %10 Workgroup
%2 = OpFunction %4 None %5
%11 = OpLabel
%12 = OpVariable %9 Function
OpSelectionMerge %13 None
OpBranchConditional %7 %14 %13
%14 = OpLabel
%15 = OpCopyObject %10 %3
%16 = OpCopyObject %9 %12
OpBranch %13
%13 = OpLabel
%100 = OpPhi %10 %3 %11 %15 %14
OpReturn
OpFunctionEnd
)";
ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
}
} // namespace
} // namespace fuzz
} // namespace spvtools