Add 'copy object' transformation (#2766)

This transformation can introduce an instruction that uses
OpCopyObject to make a copy of some other result id.  This change
introduces the transformation, but does not yet introduce a fuzzer
pass to actually apply it.
This commit is contained in:
Alastair Donaldson 2019-08-05 18:00:13 +01:00 committed by GitHub
parent 4f14b4c8cc
commit 698b56a8f0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 1121 additions and 150 deletions

View File

@ -26,6 +26,7 @@ if(SPIRV_BUILD_FUZZER)
)
set(SPIRV_TOOLS_FUZZ_SOURCES
data_descriptor.h
fact_manager.h
fuzzer.h
fuzzer_context.h
@ -52,6 +53,7 @@ if(SPIRV_BUILD_FUZZER)
transformation_add_type_float.h
transformation_add_type_int.h
transformation_add_type_pointer.h
transformation_copy_object.h
transformation_move_block_down.h
transformation_replace_boolean_constant_with_constant_binary.h
transformation_replace_constant_with_uniform.h
@ -59,6 +61,7 @@ if(SPIRV_BUILD_FUZZER)
uniform_buffer_element_descriptor.h
${CMAKE_CURRENT_BINARY_DIR}/protobufs/spvtoolsfuzz.pb.h
data_descriptor.cpp
fact_manager.cpp
fuzzer.cpp
fuzzer_context.cpp
@ -84,6 +87,7 @@ if(SPIRV_BUILD_FUZZER)
transformation_add_type_float.cpp
transformation_add_type_int.cpp
transformation_add_type_pointer.cpp
transformation_copy_object.cpp
transformation_move_block_down.cpp
transformation_replace_boolean_constant_with_constant_binary.cpp
transformation_replace_constant_with_uniform.cpp

View File

@ -0,0 +1,42 @@
// Copyright (c) 2019 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/data_descriptor.h"
#include <algorithm>
namespace spvtools {
namespace fuzz {
protobufs::DataDescriptor MakeDataDescriptor(uint32_t object,
std::vector<uint32_t>&& indices) {
protobufs::DataDescriptor result;
result.set_object(object);
for (auto index : indices) {
result.add_index(index);
}
return result;
}
bool DataDescriptorEquals::operator()(
const protobufs::DataDescriptor* first,
const protobufs::DataDescriptor* second) const {
return first->object() == second->object() &&
first->index().size() == second->index().size() &&
std::equal(first->index().begin(), first->index().end(),
second->index().begin());
}
} // namespace fuzz
} // namespace spvtools

View File

@ -0,0 +1,39 @@
// Copyright (c) 2019 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_DATA_DESCRIPTOR_H_
#define SOURCE_FUZZ_DATA_DESCRIPTOR_H_
#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
#include <vector>
namespace spvtools {
namespace fuzz {
// Factory method to create a data descriptor message from an object id and a
// list of indices.
protobufs::DataDescriptor MakeDataDescriptor(uint32_t object,
std::vector<uint32_t>&& indices);
// Equality function for data descriptors.
struct DataDescriptorEquals {
bool operator()(const protobufs::DataDescriptor* first,
const protobufs::DataDescriptor* second) const;
};
} // namespace fuzz
} // namespace spvtools
#endif // #define SOURCE_FUZZ_DATA_DESCRIPTOR_H_

View File

@ -68,6 +68,9 @@ std::string ToString(const protobufs::Fact& fact) {
} // namespace
//=======================
// Constant uniform facts
// The purpose of this struct is to group the fields and data used to represent
// facts about uniform constants.
struct FactManager::ConstantUniformFacts {
@ -330,10 +333,44 @@ bool FactManager::ConstantUniformFacts::AddFact(
return true;
}
FactManager::FactManager() {
uniform_constant_facts_ = MakeUnique<ConstantUniformFacts>();
// End of uniform constant facts
//==============================
//==============================
// Id synonym facts
// The purpose of this struct is to group the fields and data used to represent
// facts about id synonyms.
struct FactManager::IdSynonymFacts {
// See method in FactManager which delegates to this method.
void AddFact(const protobufs::FactIdSynonym& fact);
// A record of all the synonyms that are available.
std::map<uint32_t, std::vector<protobufs::DataDescriptor>> synonyms;
// The set of keys to the above map; useful if you just want to know which ids
// have synonyms.
std::set<uint32_t> ids_with_synonyms;
};
void FactManager::IdSynonymFacts::AddFact(
const protobufs::FactIdSynonym& fact) {
if (synonyms.count(fact.id()) == 0) {
assert(ids_with_synonyms.count(fact.id()) == 0);
ids_with_synonyms.insert(fact.id());
synonyms[fact.id()] = std::vector<protobufs::DataDescriptor>();
}
assert(ids_with_synonyms.count(fact.id()) == 1);
synonyms[fact.id()].push_back(fact.data_descriptor());
}
// End of id synonym facts
//==============================
FactManager::FactManager()
: uniform_constant_facts_(MakeUnique<ConstantUniformFacts>()),
id_synonym_facts_(MakeUnique<IdSynonymFacts>()) {}
FactManager::~FactManager() = default;
void FactManager::AddFacts(const MessageConsumer& message_consumer,
@ -350,13 +387,17 @@ void FactManager::AddFacts(const MessageConsumer& message_consumer,
bool FactManager::AddFact(const spvtools::fuzz::protobufs::Fact& fact,
spvtools::opt::IRContext* context) {
assert(fact.fact_case() == protobufs::Fact::kConstantUniformFact &&
"Right now this is the only fact.");
if (!uniform_constant_facts_->AddFact(fact.constant_uniform_fact(),
context)) {
return false;
switch (fact.fact_case()) {
case protobufs::Fact::kConstantUniformFact:
return uniform_constant_facts_->AddFact(fact.constant_uniform_fact(),
context);
case protobufs::Fact::kIdSynonymFact:
id_synonym_facts_->AddFact(fact.id_synonym_fact());
return true;
default:
assert(false && "Unknown fact type.");
return false;
}
return true;
}
std::vector<uint32_t> FactManager::GetConstantsAvailableFromUniformsForType(
@ -389,5 +430,14 @@ FactManager::GetConstantUniformFactsAndTypes() const {
return uniform_constant_facts_->facts_and_type_ids;
}
const std::set<uint32_t>& FactManager::GetIdsForWhichSynonymsAreKnown() const {
return id_synonym_facts_->ids_with_synonyms;
}
const std::vector<protobufs::DataDescriptor>& FactManager::GetSynonymsForId(
uint32_t id) const {
return id_synonym_facts_->synonyms.at(id);
}
} // namespace fuzz
} // namespace spvtools

View File

@ -16,6 +16,7 @@
#define SOURCE_FUZZ_FACT_MANAGER_H_
#include <memory>
#include <set>
#include <utility>
#include <vector>
@ -51,13 +52,12 @@ class FactManager {
// fact manager.
bool AddFact(const protobufs::Fact& fact, opt::IRContext* context);
// The fact manager will ultimately be responsible for managing a few distinct
// categories of facts. In principle there could be different fact managers
// for each kind of fact, but in practice providing one 'go to' place for
// facts will be convenient. To keep some separation, the public methods of
// the fact manager should be grouped according to the kind of fact to which
// they relate. At present we only have one kind of fact: facts about
// uniform variables.
// The fact manager is responsible for managing a few distinct categories of
// facts. In principle there could be different fact managers for each kind
// of fact, but in practice providing one 'go to' place for facts is
// convenient. To keep some separation, the public methods of the fact
// manager should be grouped according to the kind of fact to which they
// relate.
//==============================
// Querying facts about uniform constants
@ -96,6 +96,21 @@ class FactManager {
// End of uniform constant facts
//==============================
//==============================
// Querying facts about id synonyms
// Returns every id for which a fact of the form "this id is synonymous
// with this piece of data" is known.
const std::set<uint32_t>& GetIdsForWhichSynonymsAreKnown() const;
// Requires that at least one synonym for |id| is known, and returns the
// sequence of all known synonyms.
const std::vector<protobufs::DataDescriptor>& GetSynonymsForId(
uint32_t id) const;
// End of id synonym facts
//==============================
private:
// For each distinct kind of fact to be managed, we use a separate opaque
// struct type.
@ -104,6 +119,10 @@ class FactManager {
// buffer elements.
std::unique_ptr<ConstantUniformFacts>
uniform_constant_facts_; // Unique pointer to internal data.
struct IdSynonymFacts; // Opaque struct for holding data about id synonyms.
std::unique_ptr<IdSynonymFacts>
id_synonym_facts_; // Unique pointer to internal data.
};
} // namespace fuzz

View File

@ -170,6 +170,41 @@ bool BlockIsInLoopContinueConstruct(opt::IRContext* context, uint32_t block_id,
return false;
}
opt::BasicBlock::iterator GetIteratorForBaseInstructionAndOffset(
opt::BasicBlock* block, const opt::Instruction* base_inst,
uint32_t offset) {
// The cases where |base_inst| is the block's label, vs. inside the block,
// are dealt with separately.
if (base_inst == block->GetLabelInst()) {
// |base_inst| is the block's label.
if (offset == 0) {
// We cannot return an iterator to the block's label.
return block->end();
}
// Conceptually, the first instruction in the block is [label + 1].
// We thus start from 1 when applying the offset.
auto inst_it = block->begin();
for (uint32_t i = 1; i < offset && inst_it != block->end(); i++) {
++inst_it;
}
// This is either the desired instruction, or the end of the block.
return inst_it;
}
// |base_inst| is inside the block.
for (auto inst_it = block->begin(); inst_it != block->end(); ++inst_it) {
if (base_inst == &*inst_it) {
// We have found the base instruction; we now apply the offset.
for (uint32_t i = 0; i < offset && inst_it != block->end(); i++) {
++inst_it;
}
// This is either the desired instruction, or the end of the block.
return inst_it;
}
}
assert(false && "The base instruction was not found.");
return nullptr;
}
} // namespace fuzzerutil
} // namespace fuzz

View File

@ -18,6 +18,8 @@
#include <vector>
#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
#include "source/opt/basic_block.h"
#include "source/opt/instruction.h"
#include "source/opt/ir_context.h"
namespace spvtools {
@ -62,6 +64,16 @@ void AddUnreachableEdgeAndUpdateOpPhis(
bool BlockIsInLoopContinueConstruct(opt::IRContext* context, uint32_t block_id,
uint32_t maybe_loop_header_id);
// Requires that |base_inst| is either the label instruction of |block| or an
// instruction inside |block|.
//
// If the block contains a (non-label, non-terminator) instruction |offset|
// instructions after |base_inst|, an iterator to this instruction is returned.
//
// Otherwise |block|->end() is returned.
opt::BasicBlock::iterator GetIteratorForBaseInstructionAndOffset(
opt::BasicBlock* block, const opt::Instruction* base_inst, uint32_t offset);
} // namespace fuzzerutil
} // namespace fuzz

View File

@ -57,6 +57,22 @@ message IdUseDescriptor {
}
message DataDescriptor {
// Represents a data element that can be accessed from an id, by walking the
// type hierarchy via a sequence of 0 or more indices.
//
// Very similar to a UniformBufferElementDescriptor, except that a
// DataDescriptor is rooted at the id of a scalar or composite.
// The object being accessed - a scalar or composite
uint32 object = 1;
// 0 or more indices, used to index into a composite object
repeated uint32 index = 2;
}
message UniformBufferElementDescriptor {
// Represents a data element inside a uniform buffer. The element is
@ -97,6 +113,7 @@ message Fact {
oneof fact {
// Order the fact options by numeric id (rather than alphabetically).
FactConstantUniform constant_uniform_fact = 1;
FactIdSynonym id_synonym_fact = 2;
}
}
@ -118,6 +135,22 @@ message FactConstantUniform {
}
message FactIdSynonym {
// Records the fact that the data held in an id is guaranteed to be equal to
// the data held in a data descriptor. spirv-fuzz can use this to replace
// uses of the id with references to the data described by the data
// descriptor.
// An id
uint32 id = 1;
// A data descriptor guaranteed to hold a value identical to that held by the
// id
DataDescriptor data_descriptor = 2;
}
message TransformationSequence {
repeated Transformation transformation = 1;
}
@ -138,6 +171,7 @@ message Transformation {
TransformationAddTypePointer add_type_pointer = 10;
TransformationReplaceConstantWithUniform replace_constant_with_uniform = 11;
TransformationAddDeadContinue add_dead_continue = 12;
TransformationCopyObject copy_object = 13;
// Add additional option using the next available number.
}
}
@ -262,6 +296,26 @@ message TransformationAddTypePointer {
}
message TransformationCopyObject {
// A transformation that introduces an OpCopyObject instruction to make a
// copy of an object.
// Id of the object to be copied
uint32 object = 1;
// The id of an instruction in a block
uint32 base_instruction_id = 2;
// An offset, such that OpCopyObject instruction should be inserted right
// before the instruction |offset| instructions after |base_instruction_id|
uint32 offset = 3;
// A fresh id for the copied object
uint32 fresh_id = 4;
}
message TransformationMoveBlockDown {
// A transformation that moves a basic block to be one position lower in
@ -291,6 +345,7 @@ message TransformationReplaceConstantWithUniform {
}
message TransformationReplaceBooleanConstantWithConstantBinary {
// A transformation to capture replacing a use of a boolean constant with
// binary operation on two constant values
@ -313,13 +368,14 @@ message TransformationReplaceBooleanConstantWithConstantBinary {
message TransformationSplitBlock {
// A transformation that splits a basic block into two basic blocks.
// A transformation that splits a basic block into two basic blocks
// The result id of an instruction.
uint32 result_id = 1;
// The result id of an instruction
uint32 base_instruction_id = 1;
// An offset, such that the block containing |result_id_| should be split
// right before the instruction |offset_| instructions after |result_id_|.
// An offset, such that the block containing |base_instruction_id| should be
// split right before the instruction |offset| instructions after
// |base_instruction_id|
uint32 offset = 2;
// An id that must not yet be used by the module to which this transformation

View File

@ -0,0 +1,158 @@
// Copyright (c) 2019 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_copy_object.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/opt/instruction.h"
#include "source/util/make_unique.h"
namespace spvtools {
namespace fuzz {
TransformationCopyObject::TransformationCopyObject(
const protobufs::TransformationCopyObject& message)
: message_(message) {}
TransformationCopyObject::TransformationCopyObject(uint32_t object,
uint32_t base_instruction_id,
uint32_t offset,
uint32_t fresh_id) {
message_.set_object(object);
message_.set_base_instruction_id(base_instruction_id);
message_.set_offset(offset);
message_.set_fresh_id(fresh_id);
}
bool TransformationCopyObject::IsApplicable(
opt::IRContext* context, const FactManager& /*fact_manager*/) const {
if (!fuzzerutil::IsFreshId(context, message_.fresh_id())) {
// We require the id for the object copy to be unused.
return false;
}
// The id of the object to be copied must exist
auto object_inst = context->get_def_use_mgr()->GetDef(message_.object());
if (!object_inst) {
return false;
}
if (!object_inst->type_id()) {
// We can only apply OpCopyObject to instructions that have types.
return false;
}
if (!context->get_decoration_mgr()
->GetDecorationsFor(message_.object(), true)
.empty()) {
// We do not copy objects that have decorations: if the copy is not
// decorated analogously, using the original object vs. its copy may not be
// equivalent.
// TODO(afd): it would be possible to make the copy but not add an id
// synonym.
return false;
}
auto base_instruction =
context->get_def_use_mgr()->GetDef(message_.base_instruction_id());
if (!base_instruction) {
// The given id to insert after is not defined.
return false;
}
auto destination_block = context->get_instr_block(base_instruction);
if (!destination_block) {
// The given id to insert after is not in a block.
return false;
}
auto insert_before = fuzzerutil::GetIteratorForBaseInstructionAndOffset(
destination_block, base_instruction, message_.offset());
if (insert_before == destination_block->end()) {
// The offset was inappropriate.
return false;
}
if (insert_before->PreviousNode() &&
(insert_before->PreviousNode()->opcode() == SpvOpLoopMerge ||
insert_before->PreviousNode()->opcode() == SpvOpSelectionMerge)) {
// We cannot insert a copy directly after a merge instruction.
return false;
}
if (insert_before->opcode() == SpvOpVariable) {
// We cannot insert a copy directly before a variable; variables in a
// function must be contiguous in the entry block.
return false;
}
// We cannot insert a copy directly before OpPhi, because OpPhi instructions
// need to be contiguous at the start of a block.
if (insert_before->opcode() == SpvOpPhi) {
return false;
}
// |message_object| must be available at the point where we want to add the
// copy. It is available if it is at global scope (in which case it has no
// block), or if it dominates the point of insertion but is different from the
// point of insertion.
//
// The reason why the object needs to be different from the insertion point is
// that the copy will be added *before* this point, and we do not want to
// insert it before the object's defining instruction.
return !context->get_instr_block(object_inst) ||
(object_inst != &*insert_before &&
context->GetDominatorAnalysis(destination_block->GetParent())
->Dominates(object_inst, &*insert_before));
}
void TransformationCopyObject::Apply(opt::IRContext* context,
FactManager* fact_manager) const {
// - A new instruction,
// %|message_.fresh_id| = OpCopyObject %ty %|message_.object|
// is added directly before the instruction at |message_.insert_after_id| +
// |message_|.offset, where %ty is the type of |message_.object|.
// - The fact that |message_.fresh_id| and |message_.object| are synonyms
// is added to the fact manager.
// The id of the object to be copied must exist
auto object_inst = context->get_def_use_mgr()->GetDef(message_.object());
assert(object_inst && "The object to be copied must exist.");
auto base_instruction =
context->get_def_use_mgr()->GetDef(message_.base_instruction_id());
assert(base_instruction && "The base instruction must exist.");
auto destination_block = context->get_instr_block(base_instruction);
assert(destination_block && "The base instruction must be in a block.");
auto insert_before = fuzzerutil::GetIteratorForBaseInstructionAndOffset(
destination_block, base_instruction, message_.offset());
assert(insert_before != destination_block->end() &&
"There must be an instruction before which the copy can be inserted.");
opt::Instruction::OperandList operands = {
{SPV_OPERAND_TYPE_ID, {message_.object()}}};
insert_before->InsertBefore(MakeUnique<opt::Instruction>(
context, SpvOp::SpvOpCopyObject, object_inst->type_id(),
message_.fresh_id(), operands));
fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id());
context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone);
protobufs::Fact fact;
fact.mutable_id_synonym_fact()->set_id(message_.object());
fact.mutable_id_synonym_fact()->mutable_data_descriptor()->set_object(
message_.fresh_id());
fact_manager->AddFact(fact, context);
}
protobufs::Transformation TransformationCopyObject::ToMessage() const {
protobufs::Transformation result;
*result.mutable_copy_object() = message_;
return result;
}
} // namespace fuzz
} // namespace spvtools

View File

@ -0,0 +1,68 @@
// Copyright (c) 2019 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_COPY_OBJECT_H_
#define SOURCE_FUZZ_TRANSFORMATION_COPY_OBJECT_H_
#include "source/fuzz/fact_manager.h"
#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
#include "source/fuzz/transformation.h"
#include "source/opt/ir_context.h"
namespace spvtools {
namespace fuzz {
class TransformationCopyObject : public Transformation {
public:
explicit TransformationCopyObject(
const protobufs::TransformationCopyObject& message);
TransformationCopyObject(uint32_t fresh_id, uint32_t object,
uint32_t insert_after_id, uint32_t offset);
// - |message_.fresh_id| must not be used by the module.
// - |message_.object| must be a result id that is a legitimate operand for
// OpCopyObject. In particular, it must be the id of an instruction that
// has a result type
// - |message_.object| must not be the target of any decoration.
// TODO(afd): consider copying decorations along with objects.
// - |message_.insert_after_id| must be the result id of an instruction
// 'base' in some block 'blk'.
// - 'blk' must contain an instruction 'inst' located |message_.offset|
// instructions after 'base' (if |message_.offset| = 0 then 'inst' =
// 'base').
// - It must be legal to insert an OpCopyObject instruction directly
// before 'inst'.
// - |message_object| must be available directly before 'inst'.
bool IsApplicable(opt::IRContext* context,
const FactManager& fact_manager) const override;
// - A new instruction,
// %|message_.fresh_id| = OpCopyObject %ty %|message_.object|
// is added directly before the instruction at |message_.insert_after_id| +
// |message_|.offset, where %ty is the type of |message_.object|.
// - The fact that |message_.fresh_id| and |message_.object| are synonyms
// is added to the fact manager.
void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
protobufs::Transformation ToMessage() const override;
private:
protobufs::TransformationCopyObject message_;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_TRANSFORMATION_COPY_OBJECT_H_

View File

@ -26,147 +26,104 @@ TransformationSplitBlock::TransformationSplitBlock(
const spvtools::fuzz::protobufs::TransformationSplitBlock& message)
: message_(message) {}
TransformationSplitBlock::TransformationSplitBlock(uint32_t result_id,
TransformationSplitBlock::TransformationSplitBlock(uint32_t base_instruction_id,
uint32_t offset,
uint32_t fresh_id) {
message_.set_result_id(result_id);
message_.set_base_instruction_id(base_instruction_id);
message_.set_offset(offset);
message_.set_fresh_id(fresh_id);
}
std::pair<bool, opt::BasicBlock::iterator>
TransformationSplitBlock::FindInstToSplitBefore(opt::BasicBlock* block) const {
// There are three possibilities:
// (1) the transformation wants to split at some offset from the block's
// label.
// (2) the transformation wants to split at some offset from a
// non-label instruction inside the block.
// (3) the split assocaiated with this transformation has nothing to do with
// this block
if (message_.result_id() == block->id()) {
// Case (1).
if (message_.offset() == 0) {
// The offset is not allowed to be 0: this would mean splitting before the
// block's label.
// By returning (true, block->end()), we indicate that we did find the
// instruction (so that it is not worth searching further for it), but
// that splitting will not be possible.
return {true, block->end()};
}
// Conceptually, the first instruction in the block is [label + 1].
// We thus start from 1 when applying the offset.
auto inst_it = block->begin();
for (uint32_t i = 1; i < message_.offset() && inst_it != block->end();
i++) {
++inst_it;
}
// This is either the desired instruction, or the end of the block.
return {true, inst_it};
}
for (auto inst_it = block->begin(); inst_it != block->end(); ++inst_it) {
if (message_.result_id() == inst_it->result_id()) {
// Case (2): we have found the base instruction; we now apply the offset.
for (uint32_t i = 0; i < message_.offset() && inst_it != block->end();
i++) {
++inst_it;
}
// This is either the desired instruction, or the end of the block.
return {true, inst_it};
}
}
// Case (3).
return {false, block->end()};
}
bool TransformationSplitBlock::IsApplicable(
opt::IRContext* context, const FactManager& /*unused*/) const {
if (!fuzzerutil::IsFreshId(context, message_.fresh_id())) {
// We require the id for the new block to be unused.
return false;
}
// Consider every block in every function.
for (auto& function : *context->module()) {
for (auto& block : function) {
auto maybe_split_before = FindInstToSplitBefore(&block);
if (!maybe_split_before.first) {
continue;
}
if (maybe_split_before.second == block.end()) {
// The base instruction was found, but the offset was inappropriate.
return false;
}
if (block.IsLoopHeader()) {
// We cannot split a loop header block: back-edges would become invalid.
return false;
}
auto split_before = maybe_split_before.second;
if (split_before->PreviousNode() &&
split_before->PreviousNode()->opcode() == SpvOpSelectionMerge) {
// We cannot split directly after a selection merge: this would separate
// the merge from its associated branch or switch operation.
return false;
}
if (split_before->opcode() == SpvOpVariable) {
// We cannot split directly after a variable; variables in a function
// must be contiguous in the entry block.
return false;
}
if (split_before->opcode() == SpvOpPhi &&
split_before->NumInOperands() != 2) {
// We cannot split before an OpPhi unless the OpPhi has exactly one
// associated incoming edge.
return false;
}
return true;
}
auto base_instruction =
context->get_def_use_mgr()->GetDef(message_.base_instruction_id());
if (!base_instruction) {
// The instruction describing the block we should split does not exist.
return false;
}
return false;
auto block_containing_base_instruction =
context->get_instr_block(base_instruction);
if (!block_containing_base_instruction) {
// The instruction describing the block we should split is not contained in
// a block.
return false;
}
if (block_containing_base_instruction->IsLoopHeader()) {
// We cannot split a loop header block: back-edges would become invalid.
return false;
}
auto split_before = fuzzerutil::GetIteratorForBaseInstructionAndOffset(
block_containing_base_instruction, base_instruction, message_.offset());
if (split_before == block_containing_base_instruction->end()) {
// The offset was inappropriate.
return false;
}
if (split_before->PreviousNode() &&
split_before->PreviousNode()->opcode() == SpvOpSelectionMerge) {
// We cannot split directly after a selection merge: this would separate
// the merge from its associated branch or switch operation.
return false;
}
if (split_before->opcode() == SpvOpVariable) {
// We cannot split directly after a variable; variables in a function
// must be contiguous in the entry block.
return false;
}
// We cannot split before an OpPhi unless the OpPhi has exactly one
// associated incoming edge.
return !(split_before->opcode() == SpvOpPhi &&
split_before->NumInOperands() != 2);
}
void TransformationSplitBlock::Apply(opt::IRContext* context,
FactManager* /*unused*/) const {
for (auto& function : *context->module()) {
for (auto& block : function) {
auto maybe_split_before = FindInstToSplitBefore(&block);
if (!maybe_split_before.first) {
continue;
}
assert(maybe_split_before.second != block.end() &&
"If the transformation is applicable, we should have an "
"instruction to split on.");
// We need to make sure the module's id bound is large enough to add the
// fresh id.
fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id());
// Split the block.
auto new_bb = block.SplitBasicBlock(context, message_.fresh_id(),
maybe_split_before.second);
// The split does not automatically add a branch between the two parts of
// the original block, so we add one.
block.AddInstruction(MakeUnique<opt::Instruction>(
auto base_instruction =
context->get_def_use_mgr()->GetDef(message_.base_instruction_id());
assert(base_instruction && "Base instruction must exist");
auto block_containing_base_instruction =
context->get_instr_block(base_instruction);
assert(block_containing_base_instruction &&
"Base instruction must be in a block");
auto split_before = fuzzerutil::GetIteratorForBaseInstructionAndOffset(
block_containing_base_instruction, base_instruction, message_.offset());
assert(split_before != block_containing_base_instruction->end() &&
"If the transformation is applicable, we should have an "
"instruction to split on.");
// We need to make sure the module's id bound is large enough to add the
// fresh id.
fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id());
// Split the block.
auto new_bb = block_containing_base_instruction->SplitBasicBlock(
context, message_.fresh_id(), split_before);
// The split does not automatically add a branch between the two parts of
// the original block, so we add one.
block_containing_base_instruction->AddInstruction(
MakeUnique<opt::Instruction>(
context, SpvOpBranch, 0, 0,
std::initializer_list<opt::Operand>{
opt::Operand(spv_operand_type_t::SPV_OPERAND_TYPE_ID,
{message_.fresh_id()})}));
// If we split before OpPhi instructions, we need to update their
// predecessor operand so that the block they used to be inside is now the
// predecessor.
new_bb->ForEachPhiInst([&block](opt::Instruction* phi_inst) {
// If we split before OpPhi instructions, we need to update their
// predecessor operand so that the block they used to be inside is now the
// predecessor.
new_bb->ForEachPhiInst(
[block_containing_base_instruction](opt::Instruction* phi_inst) {
// The following assertion is a sanity check. It is guaranteed to hold
// if IsApplicable holds.
assert(phi_inst->NumInOperands() == 2 &&
"We can only split a block before an OpPhi if block has exactly "
"one predecessor.");
phi_inst->SetInOperand(1, {block.id()});
phi_inst->SetInOperand(1, {block_containing_base_instruction->id()});
});
// Invalidate all analyses
context->InvalidateAnalysesExceptFor(
opt::IRContext::Analysis::kAnalysisNone);
return;
}
}
assert(0 &&
"Should be unreachable: it should have been possible to apply this "
"transformation.");
// Invalidate all analyses
context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone);
}
protobufs::Transformation TransformationSplitBlock::ToMessage() const {

View File

@ -28,13 +28,13 @@ class TransformationSplitBlock : public Transformation {
explicit TransformationSplitBlock(
const protobufs::TransformationSplitBlock& message);
TransformationSplitBlock(uint32_t result_id, uint32_t offset,
TransformationSplitBlock(uint32_t base_instruction_id, uint32_t offset,
uint32_t fresh_id);
// - |message_.result_id| must be the result id of an instruction 'base' in
// some block 'blk'.
// - |message_.base_instruction_id| must be the result id of an instruction
// 'base' in some block 'blk'.
// - 'blk' must contain an instruction 'inst' located |message_.offset|
// instructions after 'inst' (if |message_.offset| = 0 then 'inst' =
// instructions after 'base' (if |message_.offset| = 0 then 'inst' =
// 'base').
// - Splitting 'blk' at 'inst', so that all instructions from 'inst' onwards
// appear in a new block that 'blk' directly jumps to must be valid.
@ -52,14 +52,6 @@ class TransformationSplitBlock : public Transformation {
protobufs::Transformation ToMessage() const override;
private:
// Returns:
// - (true, block->end()) if the relevant instruction is in this block
// but inapplicable
// - (true, it) if 'it' is an iterator for the relevant instruction
// - (false, _) otherwise.
std::pair<bool, opt::BasicBlock::iterator> FindInstToSplitBefore(
opt::BasicBlock* block) const;
protobufs::TransformationSplitBlock message_;
};

View File

@ -14,7 +14,7 @@
#include "source/fuzz/uniform_buffer_element_descriptor.h"
#include <source/opt/instruction.h>
#include <algorithm>
namespace spvtools {
namespace fuzz {

View File

@ -15,7 +15,6 @@
#ifndef SOURCE_FUZZ_UNIFORM_BUFFER_ELEMENT_DESCRIPTOR_H_
#define SOURCE_FUZZ_UNIFORM_BUFFER_ELEMENT_DESCRIPTOR_H_
#include <algorithm>
#include <vector>
#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
@ -25,8 +24,8 @@
namespace spvtools {
namespace fuzz {
// Factory method to create a uniform buffer element descriptor message from an
// id and list of indices.
// Factory method to create a uniform buffer element descriptor message from
// descriptor set and binding ids and a list of indices.
protobufs::UniformBufferElementDescriptor MakeUniformBufferElementDescriptor(
uint32_t descriptor_set, uint32_t binding, std::vector<uint32_t>&& indices);

View File

@ -30,6 +30,7 @@ if (${SPIRV_BUILD_FUZZER})
transformation_add_type_float_test.cpp
transformation_add_type_int_test.cpp
transformation_add_type_pointer_test.cpp
transformation_copy_object_test.cpp
transformation_move_block_down_test.cpp
transformation_replace_boolean_constant_with_constant_binary_test.cpp
transformation_replace_constant_with_uniform_test.cpp

View File

@ -0,0 +1,539 @@
// Copyright (c) 2019 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_copy_object.h"
#include "source/fuzz/data_descriptor.h"
#include "test/fuzz/fuzz_test_util.h"
namespace spvtools {
namespace fuzz {
namespace {
TEST(TransformationCopyObjectTest, CopyBooleanConstants) {
std::string shader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
OpName %4 "main"
%2 = OpTypeVoid
%6 = OpTypeBool
%7 = OpConstantTrue %6
%8 = OpConstantFalse %6
%3 = OpTypeFunction %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_EQ(0, fact_manager.GetIdsForWhichSynonymsAreKnown().size());
TransformationCopyObject copy_true(7, 5, 1, 100);
ASSERT_TRUE(copy_true.IsApplicable(context.get(), fact_manager));
copy_true.Apply(context.get(), &fact_manager);
const std::set<uint32_t>& ids_for_which_synonyms_are_known =
fact_manager.GetIdsForWhichSynonymsAreKnown();
ASSERT_EQ(1, ids_for_which_synonyms_are_known.size());
ASSERT_TRUE(ids_for_which_synonyms_are_known.find(7) !=
ids_for_which_synonyms_are_known.end());
ASSERT_EQ(1, fact_manager.GetSynonymsForId(7).size());
protobufs::DataDescriptor descriptor_100 = MakeDataDescriptor(100, {});
ASSERT_TRUE(DataDescriptorEquals()(&descriptor_100,
&fact_manager.GetSynonymsForId(7)[0]));
TransformationCopyObject copy_false(8, 100, 1, 101);
ASSERT_TRUE(copy_false.IsApplicable(context.get(), fact_manager));
copy_false.Apply(context.get(), &fact_manager);
ASSERT_EQ(2, ids_for_which_synonyms_are_known.size());
ASSERT_TRUE(ids_for_which_synonyms_are_known.find(8) !=
ids_for_which_synonyms_are_known.end());
ASSERT_EQ(1, fact_manager.GetSynonymsForId(8).size());
protobufs::DataDescriptor descriptor_101 = MakeDataDescriptor(101, {});
ASSERT_TRUE(DataDescriptorEquals()(&descriptor_101,
&fact_manager.GetSynonymsForId(8)[0]));
TransformationCopyObject copy_false_again(101, 5, 3, 102);
ASSERT_TRUE(copy_false_again.IsApplicable(context.get(), fact_manager));
copy_false_again.Apply(context.get(), &fact_manager);
ASSERT_EQ(3, ids_for_which_synonyms_are_known.size());
ASSERT_TRUE(ids_for_which_synonyms_are_known.find(101) !=
ids_for_which_synonyms_are_known.end());
ASSERT_EQ(1, fact_manager.GetSynonymsForId(101).size());
protobufs::DataDescriptor descriptor_102 = MakeDataDescriptor(102, {});
ASSERT_TRUE(DataDescriptorEquals()(&descriptor_102,
&fact_manager.GetSynonymsForId(101)[0]));
TransformationCopyObject copy_true_again(7, 102, 1, 103);
ASSERT_TRUE(copy_true_again.IsApplicable(context.get(), fact_manager));
copy_true_again.Apply(context.get(), &fact_manager);
// This does re-uses an id for which synonyms are already known, so the count
// of such ids does not change.
ASSERT_EQ(3, ids_for_which_synonyms_are_known.size());
ASSERT_TRUE(ids_for_which_synonyms_are_known.find(7) !=
ids_for_which_synonyms_are_known.end());
ASSERT_EQ(2, fact_manager.GetSynonymsForId(7).size());
protobufs::DataDescriptor descriptor_103 = MakeDataDescriptor(103, {});
ASSERT_TRUE(DataDescriptorEquals()(&descriptor_103,
&fact_manager.GetSynonymsForId(7)[0]) ||
DataDescriptorEquals()(&descriptor_103,
&fact_manager.GetSynonymsForId(7)[1]));
std::string after_transformation = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
OpName %4 "main"
%2 = OpTypeVoid
%6 = OpTypeBool
%7 = OpConstantTrue %6
%8 = OpConstantFalse %6
%3 = OpTypeFunction %2
%4 = OpFunction %2 None %3
%5 = OpLabel
%100 = OpCopyObject %6 %7
%101 = OpCopyObject %6 %8
%102 = OpCopyObject %6 %101
%103 = OpCopyObject %6 %7
OpReturn
OpFunctionEnd
)";
ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
}
TEST(TransformationCopyObjectTest, CheckIllegalCases) {
// The following SPIR-V comes from this GLSL, pushed through spirv-opt
// and then doctored a bit.
//
// #version 310 es
//
// precision highp float;
//
// struct S {
// int a;
// float b;
// };
//
// layout(set = 0, binding = 2) uniform block {
// S s;
// lowp float f;
// int ii;
// } ubuf;
//
// layout(location = 0) out vec4 color;
//
// void main() {
// float c = 0.0;
// lowp float d = 0.0;
// S localS = ubuf.s;
// for (int i = 0; i < ubuf.s.a; i++) {
// switch (ubuf.ii) {
// case 0:
// c += 0.1;
// d += 0.2;
// case 1:
// c += 0.1;
// if (c > d) {
// d += 0.2;
// } else {
// d += c;
// }
// break;
// default:
// i += 1;
// localS.b += d;
// }
// }
// color = vec4(c, d, localS.b, 1.0);
// }
std::string shader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main" %80
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
OpName %4 "main"
OpName %12 "S"
OpMemberName %12 0 "a"
OpMemberName %12 1 "b"
OpName %15 "S"
OpMemberName %15 0 "a"
OpMemberName %15 1 "b"
OpName %16 "block"
OpMemberName %16 0 "s"
OpMemberName %16 1 "f"
OpMemberName %16 2 "ii"
OpName %18 "ubuf"
OpName %80 "color"
OpMemberDecorate %12 0 RelaxedPrecision
OpMemberDecorate %15 0 RelaxedPrecision
OpMemberDecorate %15 0 Offset 0
OpMemberDecorate %15 1 Offset 4
OpMemberDecorate %16 0 Offset 0
OpMemberDecorate %16 1 RelaxedPrecision
OpMemberDecorate %16 1 Offset 16
OpMemberDecorate %16 2 RelaxedPrecision
OpMemberDecorate %16 2 Offset 20
OpDecorate %16 Block
OpDecorate %18 DescriptorSet 0
OpDecorate %18 Binding 2
OpDecorate %38 RelaxedPrecision
OpDecorate %43 RelaxedPrecision
OpDecorate %53 RelaxedPrecision
OpDecorate %62 RelaxedPrecision
OpDecorate %69 RelaxedPrecision
OpDecorate %77 RelaxedPrecision
OpDecorate %80 Location 0
OpDecorate %101 RelaxedPrecision
OpDecorate %102 RelaxedPrecision
OpDecorate %96 RelaxedPrecision
OpDecorate %108 RelaxedPrecision
OpDecorate %107 RelaxedPrecision
OpDecorate %98 RelaxedPrecision
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeFloat 32
%9 = OpConstant %6 0
%11 = OpTypeInt 32 1
%12 = OpTypeStruct %11 %6
%15 = OpTypeStruct %11 %6
%16 = OpTypeStruct %15 %6 %11
%17 = OpTypePointer Uniform %16
%18 = OpVariable %17 Uniform
%19 = OpConstant %11 0
%20 = OpTypePointer Uniform %15
%27 = OpConstant %11 1
%36 = OpTypePointer Uniform %11
%39 = OpTypeBool
%41 = OpConstant %11 2
%48 = OpConstant %6 0.100000001
%51 = OpConstant %6 0.200000003
%78 = OpTypeVector %6 4
%79 = OpTypePointer Output %78
%80 = OpVariable %79 Output
%85 = OpConstant %6 1
%95 = OpUndef %12
%112 = OpTypePointer Uniform %6
%113 = OpTypeInt 32 0
%114 = OpConstant %113 1
%179 = OpTypePointer Function %39
%4 = OpFunction %2 None %3
%5 = OpLabel
%180 = OpVariable %179 Function
%181 = OpVariable %179 Function
%182 = OpVariable %179 Function
%21 = OpAccessChain %20 %18 %19
%115 = OpAccessChain %112 %21 %114
%116 = OpLoad %6 %115
%90 = OpCompositeInsert %12 %116 %95 1
OpBranch %30
%30 = OpLabel
%99 = OpPhi %12 %90 %5 %109 %47
%98 = OpPhi %6 %9 %5 %107 %47
%97 = OpPhi %6 %9 %5 %105 %47
%96 = OpPhi %11 %19 %5 %77 %47
%37 = OpAccessChain %36 %18 %19 %19
%38 = OpLoad %11 %37
%40 = OpSLessThan %39 %96 %38
OpLoopMerge %32 %47 None
OpBranchConditional %40 %31 %32
%31 = OpLabel
%42 = OpAccessChain %36 %18 %41
%43 = OpLoad %11 %42
OpSelectionMerge %47 None
OpSwitch %43 %46 0 %44 1 %45
%46 = OpLabel
%69 = OpIAdd %11 %96 %27
%72 = OpCompositeExtract %6 %99 1
%73 = OpFAdd %6 %72 %98
%93 = OpCompositeInsert %12 %73 %99 1
OpBranch %47
%44 = OpLabel
%50 = OpFAdd %6 %97 %48
%53 = OpFAdd %6 %98 %51
OpBranch %45
%45 = OpLabel
%101 = OpPhi %6 %98 %31 %53 %44
%100 = OpPhi %6 %97 %31 %50 %44
%55 = OpFAdd %6 %100 %48
%58 = OpFOrdGreaterThan %39 %55 %101
OpSelectionMerge %60 None
OpBranchConditional %58 %59 %63
%59 = OpLabel
%62 = OpFAdd %6 %101 %51
OpBranch %60
%63 = OpLabel
%66 = OpFAdd %6 %101 %55
OpBranch %60
%60 = OpLabel
%108 = OpPhi %6 %62 %59 %66 %63
OpBranch %47
%47 = OpLabel
%109 = OpPhi %12 %93 %46 %99 %60
%107 = OpPhi %6 %98 %46 %108 %60
%105 = OpPhi %6 %97 %46 %55 %60
%102 = OpPhi %11 %69 %46 %96 %60
%77 = OpIAdd %11 %102 %27
OpBranch %30
%32 = OpLabel
%84 = OpCompositeExtract %6 %99 1
%86 = OpCompositeConstruct %78 %97 %98 %84 %85
OpStore %80 %86
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;
// Inapplicable because %18 is decorated.
ASSERT_FALSE(TransformationCopyObject(18, 21, 0, 200)
.IsApplicable(context.get(), fact_manager));
// Inapplicable because %77 is decorated.
ASSERT_FALSE(TransformationCopyObject(17, 17, 1, 200)
.IsApplicable(context.get(), fact_manager));
// Inapplicable because %80 is decorated.
ASSERT_FALSE(TransformationCopyObject(80, 77, 0, 200)
.IsApplicable(context.get(), fact_manager));
// Inapplicable because %84 is not available at the requested point
ASSERT_FALSE(TransformationCopyObject(84, 32, 1, 200)
.IsApplicable(context.get(), fact_manager));
// Fine because %84 is available at the requested point
ASSERT_TRUE(TransformationCopyObject(84, 32, 2, 200)
.IsApplicable(context.get(), fact_manager));
// Inapplicable because id %9 is already in use
ASSERT_FALSE(TransformationCopyObject(84, 32, 2, 9)
.IsApplicable(context.get(), fact_manager));
// Inapplicable because the requested point is not in a block
ASSERT_FALSE(TransformationCopyObject(84, 86, 3, 200)
.IsApplicable(context.get(), fact_manager));
// Inapplicable because %9 is not in a function
ASSERT_FALSE(TransformationCopyObject(9, 9, 1, 200)
.IsApplicable(context.get(), fact_manager));
// Inapplicable because %9 is not in a function
ASSERT_FALSE(TransformationCopyObject(9, 9, 1, 200)
.IsApplicable(context.get(), fact_manager));
// Inapplicable because the insert point is right before, or inside, a chunk
// of OpPhis
ASSERT_FALSE(TransformationCopyObject(9, 30, 1, 200)
.IsApplicable(context.get(), fact_manager));
ASSERT_FALSE(TransformationCopyObject(9, 99, 1, 200)
.IsApplicable(context.get(), fact_manager));
// OK, because the insert point is just after a chunk of OpPhis.
ASSERT_TRUE(TransformationCopyObject(9, 96, 1, 200)
.IsApplicable(context.get(), fact_manager));
// Inapplicable because the insert point is right after an OpSelectionMerge
ASSERT_FALSE(TransformationCopyObject(9, 58, 2, 200)
.IsApplicable(context.get(), fact_manager));
// OK, because the insert point is right before the OpSelectionMerge
ASSERT_TRUE(TransformationCopyObject(9, 58, 1, 200)
.IsApplicable(context.get(), fact_manager));
// Inapplicable because the insert point is right after an OpSelectionMerge
ASSERT_FALSE(TransformationCopyObject(9, 43, 2, 200)
.IsApplicable(context.get(), fact_manager));
// OK, because the insert point is right before the OpSelectionMerge
ASSERT_TRUE(TransformationCopyObject(9, 43, 1, 200)
.IsApplicable(context.get(), fact_manager));
// Inapplicable because the insert point is right after an OpLoopMerge
ASSERT_FALSE(TransformationCopyObject(9, 40, 2, 200)
.IsApplicable(context.get(), fact_manager));
// OK, because the insert point is right before the OpLoopMerge
ASSERT_TRUE(TransformationCopyObject(9, 40, 1, 200)
.IsApplicable(context.get(), fact_manager));
// Inapplicable because id %300 does not exist
ASSERT_FALSE(TransformationCopyObject(300, 40, 1, 200)
.IsApplicable(context.get(), fact_manager));
// Inapplicable because the following instruction is OpVariable
ASSERT_FALSE(TransformationCopyObject(9, 180, 0, 200)
.IsApplicable(context.get(), fact_manager));
ASSERT_FALSE(TransformationCopyObject(9, 181, 0, 200)
.IsApplicable(context.get(), fact_manager));
ASSERT_FALSE(TransformationCopyObject(9, 182, 0, 200)
.IsApplicable(context.get(), fact_manager));
// OK, because this is just past the group of OpVariable instructions.
ASSERT_TRUE(TransformationCopyObject(9, 182, 1, 200)
.IsApplicable(context.get(), fact_manager));
}
TEST(TransformationCopyObjectTest, MiscellaneousCopies) {
// The following SPIR-V comes from this GLSL:
//
// #version 310 es
//
// precision highp float;
//
// float g;
//
// vec4 h;
//
// void main() {
// int a;
// int b;
// b = int(g);
// h.x = float(a);
// }
std::string shader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
OpName %4 "main"
OpName %8 "b"
OpName %11 "g"
OpName %16 "h"
OpName %17 "a"
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeInt 32 1
%7 = OpTypePointer Function %6
%9 = OpTypeFloat 32
%10 = OpTypePointer Private %9
%11 = OpVariable %10 Private
%14 = OpTypeVector %9 4
%15 = OpTypePointer Private %14
%16 = OpVariable %15 Private
%20 = OpTypeInt 32 0
%21 = OpConstant %20 0
%4 = OpFunction %2 None %3
%5 = OpLabel
%8 = OpVariable %7 Function
%17 = OpVariable %7 Function
%12 = OpLoad %9 %11
%13 = OpConvertFToS %6 %12
OpStore %8 %13
%18 = OpLoad %6 %17
%19 = OpConvertSToF %9 %18
%22 = OpAccessChain %10 %16 %21
OpStore %22 %19
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;
std::vector<TransformationCopyObject> transformations = {
TransformationCopyObject(19, 22, 1, 100),
TransformationCopyObject(22, 22, 1, 101),
TransformationCopyObject(12, 22, 1, 102),
TransformationCopyObject(11, 22, 1, 103),
TransformationCopyObject(16, 22, 1, 104),
TransformationCopyObject(8, 22, 1, 105),
TransformationCopyObject(17, 22, 1, 106)};
for (auto& transformation : transformations) {
ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
transformation.Apply(context.get(), &fact_manager);
}
ASSERT_TRUE(IsValid(env, context.get()));
std::string after_transformation = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
OpName %4 "main"
OpName %8 "b"
OpName %11 "g"
OpName %16 "h"
OpName %17 "a"
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeInt 32 1
%7 = OpTypePointer Function %6
%9 = OpTypeFloat 32
%10 = OpTypePointer Private %9
%11 = OpVariable %10 Private
%14 = OpTypeVector %9 4
%15 = OpTypePointer Private %14
%16 = OpVariable %15 Private
%20 = OpTypeInt 32 0
%21 = OpConstant %20 0
%4 = OpFunction %2 None %3
%5 = OpLabel
%8 = OpVariable %7 Function
%17 = OpVariable %7 Function
%12 = OpLoad %9 %11
%13 = OpConvertFToS %6 %12
OpStore %8 %13
%18 = OpLoad %6 %17
%19 = OpConvertSToF %9 %18
%22 = OpAccessChain %10 %16 %21
%106 = OpCopyObject %7 %17
%105 = OpCopyObject %7 %8
%104 = OpCopyObject %15 %16
%103 = OpCopyObject %10 %11
%102 = OpCopyObject %9 %12
%101 = OpCopyObject %10 %22
%100 = OpCopyObject %9 %19
OpStore %22 %19
OpReturn
OpFunctionEnd
)";
ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
}
} // namespace
} // namespace fuzz
} // namespace spvtools