mirror of
https://github.com/KhronosGroup/SPIRV-Tools
synced 2024-10-18 11:10:05 +00:00
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:
parent
4f14b4c8cc
commit
698b56a8f0
@ -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
|
||||
|
42
source/fuzz/data_descriptor.cpp
Normal file
42
source/fuzz/data_descriptor.cpp
Normal 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
|
39
source/fuzz/data_descriptor.h
Normal file
39
source/fuzz/data_descriptor.h
Normal 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_
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
158
source/fuzz/transformation_copy_object.cpp
Normal file
158
source/fuzz/transformation_copy_object.cpp
Normal 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
|
68
source/fuzz/transformation_copy_object.h
Normal file
68
source/fuzz/transformation_copy_object.h
Normal 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_
|
@ -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 {
|
||||
|
@ -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_;
|
||||
};
|
||||
|
||||
|
@ -14,7 +14,7 @@
|
||||
|
||||
#include "source/fuzz/uniform_buffer_element_descriptor.h"
|
||||
|
||||
#include <source/opt/instruction.h>
|
||||
#include <algorithm>
|
||||
|
||||
namespace spvtools {
|
||||
namespace fuzz {
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
|
||||
|
539
test/fuzz/transformation_copy_object_test.cpp
Normal file
539
test/fuzz/transformation_copy_object_test.cpp
Normal 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
|
Loading…
Reference in New Issue
Block a user