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.
diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt
index fbabba1..49ee843 100644
--- a/source/fuzz/CMakeLists.txt
+++ b/source/fuzz/CMakeLists.txt
@@ -26,6 +26,7 @@
)
set(SPIRV_TOOLS_FUZZ_SOURCES
+ data_descriptor.h
fact_manager.h
fuzzer.h
fuzzer_context.h
@@ -52,6 +53,7 @@
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 @@
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 @@
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
diff --git a/source/fuzz/data_descriptor.cpp b/source/fuzz/data_descriptor.cpp
new file mode 100644
index 0000000..9cdb2c5
--- /dev/null
+++ b/source/fuzz/data_descriptor.cpp
@@ -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
diff --git a/source/fuzz/data_descriptor.h b/source/fuzz/data_descriptor.h
new file mode 100644
index 0000000..731bd21
--- /dev/null
+++ b/source/fuzz/data_descriptor.h
@@ -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_
diff --git a/source/fuzz/fact_manager.cpp b/source/fuzz/fact_manager.cpp
index 442ff16..61daa64 100644
--- a/source/fuzz/fact_manager.cpp
+++ b/source/fuzz/fact_manager.cpp
@@ -68,6 +68,9 @@
} // 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 @@
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 @@
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 @@
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
diff --git a/source/fuzz/fact_manager.h b/source/fuzz/fact_manager.h
index cb4ac58..f6ea247 100644
--- a/source/fuzz/fact_manager.h
+++ b/source/fuzz/fact_manager.h
@@ -16,6 +16,7 @@
#define SOURCE_FUZZ_FACT_MANAGER_H_
#include <memory>
+#include <set>
#include <utility>
#include <vector>
@@ -51,13 +52,12 @@
// 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 @@
// 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 @@
// 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
diff --git a/source/fuzz/fuzzer_util.cpp b/source/fuzz/fuzzer_util.cpp
index 9a05c74..9972e47 100644
--- a/source/fuzz/fuzzer_util.cpp
+++ b/source/fuzz/fuzzer_util.cpp
@@ -170,6 +170,41 @@
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
diff --git a/source/fuzz/fuzzer_util.h b/source/fuzz/fuzzer_util.h
index 15228de..47588b0 100644
--- a/source/fuzz/fuzzer_util.h
+++ b/source/fuzz/fuzzer_util.h
@@ -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 @@
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
diff --git a/source/fuzz/protobufs/spvtoolsfuzz.proto b/source/fuzz/protobufs/spvtoolsfuzz.proto
index 13d8a05..4e8dcac 100644
--- a/source/fuzz/protobufs/spvtoolsfuzz.proto
+++ b/source/fuzz/protobufs/spvtoolsfuzz.proto
@@ -57,6 +57,22 @@
}
+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 @@
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 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 @@
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 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 TransformationReplaceBooleanConstantWithConstantBinary {
+
// A transformation to capture replacing a use of a boolean constant with
// binary operation on two constant values
@@ -313,13 +368,14 @@
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
diff --git a/source/fuzz/transformation_copy_object.cpp b/source/fuzz/transformation_copy_object.cpp
new file mode 100644
index 0000000..f9ead43
--- /dev/null
+++ b/source/fuzz/transformation_copy_object.cpp
@@ -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
diff --git a/source/fuzz/transformation_copy_object.h b/source/fuzz/transformation_copy_object.h
new file mode 100644
index 0000000..6ce72df
--- /dev/null
+++ b/source/fuzz/transformation_copy_object.h
@@ -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_
diff --git a/source/fuzz/transformation_split_block.cpp b/source/fuzz/transformation_split_block.cpp
index a8c33de..a2da371 100644
--- a/source/fuzz/transformation_split_block.cpp
+++ b/source/fuzz/transformation_split_block.cpp
@@ -26,147 +26,104 @@
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 {
diff --git a/source/fuzz/transformation_split_block.h b/source/fuzz/transformation_split_block.h
index ef4aa75..4a7095a 100644
--- a/source/fuzz/transformation_split_block.h
+++ b/source/fuzz/transformation_split_block.h
@@ -28,13 +28,13 @@
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 @@
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_;
};
diff --git a/source/fuzz/uniform_buffer_element_descriptor.cpp b/source/fuzz/uniform_buffer_element_descriptor.cpp
index 8c758e4..90fd85e 100644
--- a/source/fuzz/uniform_buffer_element_descriptor.cpp
+++ b/source/fuzz/uniform_buffer_element_descriptor.cpp
@@ -14,7 +14,7 @@
#include "source/fuzz/uniform_buffer_element_descriptor.h"
-#include <source/opt/instruction.h>
+#include <algorithm>
namespace spvtools {
namespace fuzz {
diff --git a/source/fuzz/uniform_buffer_element_descriptor.h b/source/fuzz/uniform_buffer_element_descriptor.h
index 23a16f0..d35de57 100644
--- a/source/fuzz/uniform_buffer_element_descriptor.h
+++ b/source/fuzz/uniform_buffer_element_descriptor.h
@@ -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);
diff --git a/test/fuzz/CMakeLists.txt b/test/fuzz/CMakeLists.txt
index c0e2925..6a101dd 100644
--- a/test/fuzz/CMakeLists.txt
+++ b/test/fuzz/CMakeLists.txt
@@ -30,6 +30,7 @@
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
diff --git a/test/fuzz/transformation_copy_object_test.cpp b/test/fuzz/transformation_copy_object_test.cpp
new file mode 100644
index 0000000..0c214c8
--- /dev/null
+++ b/test/fuzz/transformation_copy_object_test.cpp
@@ -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