diff --git a/3rdparty/spirv-tools/include/generated/build-version.inc b/3rdparty/spirv-tools/include/generated/build-version.inc index dfa665a4d..d03dbba53 100644 --- a/3rdparty/spirv-tools/include/generated/build-version.inc +++ b/3rdparty/spirv-tools/include/generated/build-version.inc @@ -1 +1 @@ -"v2020.5", "SPIRV-Tools v2020.5 46c9719f1b46f358df41b11c2575182cb5befbd5" +"v2020.5", "SPIRV-Tools v2020.5 e272a31e19b278158551bc64142947df09ede4ac" diff --git a/3rdparty/spirv-tools/source/fuzz/CMakeLists.txt b/3rdparty/spirv-tools/source/fuzz/CMakeLists.txt index db32902fa..905d53572 100644 --- a/3rdparty/spirv-tools/source/fuzz/CMakeLists.txt +++ b/3rdparty/spirv-tools/source/fuzz/CMakeLists.txt @@ -30,9 +30,15 @@ if(SPIRV_BUILD_FUZZER) set(SPIRV_TOOLS_FUZZ_SOURCES call_graph.h + counter_overflow_id_source.h data_descriptor.h equivalence_relation.h - fact_manager.h + fact_manager/constant_uniform_facts.h + fact_manager/data_synonym_and_id_equation_facts.h + fact_manager/dead_block_facts.h + fact_manager/fact_manager.h + fact_manager/irrelevant_value_facts.h + fact_manager/livesafe_function_facts.h force_render_red.h fuzzer.h fuzzer_context.h @@ -53,6 +59,7 @@ if(SPIRV_BUILD_FUZZER) fuzzer_pass_add_local_variables.h fuzzer_pass_add_loop_preheaders.h fuzzer_pass_add_no_contraction_decorations.h + fuzzer_pass_add_opphi_synonyms.h fuzzer_pass_add_parameters.h fuzzer_pass_add_relaxed_decorations.h fuzzer_pass_add_stores.h @@ -66,6 +73,7 @@ if(SPIRV_BUILD_FUZZER) fuzzer_pass_construct_composites.h fuzzer_pass_copy_objects.h fuzzer_pass_donate_modules.h + fuzzer_pass_inline_functions.h fuzzer_pass_invert_comparison_operators.h fuzzer_pass_interchange_signedness_of_integer_operands.h fuzzer_pass_interchange_zero_like_constants.h @@ -94,6 +102,7 @@ if(SPIRV_BUILD_FUZZER) id_use_descriptor.h instruction_descriptor.h instruction_message.h + overflow_id_source.h protobufs/spirvfuzz_protobufs.h pseudo_random_generator.h random_generator.h @@ -116,6 +125,7 @@ if(SPIRV_BUILD_FUZZER) transformation_add_local_variable.h transformation_add_loop_preheader.h transformation_add_no_contraction_decoration.h + transformation_add_opphi_synonym.h transformation_add_parameter.h transformation_add_relaxed_decoration.h transformation_add_spec_constant_op.h @@ -137,6 +147,7 @@ if(SPIRV_BUILD_FUZZER) transformation_context.h transformation_equation_instruction.h transformation_function_call.h + transformation_inline_function.h transformation_invert_comparison_operator.h transformation_load.h transformation_make_vector_operation_dynamic.h @@ -173,8 +184,14 @@ if(SPIRV_BUILD_FUZZER) ${CMAKE_CURRENT_BINARY_DIR}/protobufs/spvtoolsfuzz.pb.h call_graph.cpp + counter_overflow_id_source.cpp data_descriptor.cpp - fact_manager.cpp + fact_manager/constant_uniform_facts.cpp + fact_manager/data_synonym_and_id_equation_facts.cpp + fact_manager/dead_block_facts.cpp + fact_manager/fact_manager.cpp + fact_manager/irrelevant_value_facts.cpp + fact_manager/livesafe_function_facts.cpp force_render_red.cpp fuzzer.cpp fuzzer_context.cpp @@ -195,6 +212,7 @@ if(SPIRV_BUILD_FUZZER) fuzzer_pass_add_local_variables.cpp fuzzer_pass_add_loop_preheaders.cpp fuzzer_pass_add_no_contraction_decorations.cpp + fuzzer_pass_add_opphi_synonyms.cpp fuzzer_pass_add_parameters.cpp fuzzer_pass_add_relaxed_decorations.cpp fuzzer_pass_add_stores.cpp @@ -208,6 +226,7 @@ if(SPIRV_BUILD_FUZZER) fuzzer_pass_construct_composites.cpp fuzzer_pass_copy_objects.cpp fuzzer_pass_donate_modules.cpp + fuzzer_pass_inline_functions.cpp fuzzer_pass_invert_comparison_operators.cpp fuzzer_pass_interchange_signedness_of_integer_operands.cpp fuzzer_pass_interchange_zero_like_constants.cpp @@ -236,6 +255,7 @@ if(SPIRV_BUILD_FUZZER) id_use_descriptor.cpp instruction_descriptor.cpp instruction_message.cpp + overflow_id_source.cpp pseudo_random_generator.cpp random_generator.cpp replayer.cpp @@ -257,6 +277,7 @@ if(SPIRV_BUILD_FUZZER) transformation_add_local_variable.cpp transformation_add_loop_preheader.cpp transformation_add_no_contraction_decoration.cpp + transformation_add_opphi_synonym.cpp transformation_add_parameter.cpp transformation_add_relaxed_decoration.cpp transformation_add_spec_constant_op.cpp @@ -278,6 +299,7 @@ if(SPIRV_BUILD_FUZZER) transformation_context.cpp transformation_equation_instruction.cpp transformation_function_call.cpp + transformation_inline_function.cpp transformation_invert_comparison_operator.cpp transformation_load.cpp transformation_make_vector_operation_dynamic.cpp diff --git a/3rdparty/spirv-tools/source/fuzz/counter_overflow_id_source.cpp b/3rdparty/spirv-tools/source/fuzz/counter_overflow_id_source.cpp new file mode 100644 index 000000000..1ed5603f7 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/counter_overflow_id_source.cpp @@ -0,0 +1,30 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/counter_overflow_id_source.h" + +namespace spvtools { +namespace fuzz { + +CounterOverflowIdSource::CounterOverflowIdSource(uint32_t first_available_id) + : next_available_id_(first_available_id) {} + +bool CounterOverflowIdSource::HasOverflowIds() const { return true; } + +uint32_t CounterOverflowIdSource::GetNextOverflowId() { + return next_available_id_++; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/counter_overflow_id_source.h b/3rdparty/spirv-tools/source/fuzz/counter_overflow_id_source.h new file mode 100644 index 000000000..aa8bc7abd --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/counter_overflow_id_source.h @@ -0,0 +1,45 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_COUNTER_OVERFLOW_ID_SOURCE_H_ +#define SOURCE_FUZZ_COUNTER_OVERFLOW_ID_SOURCE_H_ + +#include "source/fuzz/overflow_id_source.h" + +namespace spvtools { +namespace fuzz { + +// A source of overflow ids that uses a counter to provide successive ids from +// a given starting value. +class CounterOverflowIdSource : public OverflowIdSource { + public: + // |first_available_id| is the starting value for the counter. + explicit CounterOverflowIdSource(uint32_t first_available_id); + + // Always returns true. + bool HasOverflowIds() const override; + + // Returns the current counter value and increments the counter. + // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2541) We should + // account for the case where the maximum allowed id is reached. + uint32_t GetNextOverflowId() override; + + private: + uint32_t next_available_id_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_OVERFLOW_ID_SOURCE_COUNTER_H_ diff --git a/3rdparty/spirv-tools/source/fuzz/fact_manager/constant_uniform_facts.cpp b/3rdparty/spirv-tools/source/fuzz/fact_manager/constant_uniform_facts.cpp new file mode 100644 index 000000000..672ed5280 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/fact_manager/constant_uniform_facts.cpp @@ -0,0 +1,235 @@ +// 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/fact_manager/constant_uniform_facts.h" + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/uniform_buffer_element_descriptor.h" + +namespace spvtools { +namespace fuzz { +namespace fact_manager { + +uint32_t ConstantUniformFacts::GetConstantId( + opt::IRContext* context, + const protobufs::FactConstantUniform& constant_uniform_fact, + uint32_t type_id) { + auto type = context->get_type_mgr()->GetType(type_id); + assert(type != nullptr && "Unknown type id."); + const opt::analysis::Constant* known_constant; + if (type->AsInteger()) { + opt::analysis::IntConstant candidate_constant( + type->AsInteger(), GetConstantWords(constant_uniform_fact)); + known_constant = + context->get_constant_mgr()->FindConstant(&candidate_constant); + } else { + assert( + type->AsFloat() && + "Uniform constant facts are only supported for int and float types."); + opt::analysis::FloatConstant candidate_constant( + type->AsFloat(), GetConstantWords(constant_uniform_fact)); + known_constant = + context->get_constant_mgr()->FindConstant(&candidate_constant); + } + if (!known_constant) { + return 0; + } + return context->get_constant_mgr()->FindDeclaredConstant(known_constant, + type_id); +} + +std::vector ConstantUniformFacts::GetConstantWords( + const protobufs::FactConstantUniform& constant_uniform_fact) { + std::vector result; + for (auto constant_word : constant_uniform_fact.constant_word()) { + result.push_back(constant_word); + } + return result; +} + +bool ConstantUniformFacts::DataMatches( + const opt::Instruction& constant_instruction, + const protobufs::FactConstantUniform& constant_uniform_fact) { + assert(constant_instruction.opcode() == SpvOpConstant); + std::vector data_in_constant; + for (uint32_t i = 0; i < constant_instruction.NumInOperands(); i++) { + data_in_constant.push_back(constant_instruction.GetSingleWordInOperand(i)); + } + return data_in_constant == GetConstantWords(constant_uniform_fact); +} + +std::vector +ConstantUniformFacts::GetConstantsAvailableFromUniformsForType( + opt::IRContext* ir_context, uint32_t type_id) const { + std::vector result; + std::set already_seen; + for (auto& fact_and_type_id : facts_and_type_ids_) { + if (fact_and_type_id.second != type_id) { + continue; + } + if (auto constant_id = + GetConstantId(ir_context, fact_and_type_id.first, type_id)) { + if (already_seen.find(constant_id) == already_seen.end()) { + result.push_back(constant_id); + already_seen.insert(constant_id); + } + } + } + return result; +} + +std::vector +ConstantUniformFacts::GetUniformDescriptorsForConstant( + opt::IRContext* ir_context, uint32_t constant_id) const { + std::vector result; + auto constant_inst = ir_context->get_def_use_mgr()->GetDef(constant_id); + assert(constant_inst->opcode() == SpvOpConstant && + "The given id must be that of a constant"); + auto type_id = constant_inst->type_id(); + for (auto& fact_and_type_id : facts_and_type_ids_) { + if (fact_and_type_id.second != type_id) { + continue; + } + if (DataMatches(*constant_inst, fact_and_type_id.first)) { + result.emplace_back( + fact_and_type_id.first.uniform_buffer_element_descriptor()); + } + } + return result; +} + +uint32_t ConstantUniformFacts::GetConstantFromUniformDescriptor( + opt::IRContext* context, + const protobufs::UniformBufferElementDescriptor& uniform_descriptor) const { + // Consider each fact. + for (auto& fact_and_type : facts_and_type_ids_) { + // Check whether the uniform descriptor associated with the fact matches + // |uniform_descriptor|. + if (UniformBufferElementDescriptorEquals()( + &uniform_descriptor, + &fact_and_type.first.uniform_buffer_element_descriptor())) { + return GetConstantId(context, fact_and_type.first, fact_and_type.second); + } + } + // No fact associated with the given uniform descriptor was found. + return 0; +} + +std::vector +ConstantUniformFacts::GetTypesForWhichUniformValuesAreKnown() const { + std::vector result; + for (auto& fact_and_type : facts_and_type_ids_) { + if (std::find(result.begin(), result.end(), fact_and_type.second) == + result.end()) { + result.push_back(fact_and_type.second); + } + } + return result; +} + +bool ConstantUniformFacts::FloatingPointValueIsSuitable( + const protobufs::FactConstantUniform& fact, uint32_t width) { + const uint32_t kFloatWidth = 32; + const uint32_t kDoubleWidth = 64; + if (width != kFloatWidth && width != kDoubleWidth) { + // Only 32- and 64-bit floating-point types are handled. + return false; + } + std::vector words = GetConstantWords(fact); + if (width == 32) { + float value; + memcpy(&value, words.data(), sizeof(float)); + if (!std::isfinite(value)) { + return false; + } + } else { + double value; + memcpy(&value, words.data(), sizeof(double)); + if (!std::isfinite(value)) { + return false; + } + } + return true; +} + +bool ConstantUniformFacts::AddFact(const protobufs::FactConstantUniform& fact, + opt::IRContext* context) { + // Try to find a unique instruction that declares a variable such that the + // variable is decorated with the descriptor set and binding associated with + // the constant uniform fact. + opt::Instruction* uniform_variable = FindUniformVariable( + fact.uniform_buffer_element_descriptor(), context, true); + + if (!uniform_variable) { + return false; + } + + assert(SpvOpVariable == uniform_variable->opcode()); + assert(SpvStorageClassUniform == uniform_variable->GetSingleWordInOperand(0)); + + auto should_be_uniform_pointer_type = + context->get_type_mgr()->GetType(uniform_variable->type_id()); + if (!should_be_uniform_pointer_type->AsPointer()) { + return false; + } + if (should_be_uniform_pointer_type->AsPointer()->storage_class() != + SpvStorageClassUniform) { + return false; + } + auto should_be_uniform_pointer_instruction = + context->get_def_use_mgr()->GetDef(uniform_variable->type_id()); + auto composite_type = + should_be_uniform_pointer_instruction->GetSingleWordInOperand(1); + + auto final_element_type_id = fuzzerutil::WalkCompositeTypeIndices( + context, composite_type, + fact.uniform_buffer_element_descriptor().index()); + if (!final_element_type_id) { + return false; + } + auto final_element_type = + context->get_type_mgr()->GetType(final_element_type_id); + assert(final_element_type && + "There should be a type corresponding to this id."); + + if (!(final_element_type->AsFloat() || final_element_type->AsInteger())) { + return false; + } + auto width = final_element_type->AsFloat() + ? final_element_type->AsFloat()->width() + : final_element_type->AsInteger()->width(); + + if (final_element_type->AsFloat() && + !FloatingPointValueIsSuitable(fact, width)) { + return false; + } + + auto required_words = (width + 32 - 1) / 32; + if (static_cast(fact.constant_word().size()) != required_words) { + return false; + } + facts_and_type_ids_.emplace_back( + std::pair( + fact, final_element_type_id)); + return true; +} + +const std::vector>& +ConstantUniformFacts::GetConstantUniformFactsAndTypes() const { + return facts_and_type_ids_; +} + +} // namespace fact_manager +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/fact_manager/constant_uniform_facts.h b/3rdparty/spirv-tools/source/fuzz/fact_manager/constant_uniform_facts.h new file mode 100644 index 000000000..6d052e504 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/fact_manager/constant_uniform_facts.h @@ -0,0 +1,90 @@ +// 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_FACT_MANAGER_CONSTANT_UNIFORM_FACTS_H_ +#define SOURCE_FUZZ_FACT_MANAGER_CONSTANT_UNIFORM_FACTS_H_ + +#include + +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace fuzz { +namespace fact_manager { + +// The purpose of this class is to group the fields and data used to represent +// facts about uniform constants. +class ConstantUniformFacts { + public: + // See method in FactManager which delegates to this method. + bool AddFact(const protobufs::FactConstantUniform& fact, + opt::IRContext* context); + + // See method in FactManager which delegates to this method. + std::vector GetConstantsAvailableFromUniformsForType( + opt::IRContext* ir_context, uint32_t type_id) const; + + // See method in FactManager which delegates to this method. + std::vector + GetUniformDescriptorsForConstant(opt::IRContext* ir_context, + uint32_t constant_id) const; + + // See method in FactManager which delegates to this method. + uint32_t GetConstantFromUniformDescriptor( + opt::IRContext* context, + const protobufs::UniformBufferElementDescriptor& uniform_descriptor) + const; + + // See method in FactManager which delegates to this method. + std::vector GetTypesForWhichUniformValuesAreKnown() const; + + // See method in FactManager which delegates to this method. + const std::vector>& + GetConstantUniformFactsAndTypes() const; + + private: + // Returns true if and only if the words associated with + // |constant_instruction| exactly match the words for the constant associated + // with |constant_uniform_fact|. + static bool DataMatches( + const opt::Instruction& constant_instruction, + const protobufs::FactConstantUniform& constant_uniform_fact); + + // Yields the constant words associated with |constant_uniform_fact|. + static std::vector GetConstantWords( + const protobufs::FactConstantUniform& constant_uniform_fact); + + // Yields the id of a constant of type |type_id| whose data matches the + // constant data in |constant_uniform_fact|, or 0 if no such constant is + // declared. + static uint32_t GetConstantId( + opt::IRContext* context, + const protobufs::FactConstantUniform& constant_uniform_fact, + uint32_t type_id); + + // Checks that the width of a floating-point constant is supported, and that + // the constant is finite. + static bool FloatingPointValueIsSuitable( + const protobufs::FactConstantUniform& fact, uint32_t width); + + std::vector> + facts_and_type_ids_; +}; + +} // namespace fact_manager +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_FACT_MANAGER_CONSTANT_UNIFORM_FACTS_H_ diff --git a/3rdparty/spirv-tools/source/fuzz/fact_manager.cpp b/3rdparty/spirv-tools/source/fuzz/fact_manager/data_synonym_and_id_equation_facts.cpp similarity index 52% rename from 3rdparty/spirv-tools/source/fuzz/fact_manager.cpp rename to 3rdparty/spirv-tools/source/fuzz/fact_manager/data_synonym_and_id_equation_facts.cpp index a2fec768f..877a38c67 100644 --- a/3rdparty/spirv-tools/source/fuzz/fact_manager.cpp +++ b/3rdparty/spirv-tools/source/fuzz/fact_manager/data_synonym_and_id_equation_facts.cpp @@ -12,529 +12,60 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "source/fuzz/fact_manager.h" +#include "source/fuzz/fact_manager/data_synonym_and_id_equation_facts.h" -#include -#include -#include - -#include "source/fuzz/equivalence_relation.h" #include "source/fuzz/fuzzer_util.h" -#include "source/fuzz/uniform_buffer_element_descriptor.h" -#include "source/opt/ir_context.h" namespace spvtools { namespace fuzz { +namespace fact_manager { -namespace { - -std::string ToString(const protobufs::FactConstantUniform& fact) { - std::stringstream stream; - stream << "(" << fact.uniform_buffer_element_descriptor().descriptor_set() - << ", " << fact.uniform_buffer_element_descriptor().binding() << ")["; - - bool first = true; - for (auto index : fact.uniform_buffer_element_descriptor().index()) { - if (first) { - first = false; - } else { - stream << ", "; - } - stream << index; - } - - stream << "] == ["; - - first = true; - for (auto constant_word : fact.constant_word()) { - if (first) { - first = false; - } else { - stream << ", "; - } - stream << constant_word; - } - - stream << "]"; - return stream.str(); -} - -std::string ToString(const protobufs::FactDataSynonym& fact) { - std::stringstream stream; - stream << fact.data1() << " = " << fact.data2(); - return stream.str(); -} - -std::string ToString(const protobufs::FactIdEquation& fact) { - std::stringstream stream; - stream << fact.lhs_id(); - stream << " " << static_cast(fact.opcode()); - for (auto rhs_id : fact.rhs_id()) { - stream << " " << rhs_id; - } - return stream.str(); -} - -std::string ToString(const protobufs::Fact& fact) { - switch (fact.fact_case()) { - case protobufs::Fact::kConstantUniformFact: - return ToString(fact.constant_uniform_fact()); - case protobufs::Fact::kDataSynonymFact: - return ToString(fact.data_synonym_fact()); - case protobufs::Fact::kIdEquationFact: - return ToString(fact.id_equation_fact()); - default: - assert(false && "Stringification not supported for this fact."); - return ""; - } -} - -} // namespace - -//======================= -// Constant uniform facts - -// The purpose of this class is to group the fields and data used to represent -// facts about uniform constants. -class FactManager::ConstantUniformFacts { - public: - // See method in FactManager which delegates to this method. - bool AddFact(const protobufs::FactConstantUniform& fact, - opt::IRContext* context); - - // See method in FactManager which delegates to this method. - std::vector GetConstantsAvailableFromUniformsForType( - opt::IRContext* ir_context, uint32_t type_id) const; - - // See method in FactManager which delegates to this method. - const std::vector - GetUniformDescriptorsForConstant(opt::IRContext* ir_context, - uint32_t constant_id) const; - - // See method in FactManager which delegates to this method. - uint32_t GetConstantFromUniformDescriptor( - opt::IRContext* context, - const protobufs::UniformBufferElementDescriptor& uniform_descriptor) - const; - - // See method in FactManager which delegates to this method. - std::vector GetTypesForWhichUniformValuesAreKnown() const; - - // See method in FactManager which delegates to this method. - const std::vector>& - GetConstantUniformFactsAndTypes() const; - - private: - // Returns true if and only if the words associated with - // |constant_instruction| exactly match the words for the constant associated - // with |constant_uniform_fact|. - bool DataMatches( - const opt::Instruction& constant_instruction, - const protobufs::FactConstantUniform& constant_uniform_fact) const; - - // Yields the constant words associated with |constant_uniform_fact|. - std::vector GetConstantWords( - const protobufs::FactConstantUniform& constant_uniform_fact) const; - - // Yields the id of a constant of type |type_id| whose data matches the - // constant data in |constant_uniform_fact|, or 0 if no such constant is - // declared. - uint32_t GetConstantId( - opt::IRContext* context, - const protobufs::FactConstantUniform& constant_uniform_fact, - uint32_t type_id) const; - - // Checks that the width of a floating-point constant is supported, and that - // the constant is finite. - bool FloatingPointValueIsSuitable(const protobufs::FactConstantUniform& fact, - uint32_t width) const; - - std::vector> - facts_and_type_ids_; -}; - -uint32_t FactManager::ConstantUniformFacts::GetConstantId( - opt::IRContext* context, - const protobufs::FactConstantUniform& constant_uniform_fact, - uint32_t type_id) const { - auto type = context->get_type_mgr()->GetType(type_id); - assert(type != nullptr && "Unknown type id."); - const opt::analysis::Constant* known_constant; - if (type->AsInteger()) { - opt::analysis::IntConstant candidate_constant( - type->AsInteger(), GetConstantWords(constant_uniform_fact)); - known_constant = - context->get_constant_mgr()->FindConstant(&candidate_constant); - } else { - assert( - type->AsFloat() && - "Uniform constant facts are only supported for int and float types."); - opt::analysis::FloatConstant candidate_constant( - type->AsFloat(), GetConstantWords(constant_uniform_fact)); - known_constant = - context->get_constant_mgr()->FindConstant(&candidate_constant); - } - if (!known_constant) { - return 0; - } - return context->get_constant_mgr()->FindDeclaredConstant(known_constant, - type_id); -} - -std::vector FactManager::ConstantUniformFacts::GetConstantWords( - const protobufs::FactConstantUniform& constant_uniform_fact) const { - std::vector result; - for (auto constant_word : constant_uniform_fact.constant_word()) { - result.push_back(constant_word); - } - return result; -} - -bool FactManager::ConstantUniformFacts::DataMatches( - const opt::Instruction& constant_instruction, - const protobufs::FactConstantUniform& constant_uniform_fact) const { - assert(constant_instruction.opcode() == SpvOpConstant); - std::vector data_in_constant; - for (uint32_t i = 0; i < constant_instruction.NumInOperands(); i++) { - data_in_constant.push_back(constant_instruction.GetSingleWordInOperand(i)); - } - return data_in_constant == GetConstantWords(constant_uniform_fact); -} - -std::vector -FactManager::ConstantUniformFacts::GetConstantsAvailableFromUniformsForType( - opt::IRContext* ir_context, uint32_t type_id) const { - std::vector result; - std::set already_seen; - for (auto& fact_and_type_id : facts_and_type_ids_) { - if (fact_and_type_id.second != type_id) { - continue; - } - if (auto constant_id = - GetConstantId(ir_context, fact_and_type_id.first, type_id)) { - if (already_seen.find(constant_id) == already_seen.end()) { - result.push_back(constant_id); - already_seen.insert(constant_id); - } - } - } - return result; -} - -const std::vector -FactManager::ConstantUniformFacts::GetUniformDescriptorsForConstant( - opt::IRContext* ir_context, uint32_t constant_id) const { - std::vector result; - auto constant_inst = ir_context->get_def_use_mgr()->GetDef(constant_id); - assert(constant_inst->opcode() == SpvOpConstant && - "The given id must be that of a constant"); - auto type_id = constant_inst->type_id(); - for (auto& fact_and_type_id : facts_and_type_ids_) { - if (fact_and_type_id.second != type_id) { - continue; - } - if (DataMatches(*constant_inst, fact_and_type_id.first)) { - result.emplace_back( - fact_and_type_id.first.uniform_buffer_element_descriptor()); - } - } - return result; -} - -uint32_t FactManager::ConstantUniformFacts::GetConstantFromUniformDescriptor( - opt::IRContext* context, - const protobufs::UniformBufferElementDescriptor& uniform_descriptor) const { - // Consider each fact. - for (auto& fact_and_type : facts_and_type_ids_) { - // Check whether the uniform descriptor associated with the fact matches - // |uniform_descriptor|. - if (UniformBufferElementDescriptorEquals()( - &uniform_descriptor, - &fact_and_type.first.uniform_buffer_element_descriptor())) { - return GetConstantId(context, fact_and_type.first, fact_and_type.second); - } - } - // No fact associated with the given uniform descriptor was found. - return 0; -} - -std::vector -FactManager::ConstantUniformFacts::GetTypesForWhichUniformValuesAreKnown() - const { - std::vector result; - for (auto& fact_and_type : facts_and_type_ids_) { - if (std::find(result.begin(), result.end(), fact_and_type.second) == - result.end()) { - result.push_back(fact_and_type.second); - } - } - return result; -} - -bool FactManager::ConstantUniformFacts::FloatingPointValueIsSuitable( - const protobufs::FactConstantUniform& fact, uint32_t width) const { - const uint32_t kFloatWidth = 32; - const uint32_t kDoubleWidth = 64; - if (width != kFloatWidth && width != kDoubleWidth) { - // Only 32- and 64-bit floating-point types are handled. - return false; - } - std::vector words = GetConstantWords(fact); - if (width == 32) { - float value; - memcpy(&value, words.data(), sizeof(float)); - if (!std::isfinite(value)) { - return false; - } - } else { - double value; - memcpy(&value, words.data(), sizeof(double)); - if (!std::isfinite(value)) { - return false; - } - } - return true; -} - -bool FactManager::ConstantUniformFacts::AddFact( - const protobufs::FactConstantUniform& fact, opt::IRContext* context) { - // Try to find a unique instruction that declares a variable such that the - // variable is decorated with the descriptor set and binding associated with - // the constant uniform fact. - opt::Instruction* uniform_variable = FindUniformVariable( - fact.uniform_buffer_element_descriptor(), context, true); - - if (!uniform_variable) { - return false; - } - - assert(SpvOpVariable == uniform_variable->opcode()); - assert(SpvStorageClassUniform == uniform_variable->GetSingleWordInOperand(0)); - - auto should_be_uniform_pointer_type = - context->get_type_mgr()->GetType(uniform_variable->type_id()); - if (!should_be_uniform_pointer_type->AsPointer()) { - return false; - } - if (should_be_uniform_pointer_type->AsPointer()->storage_class() != - SpvStorageClassUniform) { - return false; - } - auto should_be_uniform_pointer_instruction = - context->get_def_use_mgr()->GetDef(uniform_variable->type_id()); - auto composite_type = - should_be_uniform_pointer_instruction->GetSingleWordInOperand(1); - - auto final_element_type_id = fuzzerutil::WalkCompositeTypeIndices( - context, composite_type, - fact.uniform_buffer_element_descriptor().index()); - if (!final_element_type_id) { - return false; - } - auto final_element_type = - context->get_type_mgr()->GetType(final_element_type_id); - assert(final_element_type && - "There should be a type corresponding to this id."); - - if (!(final_element_type->AsFloat() || final_element_type->AsInteger())) { - return false; - } - auto width = final_element_type->AsFloat() - ? final_element_type->AsFloat()->width() - : final_element_type->AsInteger()->width(); - - if (final_element_type->AsFloat() && - !FloatingPointValueIsSuitable(fact, width)) { - return false; - } - - auto required_words = (width + 32 - 1) / 32; - if (static_cast(fact.constant_word().size()) != required_words) { - return false; - } - facts_and_type_ids_.emplace_back( - std::pair( - fact, final_element_type_id)); - return true; -} - -const std::vector>& -FactManager::ConstantUniformFacts::GetConstantUniformFactsAndTypes() const { - return facts_and_type_ids_; -} - -// End of uniform constant facts -//============================== - -//============================== -// Data synonym and id equation facts - -// This helper struct represents the right hand side of an equation as an -// operator applied to a number of data descriptor operands. -struct Operation { - SpvOp opcode; - std::vector operands; -}; - -// Hashing for operations, to allow deterministic unordered sets. -struct OperationHash { - size_t operator()(const Operation& operation) const { - std::u32string hash; - hash.push_back(operation.opcode); - for (auto operand : operation.operands) { - hash.push_back(static_cast(DataDescriptorHash()(operand))); - } - return std::hash()(hash); - } -}; - -// Equality for operations, to allow deterministic unordered sets. -struct OperationEquals { - bool operator()(const Operation& first, const Operation& second) const { - // Equal operations require... - // - // Equal opcodes. - if (first.opcode != second.opcode) { - return false; - } - // Matching operand counds. - if (first.operands.size() != second.operands.size()) { - return false; - } - // Equal operands. - for (uint32_t i = 0; i < first.operands.size(); i++) { - if (!DataDescriptorEquals()(first.operands[i], second.operands[i])) { - return false; - } - } - return true; - } -}; - -// A helper, for debugging, to represent an operation as a string. -std::string ToString(const Operation& operation) { - std::stringstream stream; - stream << operation.opcode; +size_t DataSynonymAndIdEquationFacts::OperationHash::operator()( + const Operation& operation) const { + std::u32string hash; + hash.push_back(operation.opcode); for (auto operand : operation.operands) { - stream << " " << *operand; + hash.push_back(static_cast(DataDescriptorHash()(operand))); } - return stream.str(); + return std::hash()(hash); } -// The purpose of this class is to group the fields and data used to represent -// facts about data synonyms and id equations. -class FactManager::DataSynonymAndIdEquationFacts { - public: - // See method in FactManager which delegates to this method. - void AddFact(const protobufs::FactDataSynonym& fact, opt::IRContext* context); - - // See method in FactManager which delegates to this method. - void AddFact(const protobufs::FactIdEquation& fact, opt::IRContext* context); - - // See method in FactManager which delegates to this method. - std::vector GetSynonymsForDataDescriptor( - const protobufs::DataDescriptor& data_descriptor) const; - - // See method in FactManager which delegates to this method. - std::vector GetIdsForWhichSynonymsAreKnown() const; - - // See method in FactManager which delegates to this method. - bool IsSynonymous(const protobufs::DataDescriptor& data_descriptor1, - const protobufs::DataDescriptor& data_descriptor2) const; - - // See method in FactManager which delegates to this method. - void ComputeClosureOfFacts(opt::IRContext* context, - uint32_t maximum_equivalence_class_size); - - private: - using OperationSet = - std::unordered_set; - - // Adds the synonym |dd1| = |dd2| to the set of managed facts, and recurses - // into sub-components of the data descriptors, if they are composites, to - // record that their components are pairwise-synonymous. - void AddDataSynonymFactRecursive(const protobufs::DataDescriptor& dd1, - const protobufs::DataDescriptor& dd2, - opt::IRContext* context); - - // Computes various corollary facts from the data descriptor |dd| if members - // of its equivalence class participate in equation facts with OpConvert* - // opcodes. The descriptor should be registered in the equivalence relation. - void ComputeConversionDataSynonymFacts(const protobufs::DataDescriptor& dd, - opt::IRContext* context); - - // Recurses into sub-components of the data descriptors, if they are - // composites, to record that their components are pairwise-synonymous. - void ComputeCompositeDataSynonymFacts(const protobufs::DataDescriptor& dd1, - const protobufs::DataDescriptor& dd2, - opt::IRContext* context); - - // Records the fact that |dd1| and |dd2| are equivalent, and merges the sets - // of equations that are known about them. - void MakeEquivalent(const protobufs::DataDescriptor& dd1, - const protobufs::DataDescriptor& dd2); - - // Registers a data descriptor in the equivalence relation if it hasn't been - // registered yet, and returns its representative. - const protobufs::DataDescriptor* RegisterDataDescriptor( - const protobufs::DataDescriptor& dd); - - // Returns true if and only if |dd1| and |dd2| are valid data descriptors - // whose associated data have compatible types. Two types are compatible if: - // - they are the same - // - they both are numerical or vectors of numerical components with the same - // number of components and the same bit count per component - static bool DataDescriptorsAreWellFormedAndComparable( - opt::IRContext* context, const protobufs::DataDescriptor& dd1, - const protobufs::DataDescriptor& dd2); - - OperationSet GetEquations(const protobufs::DataDescriptor* lhs) const; - - // Requires that |lhs_dd| and every element of |rhs_dds| is present in the - // |synonymous_| equivalence relation, but is not necessarily its own - // representative. Records the fact that the equation - // "|lhs_dd| |opcode| |rhs_dds_non_canonical|" holds, and adds any - // corollaries, in the form of data synonym or equation facts, that follow - // from this and other known facts. - void AddEquationFactRecursive( - const protobufs::DataDescriptor& lhs_dd, SpvOp opcode, - const std::vector& rhs_dds, - opt::IRContext* context); - - // The data descriptors that are known to be synonymous with one another are - // captured by this equivalence relation. - EquivalenceRelation - synonymous_; - - // When a new synonym fact is added, it may be possible to deduce further - // synonym facts by computing a closure of all known facts. However, this is - // an expensive operation, so it should be performed sparingly and only there - // is some chance of new facts being deduced. This boolean tracks whether a - // closure computation is required - i.e., whether a new fact has been added - // since the last time such a computation was performed. - bool closure_computation_required_ = false; - - // Represents a set of equations on data descriptors as a map indexed by - // left-hand-side, mapping a left-hand-side to a set of operations, each of - // which (together with the left-hand-side) defines an equation. +bool DataSynonymAndIdEquationFacts::OperationEquals::operator()( + const Operation& first, const Operation& second) const { + // Equal operations require... // - // All data descriptors occurring in equations are required to be present in - // the |synonymous_| equivalence relation, and to be their own representatives - // in that relation. - std::unordered_map - id_equations_; -}; + // Equal opcodes. + if (first.opcode != second.opcode) { + return false; + } + // Matching operand counts. + if (first.operands.size() != second.operands.size()) { + return false; + } + // Equal operands. + for (uint32_t i = 0; i < first.operands.size(); i++) { + if (!DataDescriptorEquals()(first.operands[i], second.operands[i])) { + return false; + } + } + return true; +} -void FactManager::DataSynonymAndIdEquationFacts::AddFact( +void DataSynonymAndIdEquationFacts::AddFact( const protobufs::FactDataSynonym& fact, opt::IRContext* context) { + // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3550) + // Assert that ids are not irrelevant. + // Add the fact, including all facts relating sub-components of the data // descriptors that are involved. AddDataSynonymFactRecursive(fact.data1(), fact.data2(), context); } -void FactManager::DataSynonymAndIdEquationFacts::AddFact( +void DataSynonymAndIdEquationFacts::AddFact( const protobufs::FactIdEquation& fact, opt::IRContext* context) { + // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3550) + // Assert that ids are not irrelevant. + protobufs::DataDescriptor lhs_dd = MakeDataDescriptor(fact.lhs_id(), {}); // Register the LHS in the equivalence relation if needed. @@ -542,21 +73,23 @@ void FactManager::DataSynonymAndIdEquationFacts::AddFact( // Get equivalence class representatives for all ids used on the RHS of the // equation. - std::vector rhs_dd_ptrs; + std::vector rhs_dds; for (auto rhs_id : fact.rhs_id()) { + // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3550) + // Assert that ids are not irrelevant. + // Register a data descriptor based on this id in the equivalence relation // if needed, and then record the equivalence class representative. - rhs_dd_ptrs.push_back( - RegisterDataDescriptor(MakeDataDescriptor(rhs_id, {}))); + rhs_dds.push_back(RegisterDataDescriptor(MakeDataDescriptor(rhs_id, {}))); } // Now add the fact. - AddEquationFactRecursive(lhs_dd, static_cast(fact.opcode()), - rhs_dd_ptrs, context); + AddEquationFactRecursive(lhs_dd, static_cast(fact.opcode()), rhs_dds, + context); } -FactManager::DataSynonymAndIdEquationFacts::OperationSet -FactManager::DataSynonymAndIdEquationFacts::GetEquations( +DataSynonymAndIdEquationFacts::OperationSet +DataSynonymAndIdEquationFacts::GetEquations( const protobufs::DataDescriptor* lhs) const { auto existing = id_equations_.find(lhs); if (existing == id_equations_.end()) { @@ -565,7 +98,7 @@ FactManager::DataSynonymAndIdEquationFacts::GetEquations( return existing->second; } -void FactManager::DataSynonymAndIdEquationFacts::AddEquationFactRecursive( +void DataSynonymAndIdEquationFacts::AddEquationFactRecursive( const protobufs::DataDescriptor& lhs_dd, SpvOp opcode, const std::vector& rhs_dds, opt::IRContext* context) { @@ -717,7 +250,7 @@ void FactManager::DataSynonymAndIdEquationFacts::AddEquationFactRecursive( } } -void FactManager::DataSynonymAndIdEquationFacts::AddDataSynonymFactRecursive( +void DataSynonymAndIdEquationFacts::AddDataSynonymFactRecursive( const protobufs::DataDescriptor& dd1, const protobufs::DataDescriptor& dd2, opt::IRContext* context) { assert(DataDescriptorsAreWellFormedAndComparable(context, dd1, dd2)); @@ -736,9 +269,8 @@ void FactManager::DataSynonymAndIdEquationFacts::AddDataSynonymFactRecursive( ComputeCompositeDataSynonymFacts(dd1, dd2, context); } -void FactManager::DataSynonymAndIdEquationFacts:: - ComputeConversionDataSynonymFacts(const protobufs::DataDescriptor& dd, - opt::IRContext* context) { +void DataSynonymAndIdEquationFacts::ComputeConversionDataSynonymFacts( + const protobufs::DataDescriptor& dd, opt::IRContext* context) { assert(synonymous_.Exists(dd) && "|dd| should've been registered in the equivalence relation"); @@ -789,10 +321,9 @@ void FactManager::DataSynonymAndIdEquationFacts:: } } -void FactManager::DataSynonymAndIdEquationFacts:: - ComputeCompositeDataSynonymFacts(const protobufs::DataDescriptor& dd1, - const protobufs::DataDescriptor& dd2, - opt::IRContext* context) { +void DataSynonymAndIdEquationFacts::ComputeCompositeDataSynonymFacts( + const protobufs::DataDescriptor& dd1, const protobufs::DataDescriptor& dd2, + opt::IRContext* context) { // Check whether this is a synonym about composite objects. If it is, // we can recursively add synonym facts about their associated sub-components. @@ -862,7 +393,7 @@ void FactManager::DataSynonymAndIdEquationFacts:: } } -void FactManager::DataSynonymAndIdEquationFacts::ComputeClosureOfFacts( +void DataSynonymAndIdEquationFacts::ComputeClosureOfFacts( opt::IRContext* context, uint32_t maximum_equivalence_class_size) { // Suppose that obj_1[a_1, ..., a_m] and obj_2[b_1, ..., b_n] are distinct // data descriptors that describe objects of the same composite type, and that @@ -1144,7 +675,7 @@ void FactManager::DataSynonymAndIdEquationFacts::ComputeClosureOfFacts( } } -void FactManager::DataSynonymAndIdEquationFacts::MakeEquivalent( +void DataSynonymAndIdEquationFacts::MakeEquivalent( const protobufs::DataDescriptor& dd1, const protobufs::DataDescriptor& dd2) { // Register the data descriptors if they are not already known to the @@ -1218,16 +749,15 @@ void FactManager::DataSynonymAndIdEquationFacts::MakeEquivalent( } const protobufs::DataDescriptor* -FactManager::DataSynonymAndIdEquationFacts::RegisterDataDescriptor( +DataSynonymAndIdEquationFacts::RegisterDataDescriptor( const protobufs::DataDescriptor& dd) { return synonymous_.Exists(dd) ? synonymous_.Find(&dd) : synonymous_.Register(dd); } -bool FactManager::DataSynonymAndIdEquationFacts:: - DataDescriptorsAreWellFormedAndComparable( - opt::IRContext* context, const protobufs::DataDescriptor& dd1, - const protobufs::DataDescriptor& dd2) { +bool DataSynonymAndIdEquationFacts::DataDescriptorsAreWellFormedAndComparable( + opt::IRContext* context, const protobufs::DataDescriptor& dd1, + const protobufs::DataDescriptor& dd2) { auto end_type_id_1 = fuzzerutil::WalkCompositeTypeIndices( context, context->get_def_use_mgr()->GetDef(dd1.object())->type_id(), dd1.index()); @@ -1238,6 +768,13 @@ bool FactManager::DataSynonymAndIdEquationFacts:: if (end_type_id_1 == 0 || end_type_id_2 == 0) { return false; } + // Neither end type is allowed to be void. + if (context->get_def_use_mgr()->GetDef(end_type_id_1)->opcode() == + SpvOpTypeVoid || + context->get_def_use_mgr()->GetDef(end_type_id_2)->opcode() == + SpvOpTypeVoid) { + return false; + } // If the end types are the same, the data descriptors are comparable. if (end_type_id_1 == end_type_id_2) { return true; @@ -1292,17 +829,16 @@ bool FactManager::DataSynonymAndIdEquationFacts:: } std::vector -FactManager::DataSynonymAndIdEquationFacts::GetSynonymsForDataDescriptor( +DataSynonymAndIdEquationFacts::GetSynonymsForDataDescriptor( const protobufs::DataDescriptor& data_descriptor) const { if (synonymous_.Exists(data_descriptor)) { return synonymous_.GetEquivalenceClass(data_descriptor); } - return std::vector(); + return {}; } std::vector -FactManager::DataSynonymAndIdEquationFacts::GetIdsForWhichSynonymsAreKnown() - const { +DataSynonymAndIdEquationFacts::GetIdsForWhichSynonymsAreKnown() const { std::vector result; for (auto& data_descriptor : synonymous_.GetAllKnownValues()) { if (data_descriptor->index().empty()) { @@ -1312,7 +848,7 @@ FactManager::DataSynonymAndIdEquationFacts::GetIdsForWhichSynonymsAreKnown() return result; } -bool FactManager::DataSynonymAndIdEquationFacts::IsSynonymous( +bool DataSynonymAndIdEquationFacts::IsSynonymous( const protobufs::DataDescriptor& data_descriptor1, const protobufs::DataDescriptor& data_descriptor2) const { return synonymous_.Exists(data_descriptor1) && @@ -1320,284 +856,6 @@ bool FactManager::DataSynonymAndIdEquationFacts::IsSynonymous( synonymous_.IsEquivalent(data_descriptor1, data_descriptor2); } -// End of data synonym facts -//============================== - -//============================== -// Dead block facts - -// The purpose of this class is to group the fields and data used to represent -// facts about data blocks. -class FactManager::DeadBlockFacts { - public: - // See method in FactManager which delegates to this method. - void AddFact(const protobufs::FactBlockIsDead& fact); - - // See method in FactManager which delegates to this method. - bool BlockIsDead(uint32_t block_id) const; - - private: - std::set dead_block_ids_; -}; - -void FactManager::DeadBlockFacts::AddFact( - const protobufs::FactBlockIsDead& fact) { - dead_block_ids_.insert(fact.block_id()); -} - -bool FactManager::DeadBlockFacts::BlockIsDead(uint32_t block_id) const { - return dead_block_ids_.count(block_id) != 0; -} - -// End of dead block facts -//============================== - -//============================== -// Livesafe function facts - -// The purpose of this class is to group the fields and data used to represent -// facts about livesafe functions. -class FactManager::LivesafeFunctionFacts { - public: - // See method in FactManager which delegates to this method. - void AddFact(const protobufs::FactFunctionIsLivesafe& fact); - - // See method in FactManager which delegates to this method. - bool FunctionIsLivesafe(uint32_t function_id) const; - - private: - std::set livesafe_function_ids_; -}; - -void FactManager::LivesafeFunctionFacts::AddFact( - const protobufs::FactFunctionIsLivesafe& fact) { - livesafe_function_ids_.insert(fact.function_id()); -} - -bool FactManager::LivesafeFunctionFacts::FunctionIsLivesafe( - uint32_t function_id) const { - return livesafe_function_ids_.count(function_id) != 0; -} - -// End of livesafe function facts -//============================== - -//============================== -// Irrelevant value facts - -// The purpose of this class is to group the fields and data used to represent -// facts about various irrelevant values in the module. -class FactManager::IrrelevantValueFacts { - public: - // See method in FactManager which delegates to this method. - void AddFact(const protobufs::FactPointeeValueIsIrrelevant& fact); - - // See method in FactManager which delegates to this method. - void AddFact(const protobufs::FactIdIsIrrelevant& fact); - - // See method in FactManager which delegates to this method. - bool PointeeValueIsIrrelevant(uint32_t pointer_id) const; - - // See method in FactManager which delegates to this method. - bool IdIsIrrelevant(uint32_t pointer_id) const; - - private: - std::unordered_set pointers_to_irrelevant_pointees_ids_; - std::unordered_set irrelevant_ids_; -}; - -void FactManager::IrrelevantValueFacts::AddFact( - const protobufs::FactPointeeValueIsIrrelevant& fact) { - pointers_to_irrelevant_pointees_ids_.insert(fact.pointer_id()); -} - -void FactManager::IrrelevantValueFacts::AddFact( - const protobufs::FactIdIsIrrelevant& fact) { - irrelevant_ids_.insert(fact.result_id()); -} - -bool FactManager::IrrelevantValueFacts::PointeeValueIsIrrelevant( - uint32_t pointer_id) const { - return pointers_to_irrelevant_pointees_ids_.count(pointer_id) != 0; -} - -bool FactManager::IrrelevantValueFacts::IdIsIrrelevant( - uint32_t pointer_id) const { - return irrelevant_ids_.count(pointer_id) != 0; -} - -// End of arbitrarily-valued variable facts -//============================== - -FactManager::FactManager() - : uniform_constant_facts_(MakeUnique()), - data_synonym_and_id_equation_facts_( - MakeUnique()), - dead_block_facts_(MakeUnique()), - livesafe_function_facts_(MakeUnique()), - irrelevant_value_facts_(MakeUnique()) {} - -FactManager::~FactManager() = default; - -void FactManager::AddFacts(const MessageConsumer& message_consumer, - const protobufs::FactSequence& initial_facts, - opt::IRContext* context) { - for (auto& fact : initial_facts.fact()) { - if (!AddFact(fact, context)) { - message_consumer( - SPV_MSG_WARNING, nullptr, {}, - ("Invalid fact " + ToString(fact) + " ignored.").c_str()); - } - } -} - -bool FactManager::AddFact(const fuzz::protobufs::Fact& fact, - opt::IRContext* context) { - switch (fact.fact_case()) { - case protobufs::Fact::kConstantUniformFact: - return uniform_constant_facts_->AddFact(fact.constant_uniform_fact(), - context); - case protobufs::Fact::kDataSynonymFact: - data_synonym_and_id_equation_facts_->AddFact(fact.data_synonym_fact(), - context); - return true; - case protobufs::Fact::kBlockIsDeadFact: - dead_block_facts_->AddFact(fact.block_is_dead_fact()); - return true; - case protobufs::Fact::kFunctionIsLivesafeFact: - livesafe_function_facts_->AddFact(fact.function_is_livesafe_fact()); - return true; - default: - assert(false && "Unknown fact type."); - return false; - } -} - -void FactManager::AddFactDataSynonym(const protobufs::DataDescriptor& data1, - const protobufs::DataDescriptor& data2, - opt::IRContext* context) { - // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3550): - // assert that neither |data1| nor |data2| are irrelevant. - protobufs::FactDataSynonym fact; - *fact.mutable_data1() = data1; - *fact.mutable_data2() = data2; - data_synonym_and_id_equation_facts_->AddFact(fact, context); -} - -std::vector FactManager::GetConstantsAvailableFromUniformsForType( - opt::IRContext* ir_context, uint32_t type_id) const { - return uniform_constant_facts_->GetConstantsAvailableFromUniformsForType( - ir_context, type_id); -} - -const std::vector -FactManager::GetUniformDescriptorsForConstant(opt::IRContext* ir_context, - uint32_t constant_id) const { - return uniform_constant_facts_->GetUniformDescriptorsForConstant(ir_context, - constant_id); -} - -uint32_t FactManager::GetConstantFromUniformDescriptor( - opt::IRContext* context, - const protobufs::UniformBufferElementDescriptor& uniform_descriptor) const { - return uniform_constant_facts_->GetConstantFromUniformDescriptor( - context, uniform_descriptor); -} - -std::vector FactManager::GetTypesForWhichUniformValuesAreKnown() - const { - return uniform_constant_facts_->GetTypesForWhichUniformValuesAreKnown(); -} - -const std::vector>& -FactManager::GetConstantUniformFactsAndTypes() const { - return uniform_constant_facts_->GetConstantUniformFactsAndTypes(); -} - -std::vector FactManager::GetIdsForWhichSynonymsAreKnown() const { - return data_synonym_and_id_equation_facts_->GetIdsForWhichSynonymsAreKnown(); -} - -std::vector -FactManager::GetSynonymsForDataDescriptor( - const protobufs::DataDescriptor& data_descriptor) const { - return data_synonym_and_id_equation_facts_->GetSynonymsForDataDescriptor( - data_descriptor); -} - -std::vector FactManager::GetSynonymsForId( - uint32_t id) const { - return GetSynonymsForDataDescriptor(MakeDataDescriptor(id, {})); -} - -bool FactManager::IsSynonymous( - const protobufs::DataDescriptor& data_descriptor1, - const protobufs::DataDescriptor& data_descriptor2) const { - return data_synonym_and_id_equation_facts_->IsSynonymous(data_descriptor1, - data_descriptor2); -} - -bool FactManager::BlockIsDead(uint32_t block_id) const { - return dead_block_facts_->BlockIsDead(block_id); -} - -void FactManager::AddFactBlockIsDead(uint32_t block_id) { - protobufs::FactBlockIsDead fact; - fact.set_block_id(block_id); - dead_block_facts_->AddFact(fact); -} - -bool FactManager::FunctionIsLivesafe(uint32_t function_id) const { - return livesafe_function_facts_->FunctionIsLivesafe(function_id); -} - -void FactManager::AddFactFunctionIsLivesafe(uint32_t function_id) { - protobufs::FactFunctionIsLivesafe fact; - fact.set_function_id(function_id); - livesafe_function_facts_->AddFact(fact); -} - -bool FactManager::PointeeValueIsIrrelevant(uint32_t pointer_id) const { - return irrelevant_value_facts_->PointeeValueIsIrrelevant(pointer_id); -} - -bool FactManager::IdIsIrrelevant(uint32_t result_id) const { - return irrelevant_value_facts_->IdIsIrrelevant(result_id); -} - -void FactManager::AddFactValueOfPointeeIsIrrelevant(uint32_t pointer_id) { - protobufs::FactPointeeValueIsIrrelevant fact; - fact.set_pointer_id(pointer_id); - irrelevant_value_facts_->AddFact(fact); -} - -void FactManager::AddFactIdIsIrrelevant(uint32_t result_id) { - // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3550): - // assert that |result_id| is not a part of any DataSynonym fact. - protobufs::FactIdIsIrrelevant fact; - fact.set_result_id(result_id); - irrelevant_value_facts_->AddFact(fact); -} - -void FactManager::AddFactIdEquation(uint32_t lhs_id, SpvOp opcode, - const std::vector& rhs_id, - opt::IRContext* context) { - // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3550): - // assert that elements of |rhs_id| and |lhs_id| are not irrelevant. - protobufs::FactIdEquation fact; - fact.set_lhs_id(lhs_id); - fact.set_opcode(opcode); - for (auto an_rhs_id : rhs_id) { - fact.add_rhs_id(an_rhs_id); - } - data_synonym_and_id_equation_facts_->AddFact(fact, context); -} - -void FactManager::ComputeClosureOfFacts( - opt::IRContext* ir_context, uint32_t maximum_equivalence_class_size) { - data_synonym_and_id_equation_facts_->ComputeClosureOfFacts( - ir_context, maximum_equivalence_class_size); -} - +} // namespace fact_manager } // namespace fuzz } // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/fact_manager/data_synonym_and_id_equation_facts.h b/3rdparty/spirv-tools/source/fuzz/fact_manager/data_synonym_and_id_equation_facts.h new file mode 100644 index 000000000..fb15e2ce8 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/fact_manager/data_synonym_and_id_equation_facts.h @@ -0,0 +1,156 @@ +// 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_FACT_MANAGER_DATA_SYNONYM_AND_ID_EQUATION_FACTS_H_ +#define SOURCE_FUZZ_FACT_MANAGER_DATA_SYNONYM_AND_ID_EQUATION_FACTS_H_ + +#include +#include + +#include "source/fuzz/data_descriptor.h" +#include "source/fuzz/equivalence_relation.h" +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace fuzz { +namespace fact_manager { + +// The purpose of this class is to group the fields and data used to represent +// facts about data synonyms and id equations. +class DataSynonymAndIdEquationFacts { + public: + // See method in FactManager which delegates to this method. + void AddFact(const protobufs::FactDataSynonym& fact, opt::IRContext* context); + + // See method in FactManager which delegates to this method. + void AddFact(const protobufs::FactIdEquation& fact, opt::IRContext* context); + + // See method in FactManager which delegates to this method. + std::vector GetSynonymsForDataDescriptor( + const protobufs::DataDescriptor& data_descriptor) const; + + // See method in FactManager which delegates to this method. + std::vector GetIdsForWhichSynonymsAreKnown() const; + + // See method in FactManager which delegates to this method. + bool IsSynonymous(const protobufs::DataDescriptor& data_descriptor1, + const protobufs::DataDescriptor& data_descriptor2) const; + + // See method in FactManager which delegates to this method. + void ComputeClosureOfFacts(opt::IRContext* context, + uint32_t maximum_equivalence_class_size); + + private: + // This helper struct represents the right hand side of an equation as an + // operator applied to a number of data descriptor operands. + struct Operation { + SpvOp opcode; + std::vector operands; + }; + + // Hashing for operations, to allow deterministic unordered sets. + struct OperationHash { + size_t operator()(const Operation& operation) const; + }; + + // Equality for operations, to allow deterministic unordered sets. + struct OperationEquals { + bool operator()(const Operation& first, const Operation& second) const; + }; + + using OperationSet = + std::unordered_set; + + // Adds the synonym |dd1| = |dd2| to the set of managed facts, and recurses + // into sub-components of the data descriptors, if they are composites, to + // record that their components are pairwise-synonymous. + void AddDataSynonymFactRecursive(const protobufs::DataDescriptor& dd1, + const protobufs::DataDescriptor& dd2, + opt::IRContext* context); + + // Computes various corollary facts from the data descriptor |dd| if members + // of its equivalence class participate in equation facts with OpConvert* + // opcodes. The descriptor should be registered in the equivalence relation. + void ComputeConversionDataSynonymFacts(const protobufs::DataDescriptor& dd, + opt::IRContext* context); + + // Recurses into sub-components of the data descriptors, if they are + // composites, to record that their components are pairwise-synonymous. + void ComputeCompositeDataSynonymFacts(const protobufs::DataDescriptor& dd1, + const protobufs::DataDescriptor& dd2, + opt::IRContext* context); + + // Records the fact that |dd1| and |dd2| are equivalent, and merges the sets + // of equations that are known about them. + void MakeEquivalent(const protobufs::DataDescriptor& dd1, + const protobufs::DataDescriptor& dd2); + + // Registers a data descriptor in the equivalence relation if it hasn't been + // registered yet, and returns its representative. + const protobufs::DataDescriptor* RegisterDataDescriptor( + const protobufs::DataDescriptor& dd); + + // Returns true if and only if |dd1| and |dd2| are valid data descriptors + // whose associated data have compatible types. Two types are compatible if: + // - they are the same + // - they both are numerical or vectors of numerical components with the same + // number of components and the same bit count per component + static bool DataDescriptorsAreWellFormedAndComparable( + opt::IRContext* context, const protobufs::DataDescriptor& dd1, + const protobufs::DataDescriptor& dd2); + + OperationSet GetEquations(const protobufs::DataDescriptor* lhs) const; + + // Requires that |lhs_dd| and every element of |rhs_dds| is present in the + // |synonymous_| equivalence relation, but is not necessarily its own + // representative. Records the fact that the equation + // "|lhs_dd| |opcode| |rhs_dds_non_canonical|" holds, and adds any + // corollaries, in the form of data synonym or equation facts, that follow + // from this and other known facts. + void AddEquationFactRecursive( + const protobufs::DataDescriptor& lhs_dd, SpvOp opcode, + const std::vector& rhs_dds, + opt::IRContext* context); + + // The data descriptors that are known to be synonymous with one another are + // captured by this equivalence relation. + EquivalenceRelation + synonymous_; + + // When a new synonym fact is added, it may be possible to deduce further + // synonym facts by computing a closure of all known facts. However, this is + // an expensive operation, so it should be performed sparingly and only there + // is some chance of new facts being deduced. This boolean tracks whether a + // closure computation is required - i.e., whether a new fact has been added + // since the last time such a computation was performed. + bool closure_computation_required_ = false; + + // Represents a set of equations on data descriptors as a map indexed by + // left-hand-side, mapping a left-hand-side to a set of operations, each of + // which (together with the left-hand-side) defines an equation. + // + // All data descriptors occurring in equations are required to be present in + // the |synonymous_| equivalence relation, and to be their own representatives + // in that relation. + std::unordered_map + id_equations_; +}; + +} // namespace fact_manager +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_FACT_MANAGER_DATA_SYNONYM_AND_ID_EQUATION_FACTS_H_ diff --git a/3rdparty/spirv-tools/source/fuzz/fact_manager/dead_block_facts.cpp b/3rdparty/spirv-tools/source/fuzz/fact_manager/dead_block_facts.cpp new file mode 100644 index 000000000..86d51af5c --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/fact_manager/dead_block_facts.cpp @@ -0,0 +1,31 @@ +// 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/fact_manager/dead_block_facts.h" + +namespace spvtools { +namespace fuzz { +namespace fact_manager { + +void DeadBlockFacts::AddFact(const protobufs::FactBlockIsDead& fact) { + dead_block_ids_.insert(fact.block_id()); +} + +bool DeadBlockFacts::BlockIsDead(uint32_t block_id) const { + return dead_block_ids_.count(block_id) != 0; +} + +} // namespace fact_manager +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/fact_manager/dead_block_facts.h b/3rdparty/spirv-tools/source/fuzz/fact_manager/dead_block_facts.h new file mode 100644 index 000000000..ca3f71acf --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/fact_manager/dead_block_facts.h @@ -0,0 +1,44 @@ +// 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_FACT_MANAGER_DEAD_BLOCK_FACTS_H_ +#define SOURCE_FUZZ_FACT_MANAGER_DEAD_BLOCK_FACTS_H_ + +#include + +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" + +namespace spvtools { +namespace fuzz { +namespace fact_manager { + +// The purpose of this class is to group the fields and data used to represent +// facts about data blocks. +class DeadBlockFacts { + public: + // See method in FactManager which delegates to this method. + void AddFact(const protobufs::FactBlockIsDead& fact); + + // See method in FactManager which delegates to this method. + bool BlockIsDead(uint32_t block_id) const; + + private: + std::unordered_set dead_block_ids_; +}; + +} // namespace fact_manager +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_FACT_MANAGER_DEAD_BLOCK_FACTS_H_ diff --git a/3rdparty/spirv-tools/source/fuzz/fact_manager/fact_manager.cpp b/3rdparty/spirv-tools/source/fuzz/fact_manager/fact_manager.cpp new file mode 100644 index 000000000..cd9fccd44 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/fact_manager/fact_manager.cpp @@ -0,0 +1,244 @@ +// 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 "fact_manager.h" + +#include +#include + +#include "source/fuzz/uniform_buffer_element_descriptor.h" +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace fuzz { +namespace { + +std::string ToString(const protobufs::FactConstantUniform& fact) { + std::stringstream stream; + stream << "(" << fact.uniform_buffer_element_descriptor().descriptor_set() + << ", " << fact.uniform_buffer_element_descriptor().binding() << ")["; + + bool first = true; + for (auto index : fact.uniform_buffer_element_descriptor().index()) { + if (first) { + first = false; + } else { + stream << ", "; + } + stream << index; + } + + stream << "] == ["; + + first = true; + for (auto constant_word : fact.constant_word()) { + if (first) { + first = false; + } else { + stream << ", "; + } + stream << constant_word; + } + + stream << "]"; + return stream.str(); +} + +std::string ToString(const protobufs::FactDataSynonym& fact) { + std::stringstream stream; + stream << fact.data1() << " = " << fact.data2(); + return stream.str(); +} + +std::string ToString(const protobufs::FactIdEquation& fact) { + std::stringstream stream; + stream << fact.lhs_id(); + stream << " " << static_cast(fact.opcode()); + for (auto rhs_id : fact.rhs_id()) { + stream << " " << rhs_id; + } + return stream.str(); +} + +std::string ToString(const protobufs::Fact& fact) { + switch (fact.fact_case()) { + case protobufs::Fact::kConstantUniformFact: + return ToString(fact.constant_uniform_fact()); + case protobufs::Fact::kDataSynonymFact: + return ToString(fact.data_synonym_fact()); + case protobufs::Fact::kIdEquationFact: + return ToString(fact.id_equation_fact()); + default: + assert(false && "Stringification not supported for this fact."); + return ""; + } +} + +} // namespace + +void FactManager::AddFacts(const MessageConsumer& message_consumer, + const protobufs::FactSequence& initial_facts, + opt::IRContext* context) { + for (auto& fact : initial_facts.fact()) { + if (!AddFact(fact, context)) { + auto message = "Invalid fact " + ToString(fact) + " ignored."; + message_consumer(SPV_MSG_WARNING, nullptr, {}, message.c_str()); + } + } +} + +bool FactManager::AddFact(const fuzz::protobufs::Fact& fact, + opt::IRContext* context) { + switch (fact.fact_case()) { + case protobufs::Fact::kConstantUniformFact: + return constant_uniform_facts_.AddFact(fact.constant_uniform_fact(), + context); + case protobufs::Fact::kDataSynonymFact: + data_synonym_and_id_equation_facts_.AddFact(fact.data_synonym_fact(), + context); + return true; + case protobufs::Fact::kBlockIsDeadFact: + dead_block_facts_.AddFact(fact.block_is_dead_fact()); + return true; + case protobufs::Fact::kFunctionIsLivesafeFact: + livesafe_function_facts_.AddFact(fact.function_is_livesafe_fact()); + return true; + default: + assert(false && "Unknown fact type."); + return false; + } +} + +void FactManager::AddFactDataSynonym(const protobufs::DataDescriptor& data1, + const protobufs::DataDescriptor& data2, + opt::IRContext* context) { + protobufs::FactDataSynonym fact; + *fact.mutable_data1() = data1; + *fact.mutable_data2() = data2; + data_synonym_and_id_equation_facts_.AddFact(fact, context); +} + +std::vector FactManager::GetConstantsAvailableFromUniformsForType( + opt::IRContext* ir_context, uint32_t type_id) const { + return constant_uniform_facts_.GetConstantsAvailableFromUniformsForType( + ir_context, type_id); +} + +std::vector +FactManager::GetUniformDescriptorsForConstant(opt::IRContext* ir_context, + uint32_t constant_id) const { + return constant_uniform_facts_.GetUniformDescriptorsForConstant(ir_context, + constant_id); +} + +uint32_t FactManager::GetConstantFromUniformDescriptor( + opt::IRContext* context, + const protobufs::UniformBufferElementDescriptor& uniform_descriptor) const { + return constant_uniform_facts_.GetConstantFromUniformDescriptor( + context, uniform_descriptor); +} + +std::vector FactManager::GetTypesForWhichUniformValuesAreKnown() + const { + return constant_uniform_facts_.GetTypesForWhichUniformValuesAreKnown(); +} + +const std::vector>& +FactManager::GetConstantUniformFactsAndTypes() const { + return constant_uniform_facts_.GetConstantUniformFactsAndTypes(); +} + +std::vector FactManager::GetIdsForWhichSynonymsAreKnown() const { + return data_synonym_and_id_equation_facts_.GetIdsForWhichSynonymsAreKnown(); +} + +std::vector +FactManager::GetSynonymsForDataDescriptor( + const protobufs::DataDescriptor& data_descriptor) const { + return data_synonym_and_id_equation_facts_.GetSynonymsForDataDescriptor( + data_descriptor); +} + +std::vector FactManager::GetSynonymsForId( + uint32_t id) const { + return GetSynonymsForDataDescriptor(MakeDataDescriptor(id, {})); +} + +bool FactManager::IsSynonymous( + const protobufs::DataDescriptor& data_descriptor1, + const protobufs::DataDescriptor& data_descriptor2) const { + return data_synonym_and_id_equation_facts_.IsSynonymous(data_descriptor1, + data_descriptor2); +} + +bool FactManager::BlockIsDead(uint32_t block_id) const { + return dead_block_facts_.BlockIsDead(block_id); +} + +void FactManager::AddFactBlockIsDead(uint32_t block_id) { + protobufs::FactBlockIsDead fact; + fact.set_block_id(block_id); + dead_block_facts_.AddFact(fact); +} + +bool FactManager::FunctionIsLivesafe(uint32_t function_id) const { + return livesafe_function_facts_.FunctionIsLivesafe(function_id); +} + +void FactManager::AddFactFunctionIsLivesafe(uint32_t function_id) { + protobufs::FactFunctionIsLivesafe fact; + fact.set_function_id(function_id); + livesafe_function_facts_.AddFact(fact); +} + +bool FactManager::PointeeValueIsIrrelevant(uint32_t pointer_id) const { + return irrelevant_value_facts_.PointeeValueIsIrrelevant(pointer_id); +} + +bool FactManager::IdIsIrrelevant(uint32_t result_id) const { + return irrelevant_value_facts_.IdIsIrrelevant(result_id); +} + +void FactManager::AddFactValueOfPointeeIsIrrelevant(uint32_t pointer_id) { + protobufs::FactPointeeValueIsIrrelevant fact; + fact.set_pointer_id(pointer_id); + irrelevant_value_facts_.AddFact(fact); +} + +void FactManager::AddFactIdIsIrrelevant(uint32_t result_id) { + protobufs::FactIdIsIrrelevant fact; + fact.set_result_id(result_id); + irrelevant_value_facts_.AddFact(fact); +} + +void FactManager::AddFactIdEquation(uint32_t lhs_id, SpvOp opcode, + const std::vector& rhs_id, + opt::IRContext* context) { + protobufs::FactIdEquation fact; + fact.set_lhs_id(lhs_id); + fact.set_opcode(opcode); + for (auto an_rhs_id : rhs_id) { + fact.add_rhs_id(an_rhs_id); + } + data_synonym_and_id_equation_facts_.AddFact(fact, context); +} + +void FactManager::ComputeClosureOfFacts( + opt::IRContext* ir_context, uint32_t maximum_equivalence_class_size) { + data_synonym_and_id_equation_facts_.ComputeClosureOfFacts( + ir_context, maximum_equivalence_class_size); +} + +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/fact_manager.h b/3rdparty/spirv-tools/source/fuzz/fact_manager/fact_manager.h similarity index 84% rename from 3rdparty/spirv-tools/source/fuzz/fact_manager.h rename to 3rdparty/spirv-tools/source/fuzz/fact_manager/fact_manager.h index f83e2ff60..89d1ab04e 100644 --- a/3rdparty/spirv-tools/source/fuzz/fact_manager.h +++ b/3rdparty/spirv-tools/source/fuzz/fact_manager/fact_manager.h @@ -12,15 +12,19 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef SOURCE_FUZZ_FACT_MANAGER_H_ -#define SOURCE_FUZZ_FACT_MANAGER_H_ +#ifndef SOURCE_FUZZ_FACT_MANAGER_FACT_MANAGER_H_ +#define SOURCE_FUZZ_FACT_MANAGER_FACT_MANAGER_H_ -#include #include #include #include #include "source/fuzz/data_descriptor.h" +#include "source/fuzz/fact_manager/constant_uniform_facts.h" +#include "source/fuzz/fact_manager/data_synonym_and_id_equation_facts.h" +#include "source/fuzz/fact_manager/dead_block_facts.h" +#include "source/fuzz/fact_manager/irrelevant_value_facts.h" +#include "source/fuzz/fact_manager/livesafe_function_facts.h" #include "source/fuzz/protobufs/spirvfuzz_protobufs.h" #include "source/opt/constants.h" @@ -38,10 +42,6 @@ namespace fuzz { // the module. class FactManager { public: - FactManager(); - - ~FactManager(); - // Adds all the facts from |facts|, checking them for validity with respect to // |context|. Warnings about invalid facts are communicated via // |message_consumer|; such facts are otherwise ignored. @@ -117,7 +117,7 @@ class FactManager { // Provides details of all uniform elements that are known to be equal to the // constant associated with |constant_id| in |ir_context|. - const std::vector + std::vector GetUniformDescriptorsForConstant(opt::IRContext* ir_context, uint32_t constant_id) const; @@ -198,35 +198,16 @@ class FactManager { //============================== private: - // For each distinct kind of fact to be managed, we use a separate opaque - // class type. - - class ConstantUniformFacts; // Opaque class for management of - // constant uniform facts. - std::unique_ptr - uniform_constant_facts_; // Unique pointer to internal data. - - class DataSynonymAndIdEquationFacts; // Opaque class for management of data - // synonym and id equation facts. - std::unique_ptr - data_synonym_and_id_equation_facts_; // Unique pointer to internal data. - - class DeadBlockFacts; // Opaque class for management of dead block facts. - std::unique_ptr - dead_block_facts_; // Unique pointer to internal data. - - class LivesafeFunctionFacts; // Opaque class for management of livesafe - // function facts. - std::unique_ptr - livesafe_function_facts_; // Unique pointer to internal data. - - class IrrelevantValueFacts; // Opaque class for management of - // facts about various irrelevant values in the module. - std::unique_ptr - irrelevant_value_facts_; // Unique pointer to internal data. + // Keep these in alphabetical order. + fact_manager::ConstantUniformFacts constant_uniform_facts_; + fact_manager::DataSynonymAndIdEquationFacts + data_synonym_and_id_equation_facts_; + fact_manager::DeadBlockFacts dead_block_facts_; + fact_manager::LivesafeFunctionFacts livesafe_function_facts_; + fact_manager::IrrelevantValueFacts irrelevant_value_facts_; }; } // namespace fuzz } // namespace spvtools -#endif // SOURCE_FUZZ_FACT_MANAGER_H_ +#endif // SOURCE_FUZZ_FACT_MANAGER_FACT_MANAGER_H_ diff --git a/3rdparty/spirv-tools/source/fuzz/fact_manager/irrelevant_value_facts.cpp b/3rdparty/spirv-tools/source/fuzz/fact_manager/irrelevant_value_facts.cpp new file mode 100644 index 000000000..85e2b6f1e --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/fact_manager/irrelevant_value_facts.cpp @@ -0,0 +1,51 @@ +// 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/fact_manager/irrelevant_value_facts.h" + +#include "source/fuzz/data_descriptor.h" +#include "source/fuzz/fuzzer_util.h" + +namespace spvtools { +namespace fuzz { +namespace fact_manager { + +void IrrelevantValueFacts::AddFact( + const protobufs::FactPointeeValueIsIrrelevant& fact) { + // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3550) + // Assert that the id does not participate in DataSynonym facts and is a + // pointer. + + pointers_to_irrelevant_pointees_ids_.insert(fact.pointer_id()); +} + +void IrrelevantValueFacts::AddFact(const protobufs::FactIdIsIrrelevant& fact) { + // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3550) + // Assert that the id does not participate in DataSynonym facts and is not a + // pointer. + + irrelevant_ids_.insert(fact.result_id()); +} + +bool IrrelevantValueFacts::PointeeValueIsIrrelevant(uint32_t pointer_id) const { + return pointers_to_irrelevant_pointees_ids_.count(pointer_id) != 0; +} + +bool IrrelevantValueFacts::IdIsIrrelevant(uint32_t pointer_id) const { + return irrelevant_ids_.count(pointer_id) != 0; +} + +} // namespace fact_manager +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/fact_manager/irrelevant_value_facts.h b/3rdparty/spirv-tools/source/fuzz/fact_manager/irrelevant_value_facts.h new file mode 100644 index 000000000..ca926c78a --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/fact_manager/irrelevant_value_facts.h @@ -0,0 +1,52 @@ +// 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_FACT_MANAGER_IRRELEVANT_VALUE_FACTS_H_ +#define SOURCE_FUZZ_FACT_MANAGER_IRRELEVANT_VALUE_FACTS_H_ + +#include + +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace fuzz { +namespace fact_manager { + +// The purpose of this class is to group the fields and data used to represent +// facts about various irrelevant values in the module. +class IrrelevantValueFacts { + public: + // See method in FactManager which delegates to this method. + void AddFact(const protobufs::FactPointeeValueIsIrrelevant& fact); + + // See method in FactManager which delegates to this method. + void AddFact(const protobufs::FactIdIsIrrelevant& fact); + + // See method in FactManager which delegates to this method. + bool PointeeValueIsIrrelevant(uint32_t pointer_id) const; + + // See method in FactManager which delegates to this method. + bool IdIsIrrelevant(uint32_t pointer_id) const; + + private: + std::unordered_set pointers_to_irrelevant_pointees_ids_; + std::unordered_set irrelevant_ids_; +}; + +} // namespace fact_manager +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_FACT_MANAGER_IRRELEVANT_VALUE_FACTS_H_ diff --git a/3rdparty/spirv-tools/source/fuzz/fact_manager/livesafe_function_facts.cpp b/3rdparty/spirv-tools/source/fuzz/fact_manager/livesafe_function_facts.cpp new file mode 100644 index 000000000..6f36afb6e --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/fact_manager/livesafe_function_facts.cpp @@ -0,0 +1,32 @@ +// 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/fact_manager/livesafe_function_facts.h" + +namespace spvtools { +namespace fuzz { +namespace fact_manager { + +void LivesafeFunctionFacts::AddFact( + const protobufs::FactFunctionIsLivesafe& fact) { + livesafe_function_ids_.insert(fact.function_id()); +} + +bool LivesafeFunctionFacts::FunctionIsLivesafe(uint32_t function_id) const { + return livesafe_function_ids_.count(function_id) != 0; +} + +} // namespace fact_manager +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/fact_manager/livesafe_function_facts.h b/3rdparty/spirv-tools/source/fuzz/fact_manager/livesafe_function_facts.h new file mode 100644 index 000000000..8c4850630 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/fact_manager/livesafe_function_facts.h @@ -0,0 +1,44 @@ +// 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_FACT_MANAGER_LIVESAFE_FUNCTION_FACTS_H_ +#define SOURCE_FUZZ_FACT_MANAGER_LIVESAFE_FUNCTION_FACTS_H_ + +#include + +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" + +namespace spvtools { +namespace fuzz { +namespace fact_manager { + +// The purpose of this class is to group the fields and data used to represent +// facts about livesafe functions. +class LivesafeFunctionFacts { + public: + // See method in FactManager which delegates to this method. + void AddFact(const protobufs::FactFunctionIsLivesafe& fact); + + // See method in FactManager which delegates to this method. + bool FunctionIsLivesafe(uint32_t function_id) const; + + private: + std::unordered_set livesafe_function_ids_; +}; + +} // namespace fact_manager +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_FACT_MANAGER_LIVESAFE_FUNCTION_FACTS_H_ diff --git a/3rdparty/spirv-tools/source/fuzz/force_render_red.cpp b/3rdparty/spirv-tools/source/fuzz/force_render_red.cpp index 5bf287980..2a9d56f9f 100644 --- a/3rdparty/spirv-tools/source/fuzz/force_render_red.cpp +++ b/3rdparty/spirv-tools/source/fuzz/force_render_red.cpp @@ -14,7 +14,7 @@ #include "source/fuzz/force_render_red.h" -#include "source/fuzz/fact_manager.h" +#include "source/fuzz/fact_manager/fact_manager.h" #include "source/fuzz/instruction_descriptor.h" #include "source/fuzz/protobufs/spirvfuzz_protobufs.h" #include "source/fuzz/transformation_context.h" @@ -26,9 +26,6 @@ #include "source/util/make_unique.h" #include "tools/util/cli_consumer.h" -#include -#include - namespace spvtools { namespace fuzz { diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer.cpp index 651db36f7..4cf3ba37c 100644 --- a/3rdparty/spirv-tools/source/fuzz/fuzzer.cpp +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer.cpp @@ -16,9 +16,8 @@ #include #include -#include -#include "source/fuzz/fact_manager.h" +#include "source/fuzz/fact_manager/fact_manager.h" #include "source/fuzz/fuzzer_context.h" #include "source/fuzz/fuzzer_pass_add_access_chains.h" #include "source/fuzz/fuzzer_pass_add_composite_inserts.h" @@ -35,6 +34,7 @@ #include "source/fuzz/fuzzer_pass_add_local_variables.h" #include "source/fuzz/fuzzer_pass_add_loop_preheaders.h" #include "source/fuzz/fuzzer_pass_add_no_contraction_decorations.h" +#include "source/fuzz/fuzzer_pass_add_opphi_synonyms.h" #include "source/fuzz/fuzzer_pass_add_parameters.h" #include "source/fuzz/fuzzer_pass_add_relaxed_decorations.h" #include "source/fuzz/fuzzer_pass_add_stores.h" @@ -49,6 +49,7 @@ #include "source/fuzz/fuzzer_pass_construct_composites.h" #include "source/fuzz/fuzzer_pass_copy_objects.h" #include "source/fuzz/fuzzer_pass_donate_modules.h" +#include "source/fuzz/fuzzer_pass_inline_functions.h" #include "source/fuzz/fuzzer_pass_interchange_signedness_of_integer_operands.h" #include "source/fuzz/fuzzer_pass_interchange_zero_like_constants.h" #include "source/fuzz/fuzzer_pass_invert_comparison_operators.h" @@ -110,51 +111,32 @@ void MaybeAddPass( } // namespace -struct Fuzzer::Impl { - Impl(spv_target_env env, uint32_t random_seed, bool validate_after_each_pass, - spv_validator_options options) - : target_env(env), - seed(random_seed), - validate_after_each_fuzzer_pass(validate_after_each_pass), - validator_options(options) {} - - bool ApplyPassAndCheckValidity(FuzzerPass* pass, - const opt::IRContext& ir_context, - const spvtools::SpirvTools& tools) const; - - const spv_target_env target_env; // Target environment. - MessageConsumer consumer; // Message consumer. - const uint32_t seed; // Seed for random number generator. - bool validate_after_each_fuzzer_pass; // Determines whether the validator - // should be invoked after every fuzzer - // pass. - spv_validator_options validator_options; // Options to control validation. -}; - -Fuzzer::Fuzzer(spv_target_env env, uint32_t seed, +Fuzzer::Fuzzer(spv_target_env target_env, uint32_t seed, bool validate_after_each_fuzzer_pass, spv_validator_options validator_options) - : impl_(MakeUnique(env, seed, validate_after_each_fuzzer_pass, - validator_options)) {} + : target_env_(target_env), + seed_(seed), + validate_after_each_fuzzer_pass_(validate_after_each_fuzzer_pass), + validator_options_(validator_options) {} Fuzzer::~Fuzzer() = default; -void Fuzzer::SetMessageConsumer(MessageConsumer c) { - impl_->consumer = std::move(c); +void Fuzzer::SetMessageConsumer(MessageConsumer consumer) { + consumer_ = std::move(consumer); } -bool Fuzzer::Impl::ApplyPassAndCheckValidity( +bool Fuzzer::ApplyPassAndCheckValidity( FuzzerPass* pass, const opt::IRContext& ir_context, const spvtools::SpirvTools& tools) const { pass->Apply(); - if (validate_after_each_fuzzer_pass) { + if (validate_after_each_fuzzer_pass_) { std::vector binary_to_validate; ir_context.module()->ToBinary(&binary_to_validate, false); if (!tools.Validate(&binary_to_validate[0], binary_to_validate.size(), - validator_options)) { - consumer(SPV_MSG_INFO, nullptr, {}, - "Binary became invalid during fuzzing (set a breakpoint to " - "inspect); stopping."); + validator_options_)) { + consumer_(SPV_MSG_INFO, nullptr, {}, + "Binary became invalid during fuzzing (set a breakpoint to " + "inspect); stopping."); return false; } } @@ -171,29 +153,28 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run( // header files being used. GOOGLE_PROTOBUF_VERIFY_VERSION; - spvtools::SpirvTools tools(impl_->target_env); - tools.SetMessageConsumer(impl_->consumer); + spvtools::SpirvTools tools(target_env_); + tools.SetMessageConsumer(consumer_); if (!tools.IsValid()) { - impl_->consumer(SPV_MSG_ERROR, nullptr, {}, - "Failed to create SPIRV-Tools interface; stopping."); + consumer_(SPV_MSG_ERROR, nullptr, {}, + "Failed to create SPIRV-Tools interface; stopping."); return Fuzzer::FuzzerResultStatus::kFailedToCreateSpirvToolsInterface; } // Initial binary should be valid. - if (!tools.Validate(&binary_in[0], binary_in.size(), - impl_->validator_options)) { - impl_->consumer(SPV_MSG_ERROR, nullptr, {}, - "Initial binary is invalid; stopping."); + if (!tools.Validate(&binary_in[0], binary_in.size(), validator_options_)) { + consumer_(SPV_MSG_ERROR, nullptr, {}, + "Initial binary is invalid; stopping."); return Fuzzer::FuzzerResultStatus::kInitialBinaryInvalid; } // Build the module from the input binary. - std::unique_ptr ir_context = BuildModule( - impl_->target_env, impl_->consumer, binary_in.data(), binary_in.size()); + std::unique_ptr ir_context = + BuildModule(target_env_, consumer_, binary_in.data(), binary_in.size()); assert(ir_context); // Make a PRNG from the seed passed to the fuzzer on creation. - PseudoRandomGenerator random_generator(impl_->seed); + PseudoRandomGenerator random_generator(seed_); // The fuzzer will introduce new ids into the module. The module's id bound // gives the smallest id that can be used for this purpose. We add an offset @@ -206,9 +187,9 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run( FuzzerContext fuzzer_context(&random_generator, minimum_fresh_id); FactManager fact_manager; - fact_manager.AddFacts(impl_->consumer, initial_facts, ir_context.get()); + fact_manager.AddFacts(consumer_, initial_facts, ir_context.get()); TransformationContext transformation_context(&fact_manager, - impl_->validator_options); + validator_options_); // Apply some semantics-preserving passes. std::vector> passes; @@ -255,6 +236,9 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run( MaybeAddPass( &passes, ir_context.get(), &transformation_context, &fuzzer_context, transformation_sequence_out); + MaybeAddPass( + &passes, ir_context.get(), &transformation_context, &fuzzer_context, + transformation_sequence_out); MaybeAddPass( &passes, ir_context.get(), &transformation_context, &fuzzer_context, transformation_sequence_out); @@ -282,6 +266,9 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run( MaybeAddPass( &passes, ir_context.get(), &transformation_context, &fuzzer_context, transformation_sequence_out, donor_suppliers); + MaybeAddPass( + &passes, ir_context.get(), &transformation_context, &fuzzer_context, + transformation_sequence_out); MaybeAddPass( &passes, ir_context.get(), &transformation_context, &fuzzer_context, transformation_sequence_out); @@ -348,7 +335,7 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run( (is_first || fuzzer_context.ChoosePercentage(kChanceOfApplyingAnotherPass))) { is_first = false; - if (!impl_->ApplyPassAndCheckValidity( + if (!ApplyPassAndCheckValidity( passes[fuzzer_context.RandomIndex(passes)].get(), *ir_context, tools)) { return Fuzzer::FuzzerResultStatus::kFuzzerPassLedToInvalidModule; @@ -392,7 +379,7 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run( &final_passes, ir_context.get(), &transformation_context, &fuzzer_context, transformation_sequence_out); for (auto& pass : final_passes) { - if (!impl_->ApplyPassAndCheckValidity(pass.get(), *ir_context, tools)) { + if (!ApplyPassAndCheckValidity(pass.get(), *ir_context, tools)) { return Fuzzer::FuzzerResultStatus::kFuzzerPassLedToInvalidModule; } } diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer.h b/3rdparty/spirv-tools/source/fuzz/fuzzer.h index 6c3ef71cc..a437d5809 100644 --- a/3rdparty/spirv-tools/source/fuzz/fuzzer.h +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer.h @@ -18,6 +18,7 @@ #include #include +#include "source/fuzz/fuzzer_pass.h" #include "source/fuzz/fuzzer_util.h" #include "source/fuzz/protobufs/spirvfuzz_protobufs.h" #include "spirv-tools/libspirv.hpp" @@ -37,11 +38,11 @@ class Fuzzer { kInitialBinaryInvalid, }; - // Constructs a fuzzer from the given target environment |env|. |seed| is a - // seed for pseudo-random number generation. + // Constructs a fuzzer from the given target environment |target_env|. |seed| + // is a seed for pseudo-random number generation. // |validate_after_each_fuzzer_pass| controls whether the validator will be // invoked after every fuzzer pass is applied. - Fuzzer(spv_target_env env, uint32_t seed, + Fuzzer(spv_target_env target_env, uint32_t seed, bool validate_after_each_fuzzer_pass, spv_validator_options validator_options); @@ -71,8 +72,27 @@ class Fuzzer { protobufs::TransformationSequence* transformation_sequence_out) const; private: - struct Impl; // Opaque struct for holding internal data. - std::unique_ptr impl_; // Unique pointer to internal data. + // Applies |pass|, which must be a pass constructed with |ir_context|, and + // then returns true if and only if |ir_context| is valid. |tools| is used to + // check validity. + bool ApplyPassAndCheckValidity(FuzzerPass* pass, + const opt::IRContext& ir_context, + const spvtools::SpirvTools& tools) const; + + // Target environment. + const spv_target_env target_env_; + + // Message consumer. + MessageConsumer consumer_; + + // Seed for random number generator. + const uint32_t seed_; + + // Determines whether the validator should be invoked after every fuzzer pass. + bool validate_after_each_fuzzer_pass_; + + // Options to control validation. + spv_validator_options validator_options_; }; } // namespace fuzz diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_context.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer_context.cpp index a187daa45..6f76bbb67 100644 --- a/3rdparty/spirv-tools/source/fuzz/fuzzer_context.cpp +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_context.cpp @@ -43,6 +43,7 @@ const std::pair kChanceOfAddingLoopPreheader = {20, 90}; const std::pair kChanceOfAddingMatrixType = {20, 70}; const std::pair kChanceOfAddingNoContractionDecoration = { 5, 70}; +const std::pair kChanceOfAddingOpPhiSynonym = {5, 70}; const std::pair kChanceOfAddingParameters = {5, 70}; const std::pair kChanceOfAddingRelaxedDecoration = {20, 90}; const std::pair kChanceOfAddingStore = {5, 50}; @@ -69,6 +70,7 @@ const std::pair kChanceOfGoingDeeperToInsertInComposite = { 30, 70}; const std::pair kChanceOfGoingDeeperWhenMakingAccessChain = {50, 95}; +const std::pair kChanceOfInliningFunction = {10, 90}; const std::pair kChanceOfInterchangingZeroLikeConstants = { 10, 90}; const std::pair @@ -181,6 +183,8 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator, ChooseBetweenMinAndMax(kChanceOfAddingMatrixType); chance_of_adding_no_contraction_decoration_ = ChooseBetweenMinAndMax(kChanceOfAddingNoContractionDecoration); + chance_of_adding_opphi_synonym_ = + ChooseBetweenMinAndMax(kChanceOfAddingOpPhiSynonym); chance_of_adding_parameters = ChooseBetweenMinAndMax(kChanceOfAddingParameters); chance_of_adding_relaxed_decoration_ = @@ -216,6 +220,8 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator, ChooseBetweenMinAndMax(kChanceOfGoingDeeperToInsertInComposite); chance_of_going_deeper_when_making_access_chain_ = ChooseBetweenMinAndMax(kChanceOfGoingDeeperWhenMakingAccessChain); + chance_of_inlining_function_ = + ChooseBetweenMinAndMax(kChanceOfInliningFunction); chance_of_interchanging_signedness_of_integer_operands_ = ChooseBetweenMinAndMax(kChanceOfInterchangingSignednessOfIntegerOperands); chance_of_interchanging_zero_like_constants_ = diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_context.h b/3rdparty/spirv-tools/source/fuzz/fuzzer_context.h index c50161406..bc98bc0c9 100644 --- a/3rdparty/spirv-tools/source/fuzz/fuzzer_context.h +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_context.h @@ -148,6 +148,9 @@ class FuzzerContext { uint32_t GetChanceOfAddingNoContractionDecoration() { return chance_of_adding_no_contraction_decoration_; } + uint32_t GetChanceOfAddingOpPhiSynonym() { + return chance_of_adding_opphi_synonym_; + } uint32_t GetChanceOfAddingParameters() { return chance_of_adding_parameters; } uint32_t GetChanceOfAddingRelaxedDecoration() { return chance_of_adding_relaxed_decoration_; @@ -195,6 +198,9 @@ class FuzzerContext { uint32_t GetChanceOfGoingDeeperWhenMakingAccessChain() { return chance_of_going_deeper_when_making_access_chain_; } + uint32_t GetChanceOfInliningFunction() { + return chance_of_inlining_function_; + } uint32_t GetChanceOfInterchangingSignednessOfIntegerOperands() { return chance_of_interchanging_signedness_of_integer_operands_; } @@ -364,6 +370,7 @@ class FuzzerContext { uint32_t chance_of_adding_loop_preheader_; uint32_t chance_of_adding_matrix_type_; uint32_t chance_of_adding_no_contraction_decoration_; + uint32_t chance_of_adding_opphi_synonym_; uint32_t chance_of_adding_parameters; uint32_t chance_of_adding_relaxed_decoration_; uint32_t chance_of_adding_store_; @@ -383,6 +390,7 @@ class FuzzerContext { uint32_t chance_of_donating_additional_module_; uint32_t chance_of_going_deeper_to_insert_in_composite_; uint32_t chance_of_going_deeper_when_making_access_chain_; + uint32_t chance_of_inlining_function_; uint32_t chance_of_interchanging_signedness_of_integer_operands_; uint32_t chance_of_interchanging_zero_like_constants_; uint32_t chance_of_inverting_comparison_operators_; diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass.cpp index 3097bc707..a6a698ecd 100644 --- a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass.cpp +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass.cpp @@ -296,8 +296,6 @@ uint32_t FuzzerPass::FindOrCreateIntegerConstant( uint32_t FuzzerPass::FindOrCreateFloatConstant( const std::vector& words, uint32_t width, bool is_irrelevant) { auto float_type_id = FindOrCreateFloatType(width); - opt::analysis::FloatConstant float_constant( - GetIRContext()->get_type_mgr()->GetType(float_type_id)->AsFloat(), words); if (auto constant_id = fuzzerutil::MaybeGetScalarConstant( GetIRContext(), *GetTransformationContext(), words, float_type_id, is_irrelevant)) { diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_opphi_synonyms.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_opphi_synonyms.cpp new file mode 100644 index 000000000..97adfb240 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_opphi_synonyms.cpp @@ -0,0 +1,297 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/fuzzer_pass_add_opphi_synonyms.h" + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/transformation_add_opphi_synonym.h" + +namespace spvtools { +namespace fuzz { + +FuzzerPassAddOpPhiSynonyms::FuzzerPassAddOpPhiSynonyms( + opt::IRContext* ir_context, TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations) + : FuzzerPass(ir_context, transformation_context, fuzzer_context, + transformations) {} + +FuzzerPassAddOpPhiSynonyms::~FuzzerPassAddOpPhiSynonyms() = default; + +void FuzzerPassAddOpPhiSynonyms::Apply() { + // Get a list of synonymous ids with the same type that can be used in the + // same OpPhi instruction. + auto equivalence_classes = GetIdEquivalenceClasses(); + + // Make a list of references, to avoid copying sets unnecessarily. + std::vector*> equivalence_class_pointers; + for (auto& set : equivalence_classes) { + equivalence_class_pointers.push_back(&set); + } + + // Keep a list of transformations to apply at the end. + std::vector transformations_to_apply; + + for (auto& function : *GetIRContext()->module()) { + for (auto& block : function) { + // Randomly decide whether to consider this block. + if (!GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext()->GetChanceOfAddingOpPhiSynonym())) { + continue; + } + + // The block must have at least one predecessor. + size_t num_preds = GetIRContext()->cfg()->preds(block.id()).size(); + if (num_preds == 0) { + continue; + } + + std::set* chosen_equivalence_class = nullptr; + + if (num_preds > 1) { + // If the block has more than one predecessor, prioritise sets with at + // least 2 ids available at some predecessor. + chosen_equivalence_class = MaybeFindSuitableEquivalenceClassRandomly( + equivalence_class_pointers, block.id(), 2); + } + + // If a set was not already chosen, choose one with at least one available + // id. + if (!chosen_equivalence_class) { + chosen_equivalence_class = MaybeFindSuitableEquivalenceClassRandomly( + equivalence_class_pointers, block.id(), 1); + } + + // If no suitable set was found, we cannot apply the transformation to + // this block. + if (!chosen_equivalence_class) { + continue; + } + + // Initialise the map from predecessor labels to ids. + std::map preds_to_ids; + + // Keep track of the ids used and of the id of a predecessor with at least + // two ids to choose from. This is to ensure that, if possible, at least + // two distinct ids will be used. + std::set ids_chosen; + uint32_t pred_with_alternatives = 0; + + // Choose an id for each predecessor. + for (uint32_t pred_id : GetIRContext()->cfg()->preds(block.id())) { + auto suitable_ids = GetSuitableIds(*chosen_equivalence_class, pred_id); + assert(!suitable_ids.empty() && + "We must be able to find at least one suitable id because the " + "equivalence class was chosen among suitable ones."); + + // If this predecessor has more than one id to choose from and it is the + // first one of this kind that we found, remember its id. + if (suitable_ids.size() > 1 && !pred_with_alternatives) { + pred_with_alternatives = pred_id; + } + + uint32_t chosen_id = + suitable_ids[GetFuzzerContext()->RandomIndex(suitable_ids)]; + + // Add this id to the set of ids chosen. + ids_chosen.emplace(chosen_id); + + // Add the pair (predecessor, chosen id) to the map. + preds_to_ids[pred_id] = chosen_id; + } + + // If: + // - the block has more than one predecessor + // - at least one predecessor has more than one alternative + // - the same id has been chosen by all the predecessors + // then choose another one for the predecessor with more than one + // alternative. + if (num_preds > 1 && pred_with_alternatives != 0 && + ids_chosen.size() == 1) { + auto suitable_ids = + GetSuitableIds(*chosen_equivalence_class, pred_with_alternatives); + uint32_t chosen_id = + GetFuzzerContext()->RemoveAtRandomIndex(&suitable_ids); + if (chosen_id == preds_to_ids[pred_with_alternatives]) { + chosen_id = GetFuzzerContext()->RemoveAtRandomIndex(&suitable_ids); + } + + preds_to_ids[pred_with_alternatives] = chosen_id; + } + + // Add the transformation to the list of transformations to apply. + transformations_to_apply.emplace_back(block.id(), preds_to_ids, + GetFuzzerContext()->GetFreshId()); + } + } + + // Apply the transformations. + for (const auto& transformation : transformations_to_apply) { + ApplyTransformation(transformation); + } +} + +std::vector> +FuzzerPassAddOpPhiSynonyms::GetIdEquivalenceClasses() { + std::vector> id_equivalence_classes; + + // Keep track of all the ids that have already be assigned to a class. + std::set already_in_a_class; + + for (const auto& pair : GetIRContext()->get_def_use_mgr()->id_to_defs()) { + // Exclude ids that have already been assigned to a class. + if (already_in_a_class.count(pair.first)) { + continue; + } + + // Exclude irrelevant ids. + if (GetTransformationContext()->GetFactManager()->IdIsIrrelevant( + pair.first)) { + continue; + } + + // Exclude ids having a type that is not allowed by the transformation. + if (!TransformationAddOpPhiSynonym::CheckTypeIsAllowed( + GetIRContext(), pair.second->type_id())) { + continue; + } + + // We need a new equivalence class for this id. + std::set new_equivalence_class; + + // Add this id to the class. + new_equivalence_class.emplace(pair.first); + already_in_a_class.emplace(pair.first); + + // Add all the synonyms with the same type to this class. + for (auto synonym : + GetTransformationContext()->GetFactManager()->GetSynonymsForId( + pair.first)) { + // The synonym must be a plain id - it cannot be an indexed access into a + // composite. + if (synonym->index_size() > 0) { + continue; + } + + // The synonym must not be irrelevant. + if (GetTransformationContext()->GetFactManager()->IdIsIrrelevant( + synonym->object())) { + continue; + } + + auto synonym_def = + GetIRContext()->get_def_use_mgr()->GetDef(synonym->object()); + // The synonym must exist and have the same type as the id we are + // considering. + if (!synonym_def || synonym_def->type_id() != pair.second->type_id()) { + continue; + } + + // We can add this synonym to the new equivalence class. + new_equivalence_class.emplace(synonym->object()); + already_in_a_class.emplace(synonym->object()); + } + + // Add the new equivalence class to the list of equivalence classes. + id_equivalence_classes.emplace_back(std::move(new_equivalence_class)); + } + + return id_equivalence_classes; +} + +bool FuzzerPassAddOpPhiSynonyms::EquivalenceClassIsSuitableForBlock( + const std::set& equivalence_class, uint32_t block_id, + uint32_t distinct_ids_required) { + bool at_least_one_id_for_each_pred = true; + + // Keep a set of the suitable ids found. + std::set suitable_ids_found; + + // Loop through all the predecessors of the block. + for (auto pred_id : GetIRContext()->cfg()->preds(block_id)) { + // Find the last instruction in the predecessor block. + auto last_instruction = + GetIRContext()->get_instr_block(pred_id)->terminator(); + + // Initially assume that there is not a suitable id for this predecessor. + bool at_least_one_suitable_id_found = false; + for (uint32_t id : equivalence_class) { + if (fuzzerutil::IdIsAvailableBeforeInstruction(GetIRContext(), + last_instruction, id)) { + // We have found a suitable id. + at_least_one_suitable_id_found = true; + suitable_ids_found.emplace(id); + + // If we have already found enough distinct suitable ids, we don't need + // to check the remaining ones for this predecessor. + if (suitable_ids_found.size() >= distinct_ids_required) { + break; + } + } + } + // If no suitable id was found for this predecessor, this equivalence class + // is not suitable and we don't need to check the other predecessors. + if (!at_least_one_suitable_id_found) { + at_least_one_id_for_each_pred = false; + break; + } + } + + // The equivalence class is suitable if at least one suitable id was found for + // each predecessor and we have found at least |distinct_ids_required| + // distinct suitable ids in general. + return at_least_one_id_for_each_pred && + suitable_ids_found.size() >= distinct_ids_required; +} + +std::vector FuzzerPassAddOpPhiSynonyms::GetSuitableIds( + const std::set& ids, uint32_t pred_id) { + // Initialise an empty vector of suitable ids. + std::vector suitable_ids; + + // Get the predecessor block. + auto predecessor = fuzzerutil::MaybeFindBlock(GetIRContext(), pred_id); + + // Loop through the ids to find the suitable ones. + for (uint32_t id : ids) { + if (fuzzerutil::IdIsAvailableBeforeInstruction( + GetIRContext(), predecessor->terminator(), id)) { + suitable_ids.push_back(id); + } + } + + return suitable_ids; +} + +std::set* +FuzzerPassAddOpPhiSynonyms::MaybeFindSuitableEquivalenceClassRandomly( + const std::vector*>& candidates, uint32_t block_id, + uint32_t distinct_ids_required) { + auto remaining_candidates = candidates; + while (!remaining_candidates.empty()) { + // Choose one set randomly and return it if it is suitable. + auto chosen = + GetFuzzerContext()->RemoveAtRandomIndex(&remaining_candidates); + if (EquivalenceClassIsSuitableForBlock(*chosen, block_id, + distinct_ids_required)) { + return chosen; + } + } + + // No suitable sets were found. + return nullptr; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_opphi_synonyms.h b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_opphi_synonyms.h new file mode 100644 index 000000000..ed61c020b --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_opphi_synonyms.h @@ -0,0 +1,73 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_FUZZER_PASS_ADD_OPPHI_SYNONYMS_ +#define SOURCE_FUZZ_FUZZER_PASS_ADD_OPPHI_SYNONYMS_ + +#include "source/fuzz/fuzzer_pass.h" + +namespace spvtools { +namespace fuzz { + +// A fuzzer pass to add OpPhi instructions which can take the values of ids that +// have been marked as synonymous. This instruction will itself be marked as +// synonymous with the others. +class FuzzerPassAddOpPhiSynonyms : public FuzzerPass { + public: + FuzzerPassAddOpPhiSynonyms( + opt::IRContext* ir_context, TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations); + + ~FuzzerPassAddOpPhiSynonyms() override; + + void Apply() override; + + // Computes the equivalence classes for the non-pointer and non-irrelevant ids + // in the module, where two ids are considered equivalent iff they have been + // declared synonymous and they have the same type. + std::vector> GetIdEquivalenceClasses(); + + // Returns true iff |equivalence_class| contains at least + // |distinct_ids_required| ids so that all of these ids are available at the + // end of at least one predecessor of the block with label |block_id|. + // Assumes that the block has at least one predecessor. + bool EquivalenceClassIsSuitableForBlock( + const std::set& equivalence_class, uint32_t block_id, + uint32_t distinct_ids_required); + + // Returns a vector with the ids that are available to use at the end of the + // block with id |pred_id|, selected among the given |ids|. Assumes that + // |pred_id| is the label of a block and all ids in |ids| exist in the module. + std::vector GetSuitableIds(const std::set& ids, + uint32_t pred_id); + + private: + // Randomly chooses one of the equivalence classes in |candidates|, so that it + // satisfies all of the following conditions: + // - For each of the predecessors of the |block_id| block, there is at least + // one id in the chosen equivalence class that is available at the end of + // it. + // - There are at least |distinct_ids_required| ids available at the end of + // some predecessor. + // Returns nullptr if no equivalence class in |candidates| satisfies the + // requirements. + std::set* MaybeFindSuitableEquivalenceClassRandomly( + const std::vector*>& candidates, uint32_t block_id, + uint32_t distinct_ids_required); +}; +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_FUZZER_PASS_ADD_OPPHI_SYNONYMS_ diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_inline_functions.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_inline_functions.cpp new file mode 100644 index 000000000..90160d83a --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_inline_functions.cpp @@ -0,0 +1,104 @@ +// Copyright (c) 2020 AndrĂ© Perez Maselco +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/fuzzer_pass_inline_functions.h" + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/instruction_descriptor.h" +#include "source/fuzz/transformation_inline_function.h" +#include "source/fuzz/transformation_split_block.h" + +namespace spvtools { +namespace fuzz { + +FuzzerPassInlineFunctions::FuzzerPassInlineFunctions( + opt::IRContext* ir_context, TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations) + : FuzzerPass(ir_context, transformation_context, fuzzer_context, + transformations) {} + +FuzzerPassInlineFunctions::~FuzzerPassInlineFunctions() = default; + +void FuzzerPassInlineFunctions::Apply() { + // |function_call_instructions| are the instructions that will be inlined. + // First, they will be collected and then do the inlining in another loop. + // This avoids changing the module while it is being inspected. + std::vector function_call_instructions; + + for (auto& function : *GetIRContext()->module()) { + for (auto& block : function) { + for (auto& instruction : block) { + if (!GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext()->GetChanceOfInliningFunction())) { + continue; + } + + // |instruction| must be suitable for inlining. + if (!TransformationInlineFunction::IsSuitableForInlining( + GetIRContext(), &instruction)) { + continue; + } + + function_call_instructions.push_back(&instruction); + } + } + } + + // Once the function calls have been collected, it's time to actually create + // and apply the inlining transformations. + for (auto& function_call_instruction : function_call_instructions) { + // If |function_call_instruction| is not the penultimate instruction in its + // block or its block termination instruction is not OpBranch, then try to + // split |function_call_block| such that the conditions are met. + auto* function_call_block = + GetIRContext()->get_instr_block(function_call_instruction); + if ((function_call_instruction != &*--function_call_block->tail() || + function_call_block->terminator()->opcode() != SpvOpBranch) && + !MaybeApplyTransformation(TransformationSplitBlock( + MakeInstructionDescriptor(GetIRContext(), + function_call_instruction->NextNode()), + GetFuzzerContext()->GetFreshId()))) { + continue; + } + + auto* called_function = fuzzerutil::FindFunction( + GetIRContext(), function_call_instruction->GetSingleWordInOperand(0)); + + // Mapping the called function instructions. + std::map result_id_map; + for (auto& called_function_block : *called_function) { + // The called function entry block label will not be inlined. + if (&called_function_block != &*called_function->entry()) { + result_id_map[called_function_block.GetLabelInst()->result_id()] = + GetFuzzerContext()->GetFreshId(); + } + + for (auto& instruction_to_inline : called_function_block) { + // The instructions are mapped to fresh ids. + if (instruction_to_inline.HasResultId()) { + result_id_map[instruction_to_inline.result_id()] = + GetFuzzerContext()->GetFreshId(); + } + } + } + + // Applies the inline function transformation. + ApplyTransformation(TransformationInlineFunction( + function_call_instruction->result_id(), result_id_map)); + } +} + +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_inline_functions.h b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_inline_functions.h new file mode 100644 index 000000000..37295d1c2 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_inline_functions.h @@ -0,0 +1,41 @@ +// Copyright (c) 2020 AndrĂ© Perez Maselco +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_FUZZER_PASS_INLINE_FUNCTIONS_H_ +#define SOURCE_FUZZ_FUZZER_PASS_INLINE_FUNCTIONS_H_ + +#include "source/fuzz/fuzzer_pass.h" + +namespace spvtools { +namespace fuzz { + +// Looks for OpFunctionCall instructions and randomly decides which ones to +// inline. If the instructions of the called function are going to be inlined, +// then a mapping, between their result ids and suitable ids, is done. +class FuzzerPassInlineFunctions : public FuzzerPass { + public: + FuzzerPassInlineFunctions(opt::IRContext* ir_context, + TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations); + + ~FuzzerPassInlineFunctions() override; + + void Apply() override; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_FUZZER_PASS_INLINE_FUNCTIONS_H_ diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_util.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer_util.cpp index 3e523c3a9..db77f5dd1 100644 --- a/3rdparty/spirv-tools/source/fuzz/fuzzer_util.cpp +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_util.cpp @@ -271,6 +271,11 @@ bool CanMakeSynonymOf(opt::IRContext* ir_context, return false; } auto type_inst = ir_context->get_def_use_mgr()->GetDef(inst->type_id()); + if (type_inst->opcode() == SpvOpTypeVoid) { + // We only make synonyms of instructions that define objects, and an object + // cannot have void type. + return false; + } if (type_inst->opcode() == SpvOpTypePointer) { switch (inst->opcode()) { case SpvOpConstantNull: @@ -970,35 +975,34 @@ uint32_t MaybeGetZeroConstant( opt::IRContext* ir_context, const TransformationContext& transformation_context, uint32_t scalar_or_composite_type_id, bool is_irrelevant) { - const auto* type = - ir_context->get_type_mgr()->GetType(scalar_or_composite_type_id); - assert(type && "|scalar_or_composite_type_id| is invalid"); + const auto* type_inst = + ir_context->get_def_use_mgr()->GetDef(scalar_or_composite_type_id); + assert(type_inst && "|scalar_or_composite_type_id| is invalid"); - switch (type->kind()) { - case opt::analysis::Type::kBool: + switch (type_inst->opcode()) { + case SpvOpTypeBool: return MaybeGetBoolConstant(ir_context, transformation_context, false, is_irrelevant); - case opt::analysis::Type::kFloat: - case opt::analysis::Type::kInteger: { + case SpvOpTypeFloat: + case SpvOpTypeInt: { + const auto width = type_inst->GetSingleWordInOperand(0); std::vector words = {0}; - if ((type->AsInteger() && type->AsInteger()->width() > 32) || - (type->AsFloat() && type->AsFloat()->width() > 32)) { + if (width > 32) { words.push_back(0); } return MaybeGetScalarConstant(ir_context, transformation_context, words, scalar_or_composite_type_id, is_irrelevant); } - case opt::analysis::Type::kStruct: { + case SpvOpTypeStruct: { std::vector component_ids; - for (const auto* component_type : type->AsStruct()->element_types()) { - auto component_type_id = - ir_context->get_type_mgr()->GetId(component_type); - assert(component_type_id && "Component type is invalid"); + for (uint32_t i = 0; i < type_inst->NumInOperands(); ++i) { + const auto component_type_id = type_inst->GetSingleWordInOperand(i); auto component_id = MaybeGetZeroConstant(ir_context, transformation_context, component_type_id, is_irrelevant); + if (component_id == 0 && is_irrelevant) { // Irrelevant constants can use either relevant or irrelevant // constituents. @@ -1017,14 +1021,9 @@ uint32_t MaybeGetZeroConstant( ir_context, transformation_context, component_ids, scalar_or_composite_type_id, is_irrelevant); } - case opt::analysis::Type::kMatrix: - case opt::analysis::Type::kVector: { - const auto* component_type = type->AsVector() - ? type->AsVector()->element_type() - : type->AsMatrix()->element_type(); - auto component_type_id = - ir_context->get_type_mgr()->GetId(component_type); - assert(component_type_id && "Component type is invalid"); + case SpvOpTypeMatrix: + case SpvOpTypeVector: { + const auto component_type_id = type_inst->GetSingleWordInOperand(0); auto component_id = MaybeGetZeroConstant( ir_context, transformation_context, component_type_id, is_irrelevant); @@ -1040,23 +1039,21 @@ uint32_t MaybeGetZeroConstant( return 0; } - auto component_count = type->AsVector() - ? type->AsVector()->element_count() - : type->AsMatrix()->element_count(); + const auto component_count = type_inst->GetSingleWordInOperand(1); return MaybeGetCompositeConstant( ir_context, transformation_context, std::vector(component_count, component_id), scalar_or_composite_type_id, is_irrelevant); } - case opt::analysis::Type::kArray: { - auto component_type_id = - ir_context->get_type_mgr()->GetId(type->AsArray()->element_type()); - assert(component_type_id && "Component type is invalid"); + case SpvOpTypeArray: { + const auto component_type_id = type_inst->GetSingleWordInOperand(0); auto component_id = MaybeGetZeroConstant( ir_context, transformation_context, component_type_id, is_irrelevant); if (component_id == 0 && is_irrelevant) { + // Irrelevant constants can use either relevant or irrelevant + // constituents. component_id = MaybeGetZeroConstant(ir_context, transformation_context, component_type_id, false); } @@ -1065,12 +1062,6 @@ uint32_t MaybeGetZeroConstant( return 0; } - auto type_id = ir_context->get_type_mgr()->GetId(type); - assert(type_id && "|type| is invalid"); - - const auto* type_inst = ir_context->get_def_use_mgr()->GetDef(type_id); - assert(type_inst && "Array's type id is invalid"); - return MaybeGetCompositeConstant( ir_context, transformation_context, std::vector(GetArraySize(*type_inst, ir_context), @@ -1113,10 +1104,7 @@ uint32_t MaybeGetCompositeConstant( bool is_irrelevant) { const auto* type = ir_context->get_type_mgr()->GetType(composite_type_id); (void)type; // Make compilers happy in release mode. - assert(type && - (type->AsArray() || type->AsStruct() || type->AsVector() || - type->AsMatrix()) && - "|composite_type_id| is invalid"); + assert(IsCompositeType(type) && "|composite_type_id| is invalid"); for (const auto& inst : ir_context->types_values()) { if (inst.opcode() == SpvOpConstantComposite && diff --git a/3rdparty/spirv-tools/source/fuzz/overflow_id_source.cpp b/3rdparty/spirv-tools/source/fuzz/overflow_id_source.cpp new file mode 100644 index 000000000..d90045546 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/overflow_id_source.cpp @@ -0,0 +1,23 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/overflow_id_source.h" + +namespace spvtools { +namespace fuzz { + +OverflowIdSource::~OverflowIdSource() = default; + +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/overflow_id_source.h b/3rdparty/spirv-tools/source/fuzz/overflow_id_source.h new file mode 100644 index 000000000..b428fa540 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/overflow_id_source.h @@ -0,0 +1,106 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_OVERFLOW_ID_SOURCE_H_ +#define SOURCE_FUZZ_OVERFLOW_ID_SOURCE_H_ + +#include + +namespace spvtools { +namespace fuzz { + +// An implementation of this interface can be used to provide fresh ids on +// demand when applying a transformation. +// +// During fuzzing this should never be required: a fuzzer pass should determine +// all the fresh ids it requires to apply a transformation. +// +// However, during shrinking we can have the situation where, after removing +// an early transformation, a later transformation needs more ids. +// +// As an example, suppose a SPIR-V function originally has this form: +// +// main() { +// stmt1; +// stmt2; +// stmt3; +// stmt4; +// } +// +// Now suppose two *outlining* transformations are applied. The first +// transformation, T1, outlines "stmt1; stmt2;" into a function foo, giving us: +// +// foo() { +// stmt1; +// stmt2; +// } +// +// main() { +// foo(); +// stmt3; +// stmt4; +// } +// +// The second transformation, T2, outlines "foo(); stmt3;" from main into a +// function bar, giving us: +// +// foo() { +// stmt1; +// stmt2; +// } +// +// bar() { +// foo(); +// stmt3; +// } +// +// main() { +// bar(); +// stmt4; +// } +// +// Suppose that T2 used a set of fresh ids, FRESH, in order to perform its +// outlining. +// +// Now suppose that during shrinking we remove T1, but still want to apply T2. +// The fresh ids used by T2 - FRESH - are sufficient to outline "foo(); stmt3;". +// However, because we did not apply T1, "foo();" does not exist and instead the +// task of T2 is to outline "stmt1; stmt2; stmt3;". The set FRESH contains +// *some* of the fresh ids required to do this (those for "stmt3;"), but not all +// of them (those for "stmt1; stmt2;" are missing). +// +// A source of overflow ids can be used to allow the shrinker to proceed +// nevertheless. +// +// It is desirable to use overflow ids only when needed. In our worked example, +// T2 should still use the ids from FRESH when handling "stmt3;", because later +// transformations might refer to those ids and will become inapplicable if +// overflow ids are used instead. +class OverflowIdSource { + public: + virtual ~OverflowIdSource(); + + // Returns true if and only if this source is capable of providing overflow + // ids. + virtual bool HasOverflowIds() const = 0; + + // Precondition: HasOverflowIds() must hold. Returns the next available + // overflow id. + virtual uint32_t GetNextOverflowId() = 0; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_OVERFLOW_ID_SOURCE_H_ diff --git a/3rdparty/spirv-tools/source/fuzz/protobufs/spvtoolsfuzz.proto b/3rdparty/spirv-tools/source/fuzz/protobufs/spvtoolsfuzz.proto index 225aa4fe7..ed3ce0e3f 100644 --- a/3rdparty/spirv-tools/source/fuzz/protobufs/spvtoolsfuzz.proto +++ b/3rdparty/spirv-tools/source/fuzz/protobufs/spvtoolsfuzz.proto @@ -413,6 +413,8 @@ message Transformation { TransformationReplaceAddSubMulWithCarryingExtended replace_add_sub_mul_with_carrying_extended = 66; TransformationPropagateInstructionUp propagate_instruction_up = 67; TransformationCompositeInsert composite_insert = 68; + TransformationInlineFunction inline_function = 69; + TransformationAddOpPhiSynonym add_opphi_synonym = 70; // Add additional option using the next available number. } } @@ -745,6 +747,24 @@ message TransformationAddNoContractionDecoration { } +message TransformationAddOpPhiSynonym { + + // Adds an OpPhi instruction at the start of a block with n predecessors (pred_1, pred_2, ..., pred_n) + // and n related ids (id_1, id_2, ..., id_n) which are pairwise synonymous. + // The instruction will be of the form: + // %fresh_id = OpPhi %type %id_1 %pred_1 %id_2 %pred_2 ... %id_n %pred_n + // and fresh_id will be recorded as being synonymous with all the other ids. + + // Label id of the block + uint32 block_id = 1; + + // Pairs (pred_i, id_i) + repeated UInt32Pair pred_to_id = 2; + + // Fresh id for the new instruction + uint32 fresh_id = 3; +} + message TransformationAddParameter { // Adds a new parameter into the function. @@ -1103,6 +1123,20 @@ message TransformationFunctionCall { } +message TransformationInlineFunction { + + // This transformation inlines a function by mapping the function instructions to fresh ids. + + // Result id of the function call instruction. + uint32 function_call_id = 1; + + // For each result id defined by the called function, + // this map provides an associated fresh id that can + // be used in the inlined version of the function call. + repeated UInt32Pair result_id_map = 2; + +} + message TransformationInvertComparisonOperator { // For some instruction with result id |operator_id| that diff --git a/3rdparty/spirv-tools/source/fuzz/replayer.cpp b/3rdparty/spirv-tools/source/fuzz/replayer.cpp index bc680d893..d439b9dfe 100644 --- a/3rdparty/spirv-tools/source/fuzz/replayer.cpp +++ b/3rdparty/spirv-tools/source/fuzz/replayer.cpp @@ -14,58 +14,38 @@ #include "source/fuzz/replayer.h" +#include #include -#include "source/fuzz/fact_manager.h" +#include "source/fuzz/counter_overflow_id_source.h" +#include "source/fuzz/fact_manager/fact_manager.h" #include "source/fuzz/protobufs/spirvfuzz_protobufs.h" #include "source/fuzz/transformation.h" -#include "source/fuzz/transformation_add_constant_boolean.h" -#include "source/fuzz/transformation_add_constant_scalar.h" -#include "source/fuzz/transformation_add_dead_break.h" -#include "source/fuzz/transformation_add_type_boolean.h" -#include "source/fuzz/transformation_add_type_float.h" -#include "source/fuzz/transformation_add_type_int.h" -#include "source/fuzz/transformation_add_type_pointer.h" #include "source/fuzz/transformation_context.h" -#include "source/fuzz/transformation_move_block_down.h" -#include "source/fuzz/transformation_replace_boolean_constant_with_constant_binary.h" -#include "source/fuzz/transformation_replace_constant_with_uniform.h" -#include "source/fuzz/transformation_split_block.h" #include "source/opt/build_module.h" #include "source/util/make_unique.h" namespace spvtools { namespace fuzz { -struct Replayer::Impl { - Impl(spv_target_env env, bool validate, spv_validator_options options) - : target_env(env), - validate_during_replay(validate), - validator_options(options) {} - - const spv_target_env target_env; // Target environment. - MessageConsumer consumer; // Message consumer. - const bool validate_during_replay; // Controls whether the validator should - // be run after every replay step. - spv_validator_options validator_options; // Options to control - // validation -}; - -Replayer::Replayer(spv_target_env env, bool validate_during_replay, +Replayer::Replayer(spv_target_env target_env, bool validate_during_replay, spv_validator_options validator_options) - : impl_(MakeUnique(env, validate_during_replay, validator_options)) {} + : target_env_(target_env), + validate_during_replay_(validate_during_replay), + validator_options_(validator_options) {} Replayer::~Replayer() = default; -void Replayer::SetMessageConsumer(MessageConsumer c) { - impl_->consumer = std::move(c); +void Replayer::SetMessageConsumer(MessageConsumer consumer) { + consumer_ = std::move(consumer); } Replayer::ReplayerResultStatus Replayer::Run( const std::vector& binary_in, const protobufs::FactSequence& initial_facts, const protobufs::TransformationSequence& transformation_sequence_in, - uint32_t num_transformations_to_apply, std::vector* binary_out, + uint32_t num_transformations_to_apply, uint32_t first_overflow_id, + std::vector* binary_out, protobufs::TransformationSequence* transformation_sequence_out) const { // Check compatibility between the library version being linked with and the // header files being used. @@ -73,43 +53,51 @@ Replayer::ReplayerResultStatus Replayer::Run( if (num_transformations_to_apply > static_cast(transformation_sequence_in.transformation_size())) { - impl_->consumer(SPV_MSG_ERROR, nullptr, {}, - "The number of transformations to be replayed must not " - "exceed the size of the transformation sequence."); + consumer_(SPV_MSG_ERROR, nullptr, {}, + "The number of transformations to be replayed must not " + "exceed the size of the transformation sequence."); return Replayer::ReplayerResultStatus::kTooManyTransformationsRequested; } - spvtools::SpirvTools tools(impl_->target_env); + spvtools::SpirvTools tools(target_env_); if (!tools.IsValid()) { - impl_->consumer(SPV_MSG_ERROR, nullptr, {}, - "Failed to create SPIRV-Tools interface; stopping."); + consumer_(SPV_MSG_ERROR, nullptr, {}, + "Failed to create SPIRV-Tools interface; stopping."); return Replayer::ReplayerResultStatus::kFailedToCreateSpirvToolsInterface; } // Initial binary should be valid. - if (!tools.Validate(&binary_in[0], binary_in.size(), - impl_->validator_options)) { - impl_->consumer(SPV_MSG_INFO, nullptr, {}, - "Initial binary is invalid; stopping."); + if (!tools.Validate(&binary_in[0], binary_in.size(), validator_options_)) { + consumer_(SPV_MSG_INFO, nullptr, {}, + "Initial binary is invalid; stopping."); return Replayer::ReplayerResultStatus::kInitialBinaryInvalid; } // Build the module from the input binary. - std::unique_ptr ir_context = BuildModule( - impl_->target_env, impl_->consumer, binary_in.data(), binary_in.size()); + std::unique_ptr ir_context = + BuildModule(target_env_, consumer_, binary_in.data(), binary_in.size()); assert(ir_context); // For replay validation, we track the last valid SPIR-V binary that was // observed. Initially this is the input binary. std::vector last_valid_binary; - if (impl_->validate_during_replay) { + if (validate_during_replay_) { last_valid_binary = binary_in; } FactManager fact_manager; - fact_manager.AddFacts(impl_->consumer, initial_facts, ir_context.get()); - TransformationContext transformation_context(&fact_manager, - impl_->validator_options); + fact_manager.AddFacts(consumer_, initial_facts, ir_context.get()); + std::unique_ptr transformation_context = + first_overflow_id == 0 + ? MakeUnique(&fact_manager, validator_options_) + : MakeUnique( + &fact_manager, validator_options_, + MakeUnique(first_overflow_id)); + + // We track the largest id bound observed, to ensure that it only increases + // as transformations are applied. + uint32_t max_observed_id_bound = ir_context->module()->id_bound(); + (void)(max_observed_id_bound); // Keep release-mode compilers happy. // Consider the transformation proto messages in turn. uint32_t counter = 0; @@ -123,22 +111,27 @@ Replayer::ReplayerResultStatus Replayer::Run( // Check whether the transformation can be applied. if (transformation->IsApplicable(ir_context.get(), - transformation_context)) { + *transformation_context)) { // The transformation is applicable, so apply it, and copy it to the // sequence of transformations that were applied. - transformation->Apply(ir_context.get(), &transformation_context); + transformation->Apply(ir_context.get(), transformation_context.get()); *transformation_sequence_out->add_transformation() = message; - if (impl_->validate_during_replay) { + assert(ir_context->module()->id_bound() >= max_observed_id_bound && + "The module's id bound should only increase due to applying " + "transformations."); + max_observed_id_bound = ir_context->module()->id_bound(); + + if (validate_during_replay_) { std::vector binary_to_validate; ir_context->module()->ToBinary(&binary_to_validate, false); // Check whether the latest transformation led to a valid binary. if (!tools.Validate(&binary_to_validate[0], binary_to_validate.size(), - impl_->validator_options)) { - impl_->consumer(SPV_MSG_INFO, nullptr, {}, - "Binary became invalid during replay (set a " - "breakpoint to inspect); stopping."); + validator_options_)) { + consumer_(SPV_MSG_INFO, nullptr, {}, + "Binary became invalid during replay (set a " + "breakpoint to inspect); stopping."); return Replayer::ReplayerResultStatus::kReplayValidationFailure; } diff --git a/3rdparty/spirv-tools/source/fuzz/replayer.h b/3rdparty/spirv-tools/source/fuzz/replayer.h index d6395aaa6..a10e536a7 100644 --- a/3rdparty/spirv-tools/source/fuzz/replayer.h +++ b/3rdparty/spirv-tools/source/fuzz/replayer.h @@ -38,7 +38,7 @@ class Replayer { }; // Constructs a replayer from the given target environment. - Replayer(spv_target_env env, bool validate_during_replay, + Replayer(spv_target_env target_env, bool validate_during_replay, spv_validator_options validator_options); // Disables copy/move constructor/assignment operations. @@ -55,20 +55,38 @@ class Replayer { // Transforms |binary_in| to |binary_out| by attempting to apply the first // |num_transformations_to_apply| transformations from - // |transformation_sequence_in|. Initial facts about the input binary and the - // context in which it will execute are provided via |initial_facts|. The - // transformations that were successfully applied are returned via + // |transformation_sequence_in|. + // + // Initial facts about the input binary and the context in which it will + // execute are provided via |initial_facts|. + // + // |first_overflow_id| should be set to 0 if overflow ids are not available + // during replay. Otherwise |first_overflow_id| must be larger than any id + // referred to in |binary_in| or |transformation_sequence_in|, and overflow + // ids will be available during replay starting from this value. + // + // The transformations that were successfully applied are returned via // |transformation_sequence_out|. ReplayerResultStatus Run( const std::vector& binary_in, const protobufs::FactSequence& initial_facts, const protobufs::TransformationSequence& transformation_sequence_in, - uint32_t num_transformations_to_apply, std::vector* binary_out, + uint32_t num_transformations_to_apply, uint32_t first_overflow_id, + std::vector* binary_out, protobufs::TransformationSequence* transformation_sequence_out) const; private: - struct Impl; // Opaque struct for holding internal data. - std::unique_ptr impl_; // Unique pointer to internal data. + // Target environment. + const spv_target_env target_env_; + + // Message consumer. + MessageConsumer consumer_; + + // Controls whether the validator should be run after every replay step. + const bool validate_during_replay_; + + // Options to control validation + spv_validator_options validator_options_; }; } // namespace fuzz diff --git a/3rdparty/spirv-tools/source/fuzz/shrinker.cpp b/3rdparty/spirv-tools/source/fuzz/shrinker.cpp index 829df63ee..7b88405a3 100644 --- a/3rdparty/spirv-tools/source/fuzz/shrinker.cpp +++ b/3rdparty/spirv-tools/source/fuzz/shrinker.cpp @@ -18,6 +18,8 @@ #include "source/fuzz/pseudo_random_generator.h" #include "source/fuzz/replayer.h" +#include "source/opt/build_module.h" +#include "source/opt/ir_context.h" #include "source/spirv_fuzzer_options.h" #include "source/util/make_unique.h" @@ -59,33 +61,26 @@ protobufs::TransformationSequence RemoveChunk( } // namespace -struct Shrinker::Impl { - Impl(spv_target_env env, uint32_t limit, bool validate, - spv_validator_options options) - : target_env(env), - step_limit(limit), - validate_during_replay(validate), - validator_options(options) {} +uint32_t Shrinker::GetIdBound(const std::vector& binary) const { + // Build the module from the input binary. + std::unique_ptr ir_context = + BuildModule(target_env_, consumer_, binary.data(), binary.size()); + assert(ir_context && "Error building module."); + return ir_context->module()->id_bound(); +} - const spv_target_env target_env; // Target environment. - MessageConsumer consumer; // Message consumer. - const uint32_t step_limit; // Step limit for reductions. - const bool validate_during_replay; // Determines whether to check for - // validity during the replaying of - // transformations. - spv_validator_options validator_options; // Options to control validation. -}; - -Shrinker::Shrinker(spv_target_env env, uint32_t step_limit, +Shrinker::Shrinker(spv_target_env target_env, uint32_t step_limit, bool validate_during_replay, spv_validator_options validator_options) - : impl_(MakeUnique(env, step_limit, validate_during_replay, - validator_options)) {} + : target_env_(target_env), + step_limit_(step_limit), + validate_during_replay_(validate_during_replay), + validator_options_(validator_options) {} Shrinker::~Shrinker() = default; -void Shrinker::SetMessageConsumer(MessageConsumer c) { - impl_->consumer = std::move(c); +void Shrinker::SetMessageConsumer(MessageConsumer consumer) { + consumer_ = std::move(consumer); } Shrinker::ShrinkerResultStatus Shrinker::Run( @@ -99,18 +94,17 @@ Shrinker::ShrinkerResultStatus Shrinker::Run( // header files being used. GOOGLE_PROTOBUF_VERIFY_VERSION; - spvtools::SpirvTools tools(impl_->target_env); + SpirvTools tools(target_env_); if (!tools.IsValid()) { - impl_->consumer(SPV_MSG_ERROR, nullptr, {}, - "Failed to create SPIRV-Tools interface; stopping."); + consumer_(SPV_MSG_ERROR, nullptr, {}, + "Failed to create SPIRV-Tools interface; stopping."); return Shrinker::ShrinkerResultStatus::kFailedToCreateSpirvToolsInterface; } // Initial binary should be valid. - if (!tools.Validate(&binary_in[0], binary_in.size(), - impl_->validator_options)) { - impl_->consumer(SPV_MSG_INFO, nullptr, {}, - "Initial binary is invalid; stopping."); + if (!tools.Validate(&binary_in[0], binary_in.size(), validator_options_)) { + consumer_(SPV_MSG_INFO, nullptr, {}, + "Initial binary is invalid; stopping."); return Shrinker::ShrinkerResultStatus::kInitialBinaryInvalid; } @@ -121,13 +115,13 @@ Shrinker::ShrinkerResultStatus Shrinker::Run( // succeeds, (b) get the binary that results from running these // transformations, and (c) get the subsequence of the initial transformations // that actually apply (in principle this could be a strict subsequence). - Replayer replayer(impl_->target_env, impl_->validate_during_replay, - impl_->validator_options); - replayer.SetMessageConsumer(impl_->consumer); + Replayer replayer(target_env_, validate_during_replay_, validator_options_); + replayer.SetMessageConsumer(consumer_); if (replayer.Run(binary_in, initial_facts, transformation_sequence_in, static_cast( transformation_sequence_in.transformation_size()), - ¤t_best_binary, ¤t_best_transformations) != + /* No overflow ids */ 0, ¤t_best_binary, + ¤t_best_transformations) != Replayer::ReplayerResultStatus::kComplete) { return ShrinkerResultStatus::kReplayFailed; } @@ -135,11 +129,17 @@ Shrinker::ShrinkerResultStatus Shrinker::Run( // Check that the binary produced by applying the initial transformations is // indeed interesting. if (!interestingness_function(current_best_binary, 0)) { - impl_->consumer(SPV_MSG_INFO, nullptr, {}, - "Initial binary is not interesting; stopping."); + consumer_(SPV_MSG_INFO, nullptr, {}, + "Initial binary is not interesting; stopping."); return ShrinkerResultStatus::kInitialBinaryNotInteresting; } + // The largest id used by the module before any shrinking has been applied + // serves as the first id that can be used for overflow purposes. + const uint32_t first_overflow_id = GetIdBound(current_best_binary); + assert(first_overflow_id >= GetIdBound(binary_in) && + "Applying transformations should only increase a module's id bound."); + uint32_t attempt = 0; // Keeps track of the number of shrink attempts that // have been tried, whether successful or not. @@ -153,7 +153,7 @@ Shrinker::ShrinkerResultStatus Shrinker::Run( // - reach the step limit, // - run out of transformations to remove, or // - cannot make the chunk size any smaller. - while (attempt < impl_->step_limit && + while (attempt < step_limit_ && !current_best_transformations.transformation().empty() && chunk_size > 0) { bool progress_this_round = @@ -183,7 +183,7 @@ Shrinker::ShrinkerResultStatus Shrinker::Run( // |chunk_size|, using |chunk_index| to track which chunk to try removing // next. The loop exits early if we reach the shrinking step limit. for (int chunk_index = num_chunks - 1; - attempt < impl_->step_limit && chunk_index >= 0; chunk_index--) { + attempt < step_limit_ && chunk_index >= 0; chunk_index--) { // Remove a chunk of transformations according to the current index and // chunk size. auto transformations_with_chunk_removed = @@ -201,7 +201,7 @@ Shrinker::ShrinkerResultStatus Shrinker::Run( binary_in, initial_facts, transformations_with_chunk_removed, static_cast( transformations_with_chunk_removed.transformation_size()), - &next_binary, &next_transformation_sequence) != + first_overflow_id, &next_binary, &next_transformation_sequence) != Replayer::ReplayerResultStatus::kComplete) { // Replay should not fail; if it does, we need to abort shrinking. return ShrinkerResultStatus::kReplayFailed; @@ -244,12 +244,12 @@ Shrinker::ShrinkerResultStatus Shrinker::Run( // Indicate whether shrinking completed or was truncated due to reaching the // step limit. - assert(attempt <= impl_->step_limit); - if (attempt == impl_->step_limit) { + assert(attempt <= step_limit_); + if (attempt == step_limit_) { std::stringstream strstream; - strstream << "Shrinking did not complete; step limit " << impl_->step_limit + strstream << "Shrinking did not complete; step limit " << step_limit_ << " was reached."; - impl_->consumer(SPV_MSG_WARNING, nullptr, {}, strstream.str().c_str()); + consumer_(SPV_MSG_WARNING, nullptr, {}, strstream.str().c_str()); return Shrinker::ShrinkerResultStatus::kStepLimitReached; } return Shrinker::ShrinkerResultStatus::kComplete; diff --git a/3rdparty/spirv-tools/source/fuzz/shrinker.h b/3rdparty/spirv-tools/source/fuzz/shrinker.h index 17b15bf8c..0fe89290d 100644 --- a/3rdparty/spirv-tools/source/fuzz/shrinker.h +++ b/3rdparty/spirv-tools/source/fuzz/shrinker.h @@ -49,8 +49,8 @@ class Shrinker { using InterestingnessFunction = std::function& binary, uint32_t counter)>; - // Constructs a shrinker from the given target environment. - Shrinker(spv_target_env env, uint32_t step_limit, bool validate_during_replay, + Shrinker(spv_target_env target_env, uint32_t step_limit, + bool validate_during_replay, spv_validator_options validator_options); // Disables copy/move constructor/assignment operations. @@ -82,8 +82,25 @@ class Shrinker { protobufs::TransformationSequence* transformation_sequence_out) const; private: - struct Impl; // Opaque struct for holding internal data. - std::unique_ptr impl_; // Unique pointer to internal data. + // Returns the id bound for the given SPIR-V binary, which is assumed to be + // valid. + uint32_t GetIdBound(const std::vector& binary) const; + + // Target environment. + const spv_target_env target_env_; + + // Message consumer. + MessageConsumer consumer_; + + // Step limit to decide when to terminate shrinking early. + const uint32_t step_limit_; + + // Determines whether to check for validity during the replaying of + // transformations. + const bool validate_during_replay_; + + // Options to control validation. + spv_validator_options validator_options_; }; } // namespace fuzz diff --git a/3rdparty/spirv-tools/source/fuzz/transformation.cpp b/3rdparty/spirv-tools/source/fuzz/transformation.cpp index ec765b3f6..3cadac95b 100644 --- a/3rdparty/spirv-tools/source/fuzz/transformation.cpp +++ b/3rdparty/spirv-tools/source/fuzz/transformation.cpp @@ -33,6 +33,7 @@ #include "source/fuzz/transformation_add_local_variable.h" #include "source/fuzz/transformation_add_loop_preheader.h" #include "source/fuzz/transformation_add_no_contraction_decoration.h" +#include "source/fuzz/transformation_add_opphi_synonym.h" #include "source/fuzz/transformation_add_parameter.h" #include "source/fuzz/transformation_add_relaxed_decoration.h" #include "source/fuzz/transformation_add_spec_constant_op.h" @@ -53,6 +54,7 @@ #include "source/fuzz/transformation_compute_data_synonym_fact_closure.h" #include "source/fuzz/transformation_equation_instruction.h" #include "source/fuzz/transformation_function_call.h" +#include "source/fuzz/transformation_inline_function.h" #include "source/fuzz/transformation_invert_comparison_operator.h" #include "source/fuzz/transformation_load.h" #include "source/fuzz/transformation_make_vector_operation_dynamic.h" @@ -140,6 +142,9 @@ std::unique_ptr Transformation::FromMessage( kAddNoContractionDecoration: return MakeUnique( message.add_no_contraction_decoration()); + case protobufs::Transformation::TransformationCase::kAddOpphiSynonym: + return MakeUnique( + message.add_opphi_synonym()); case protobufs::Transformation::TransformationCase::kAddParameter: return MakeUnique(message.add_parameter()); case protobufs::Transformation::TransformationCase::kAddRelaxedDecoration: @@ -192,6 +197,9 @@ std::unique_ptr Transformation::FromMessage( message.equation_instruction()); case protobufs::Transformation::TransformationCase::kFunctionCall: return MakeUnique(message.function_call()); + case protobufs::Transformation::TransformationCase::kInlineFunction: + return MakeUnique( + message.inline_function()); case protobufs::Transformation::TransformationCase:: kInvertComparisonOperator: return MakeUnique( diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_add_opphi_synonym.cpp b/3rdparty/spirv-tools/source/fuzz/transformation_add_opphi_synonym.cpp new file mode 100644 index 000000000..3436dd7e2 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/transformation_add_opphi_synonym.cpp @@ -0,0 +1,200 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/transformation_add_opphi_synonym.h" + +#include "source/fuzz/fuzzer_util.h" + +namespace spvtools { +namespace fuzz { +TransformationAddOpPhiSynonym::TransformationAddOpPhiSynonym( + const protobufs::TransformationAddOpPhiSynonym& message) + : message_(message) {} + +TransformationAddOpPhiSynonym::TransformationAddOpPhiSynonym( + uint32_t block_id, const std::map& preds_to_ids, + uint32_t fresh_id) { + message_.set_block_id(block_id); + *message_.mutable_pred_to_id() = + fuzzerutil::MapToRepeatedUInt32Pair(preds_to_ids); + message_.set_fresh_id(fresh_id); +} + +bool TransformationAddOpPhiSynonym::IsApplicable( + opt::IRContext* ir_context, + const TransformationContext& transformation_context) const { + // Check that |message_.block_id| is a block label id. + auto block = fuzzerutil::MaybeFindBlock(ir_context, message_.block_id()); + if (!block) { + return false; + } + + // Check that |message_.fresh_id| is actually fresh. + if (!fuzzerutil::IsFreshId(ir_context, message_.fresh_id())) { + return false; + } + + // Check that |message_.pred_to_id| contains a mapping for all of the block's + // predecessors. + std::vector predecessors = ir_context->cfg()->preds(block->id()); + + // There must be at least one predecessor. + if (predecessors.empty()) { + return false; + } + + std::map preds_to_ids = + fuzzerutil::RepeatedUInt32PairToMap(message_.pred_to_id()); + + // There must not be repeated key values in |message_.pred_to_id|. + if (preds_to_ids.size() != static_cast(message_.pred_to_id_size())) { + return false; + } + + // Check that each predecessor has a corresponding mapping and all of the + // corresponding ids exist. + for (uint32_t pred : predecessors) { + if (preds_to_ids.count(pred) == 0) { + return false; + } + + // Check that the id exists in the module. + if (!ir_context->get_def_use_mgr()->GetDef(preds_to_ids[pred])) { + return false; + } + } + + // Get the first id and its type (which should be the same as all the other + // ones) and check that the transformation supports this type. + uint32_t first_id = preds_to_ids[predecessors[0]]; + uint32_t type_id = ir_context->get_def_use_mgr()->GetDef(first_id)->type_id(); + if (!CheckTypeIsAllowed(ir_context, type_id)) { + return false; + } + + // Check that the ids corresponding to predecessors are all synonymous, have + // the same type and are available to use at the end of the predecessor. + for (uint32_t pred : predecessors) { + auto id = preds_to_ids[pred]; + + // Check that the id has the same type as the other ones. + if (ir_context->get_def_use_mgr()->GetDef(id)->type_id() != type_id) { + return false; + } + + // Check that the id is synonymous with the others by checking that it is + // synonymous with the first one (or it is the same id). + if (id != first_id && + !transformation_context.GetFactManager()->IsSynonymous( + MakeDataDescriptor(id, {}), MakeDataDescriptor(first_id, {}))) { + return false; + } + + // Check that the id is available at the end of the corresponding + // predecessor block. + + auto pred_block = ir_context->get_instr_block(pred); + + // We should always be able to find the predecessor block, since it is in + // the predecessors list of |block|. + assert(pred_block && "Could not find one of the predecessor blocks."); + + // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3722): This + // function always returns false if the block is unreachable, so it may + // need to be refactored. + if (!fuzzerutil::IdIsAvailableBeforeInstruction( + ir_context, pred_block->terminator(), id)) { + return false; + } + } + + return true; +} + +void TransformationAddOpPhiSynonym::Apply( + opt::IRContext* ir_context, + TransformationContext* transformation_context) const { + // Get the type id from one of the ids. + uint32_t first_id = message_.pred_to_id(0).second(); + uint32_t type_id = ir_context->get_def_use_mgr()->GetDef(first_id)->type_id(); + + // Define the operand list. + opt::Instruction::OperandList operand_list; + + // For each predecessor, add the corresponding operands. + for (auto& pair : message_.pred_to_id()) { + operand_list.emplace_back( + opt::Operand{SPV_OPERAND_TYPE_ID, {pair.second()}}); + operand_list.emplace_back( + opt::Operand{SPV_OPERAND_TYPE_ID, {pair.first()}}); + } + + // Add a new OpPhi instructions at the beginning of the block. + ir_context->get_instr_block(message_.block_id()) + ->begin() + .InsertBefore(MakeUnique(ir_context, SpvOpPhi, type_id, + message_.fresh_id(), + std::move(operand_list))); + + // Update the module id bound. + fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id()); + + // Invalidate all analyses, since we added an instruction to the module. + ir_context->InvalidateAnalysesExceptFor( + opt::IRContext::Analysis::kAnalysisNone); + + // Record the fact that the new id is synonym with the other ones by declaring + // that it is a synonym of the first one. + transformation_context->GetFactManager()->AddFactDataSynonym( + MakeDataDescriptor(message_.fresh_id(), {}), + MakeDataDescriptor(first_id, {}), ir_context); +} + +protobufs::Transformation TransformationAddOpPhiSynonym::ToMessage() const { + protobufs::Transformation result; + *result.mutable_add_opphi_synonym() = message_; + return result; +} + +bool TransformationAddOpPhiSynonym::CheckTypeIsAllowed( + opt::IRContext* ir_context, uint32_t type_id) { + auto type = ir_context->get_type_mgr()->GetType(type_id); + if (!type) { + return false; + } + + // We allow the following types: Bool, Integer, Float, Vector, Matrix, Array, + // Struct. + if (type->AsBool() || type->AsInteger() || type->AsFloat() || + type->AsVector() || type->AsMatrix() || type->AsArray() || + type->AsStruct()) { + return true; + } + + // We allow pointer types if the VariablePointers capability is enabled and + // the pointer has the correct storage class (Workgroup or StorageBuffer). + if (type->AsPointer()) { + auto storage_class = type->AsPointer()->storage_class(); + return ir_context->get_feature_mgr()->HasCapability( + SpvCapabilityVariablePointers) && + (storage_class == SpvStorageClassWorkgroup || + storage_class == SpvStorageClassStorageBuffer); + } + + // We do not allow other types. + return false; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_add_opphi_synonym.h b/3rdparty/spirv-tools/source/fuzz/transformation_add_opphi_synonym.h new file mode 100644 index 000000000..dc0e6d9b8 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/transformation_add_opphi_synonym.h @@ -0,0 +1,72 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_TRANSFORMATION_ADD_OPPHI_SYNONYM_H_ +#define SOURCE_FUZZ_TRANSFORMATION_ADD_OPPHI_SYNONYM_H_ + +#include "source/fuzz/transformation.h" + +namespace spvtools { +namespace fuzz { +class TransformationAddOpPhiSynonym : public Transformation { + public: + explicit TransformationAddOpPhiSynonym( + const protobufs::TransformationAddOpPhiSynonym& message); + + TransformationAddOpPhiSynonym( + uint32_t block_id, const std::map& preds_to_ids, + uint32_t fresh_id); + + // - |message_.block_id| is the label of a block with at least one + // predecessor. + // - |message_.pred_to_id| contains a mapping from each of the predecessors of + // the block to an id that is available at the end of the predecessor. + // - All the ids corresponding to a predecessor in |message_.pred_to_id|: + // - have been recorded as synonymous and all have the same type. + // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3726): if a + // predecessor is a dead block, any id of the right type could be used, + // even if it is not synonym with the others. + // - have one of the following types: Bool, Integer, Float, Vector, Matrix, + // Array, Struct. Pointer types are also allowed if the VariablePointers + // capability is enabled and the storage class is Workgroup or + // StorageBuffer. + // - |message_.fresh_id| is a fresh id. + bool IsApplicable( + opt::IRContext* ir_context, + const TransformationContext& transformation_context) const override; + + // Given a block with n predecessors, with n >= 1, and n corresponding + // synonymous ids of the same type, each available to use at the end of the + // corresponding predecessor, adds an OpPhi instruction at the beginning of + // the block of the form: + // %fresh_id = OpPhi %type %id_1 %pred_1 %id_2 %pred_2 ... %id_n %pred_n + // This instruction is then marked as synonymous with the ids. + void Apply(opt::IRContext* ir_context, + TransformationContext* transformation_context) const override; + + // Returns true if |type_id| is the id of a type in the module, which is one + // of the following: Bool, Integer, Float, Vector, Matrix, Array, Struct. + // Pointer types are also allowed if the VariablePointers capability is + // enabled and the storage class is Workgroup or StorageBuffer. + static bool CheckTypeIsAllowed(opt::IRContext* ir_context, uint32_t type_id); + + protobufs::Transformation ToMessage() const override; + + private: + protobufs::TransformationAddOpPhiSynonym message_; +}; +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_TRANSFORMATION_ADD_OPPHI_SYNONYM_H_ diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_context.cpp b/3rdparty/spirv-tools/source/fuzz/transformation_context.cpp index 6c2dfdff8..bd0e40641 100644 --- a/3rdparty/spirv-tools/source/fuzz/transformation_context.cpp +++ b/3rdparty/spirv-tools/source/fuzz/transformation_context.cpp @@ -14,12 +14,43 @@ #include "source/fuzz/transformation_context.h" +#include + +#include "source/util/make_unique.h" + namespace spvtools { namespace fuzz { +namespace { + +// An overflow id source that should never be used: its methods assert false. +// This is the right id source for use during fuzzing, when overflow ids should +// never be required. +class NullOverflowIdSource : public OverflowIdSource { + bool HasOverflowIds() const override { + assert(false && "Bad attempt to query whether overflow ids are available."); + return false; + } + + uint32_t GetNextOverflowId() override { + assert(false && "Bad attempt to request an overflow id."); + return 0; + } +}; + +} // namespace TransformationContext::TransformationContext( FactManager* fact_manager, spv_validator_options validator_options) - : fact_manager_(fact_manager), validator_options_(validator_options) {} + : fact_manager_(fact_manager), + validator_options_(validator_options), + overflow_id_source_(MakeUnique()) {} + +TransformationContext::TransformationContext( + FactManager* fact_manager, spv_validator_options validator_options, + std::unique_ptr overflow_id_source) + : fact_manager_(fact_manager), + validator_options_(validator_options), + overflow_id_source_(std::move(overflow_id_source)) {} TransformationContext::~TransformationContext() = default; diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_context.h b/3rdparty/spirv-tools/source/fuzz/transformation_context.h index 37e15a22a..c76a7bef8 100644 --- a/3rdparty/spirv-tools/source/fuzz/transformation_context.h +++ b/3rdparty/spirv-tools/source/fuzz/transformation_context.h @@ -15,7 +15,10 @@ #ifndef SOURCE_FUZZ_TRANSFORMATION_CONTEXT_H_ #define SOURCE_FUZZ_TRANSFORMATION_CONTEXT_H_ -#include "source/fuzz/fact_manager.h" +#include + +#include "source/fuzz/fact_manager/fact_manager.h" +#include "source/fuzz/overflow_id_source.h" #include "spirv-tools/libspirv.hpp" namespace spvtools { @@ -26,16 +29,29 @@ namespace fuzz { class TransformationContext { public: // Constructs a transformation context with a given fact manager and validator - // options. + // options. Overflow ids are not available from a transformation context + // constructed in this way. TransformationContext(FactManager* fact_manager, spv_validator_options validator_options); + // Constructs a transformation context with a given fact manager, validator + // options and overflow id source. + TransformationContext(FactManager* fact_manager, + spv_validator_options validator_options, + std::unique_ptr overflow_id_source); + ~TransformationContext(); FactManager* GetFactManager() { return fact_manager_; } const FactManager* GetFactManager() const { return fact_manager_; } + OverflowIdSource* GetOverflowIdSource() { return overflow_id_source_.get(); } + + const OverflowIdSource* GetOverflowIdSource() const { + return overflow_id_source_.get(); + } + spv_validator_options GetValidatorOptions() const { return validator_options_; } @@ -48,6 +64,8 @@ class TransformationContext { // Options to control validation when deciding whether transformations can be // applied. spv_validator_options validator_options_; + + std::unique_ptr overflow_id_source_; }; } // namespace fuzz diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_inline_function.cpp b/3rdparty/spirv-tools/source/fuzz/transformation_inline_function.cpp new file mode 100644 index 000000000..f244f0d87 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/transformation_inline_function.cpp @@ -0,0 +1,281 @@ +// Copyright (c) 2020 AndrĂ© Perez Maselco +// +// 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_inline_function.h" + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/instruction_descriptor.h" + +namespace spvtools { +namespace fuzz { + +TransformationInlineFunction::TransformationInlineFunction( + const spvtools::fuzz::protobufs::TransformationInlineFunction& message) + : message_(message) {} + +TransformationInlineFunction::TransformationInlineFunction( + uint32_t function_call_id, + const std::map& result_id_map) { + message_.set_function_call_id(function_call_id); + *message_.mutable_result_id_map() = + fuzzerutil::MapToRepeatedUInt32Pair(result_id_map); +} + +bool TransformationInlineFunction::IsApplicable( + opt::IRContext* ir_context, const TransformationContext& /*unused*/) const { + // The values in the |message_.result_id_map| must be all fresh and all + // distinct. + const auto result_id_map = + fuzzerutil::RepeatedUInt32PairToMap(message_.result_id_map()); + std::set ids_used_by_this_transformation; + for (auto& pair : result_id_map) { + if (!CheckIdIsFreshAndNotUsedByThisTransformation( + pair.second, ir_context, &ids_used_by_this_transformation)) { + return false; + } + } + + // |function_call_instruction| must be suitable for inlining. + auto* function_call_instruction = + ir_context->get_def_use_mgr()->GetDef(message_.function_call_id()); + if (!IsSuitableForInlining(ir_context, function_call_instruction)) { + return false; + } + + // |function_call_instruction| must be the penultimate instruction in its + // block and its block termination instruction must be an OpBranch. This + // avoids the case where the penultimate instruction is an OpLoopMerge, which + // would make the back-edge block not branch to the loop header. + auto* function_call_instruction_block = + ir_context->get_instr_block(function_call_instruction); + if (function_call_instruction != + &*--function_call_instruction_block->tail() || + function_call_instruction_block->terminator()->opcode() != SpvOpBranch) { + return false; + } + + auto* called_function = fuzzerutil::FindFunction( + ir_context, function_call_instruction->GetSingleWordInOperand(0)); + for (auto& block : *called_function) { + // Since the entry block label will not be inlined, only the remaining + // labels must have a corresponding value in the map. + if (&block != &*called_function->entry() && + !result_id_map.count(block.GetLabel()->result_id())) { + return false; + } + + // |result_id_map| must have an entry for every result id in the called + // function. + for (auto& instruction : block) { + // If |instruction| has result id, then it must have a mapped id in + // |result_id_map|. + if (instruction.HasResultId() && + !result_id_map.count(instruction.result_id())) { + return false; + } + } + } + + // |result_id_map| must not contain an entry for any parameter of the function + // that is being inlined. + bool found_entry_for_parameter = false; + called_function->ForEachParam( + [&result_id_map, &found_entry_for_parameter](opt::Instruction* param) { + if (result_id_map.count(param->result_id())) { + found_entry_for_parameter = true; + } + }); + return !found_entry_for_parameter; +} + +void TransformationInlineFunction::Apply( + opt::IRContext* ir_context, TransformationContext* /*unused*/) const { + auto* function_call_instruction = + ir_context->get_def_use_mgr()->GetDef(message_.function_call_id()); + auto* caller_function = + ir_context->get_instr_block(function_call_instruction)->GetParent(); + auto* called_function = fuzzerutil::FindFunction( + ir_context, function_call_instruction->GetSingleWordInOperand(0)); + const auto result_id_map = + fuzzerutil::RepeatedUInt32PairToMap(message_.result_id_map()); + auto* successor_block = ir_context->cfg()->block( + ir_context->get_instr_block(function_call_instruction) + ->terminator() + ->GetSingleWordInOperand(0)); + + // Inline the |called_function| entry block. + for (auto& entry_block_instruction : *called_function->entry()) { + opt::Instruction* inlined_instruction = nullptr; + + if (entry_block_instruction.opcode() == SpvOpVariable) { + // All OpVariable instructions in a function must be in the first block + // in the function. + inlined_instruction = caller_function->begin()->begin()->InsertBefore( + MakeUnique(entry_block_instruction)); + } else { + inlined_instruction = function_call_instruction->InsertBefore( + MakeUnique(entry_block_instruction)); + } + + AdaptInlinedInstruction(ir_context, inlined_instruction); + } + + // Inline the |called_function| non-entry blocks. + for (auto& block : *called_function) { + if (&block == &*called_function->entry()) { + continue; + } + + auto* cloned_block = block.Clone(ir_context); + cloned_block = caller_function->InsertBasicBlockBefore( + std::unique_ptr(cloned_block), successor_block); + cloned_block->SetParent(caller_function); + cloned_block->GetLabel()->SetResultId( + result_id_map.at(cloned_block->GetLabel()->result_id())); + fuzzerutil::UpdateModuleIdBound(ir_context, + cloned_block->GetLabel()->result_id()); + + for (auto& inlined_instruction : *cloned_block) { + AdaptInlinedInstruction(ir_context, &inlined_instruction); + } + } + + // Removes the function call instruction and its block termination instruction + // from |caller_function|. + ir_context->KillInst( + ir_context->get_instr_block(function_call_instruction)->terminator()); + ir_context->KillInst(function_call_instruction); + + // Since the SPIR-V module has changed, no analyses must be validated. + ir_context->InvalidateAnalysesExceptFor( + opt::IRContext::Analysis::kAnalysisNone); +} + +protobufs::Transformation TransformationInlineFunction::ToMessage() const { + protobufs::Transformation result; + *result.mutable_inline_function() = message_; + return result; +} + +bool TransformationInlineFunction::IsSuitableForInlining( + opt::IRContext* ir_context, opt::Instruction* function_call_instruction) { + // |function_call_instruction| must be defined and must be an OpFunctionCall + // instruction. + if (!function_call_instruction || + function_call_instruction->opcode() != SpvOpFunctionCall) { + return false; + } + + // If |function_call_instruction| return type is void, then + // |function_call_instruction| must not have uses. + if (ir_context->get_type_mgr() + ->GetType(function_call_instruction->type_id()) + ->AsVoid() && + ir_context->get_def_use_mgr()->NumUses(function_call_instruction) != 0) { + return false; + } + + // |called_function| must not have an early return. + auto called_function = fuzzerutil::FindFunction( + ir_context, function_call_instruction->GetSingleWordInOperand(0)); + if (called_function->HasEarlyReturn()) { + return false; + } + + // |called_function| must not use OpKill or OpUnreachable. + if (fuzzerutil::FunctionContainsOpKillOrUnreachable(*called_function)) { + return false; + } + + return true; +} + +void TransformationInlineFunction::AdaptInlinedInstruction( + opt::IRContext* ir_context, + opt::Instruction* instruction_to_be_inlined) const { + auto* function_call_instruction = + ir_context->get_def_use_mgr()->GetDef(message_.function_call_id()); + auto* called_function = fuzzerutil::FindFunction( + ir_context, function_call_instruction->GetSingleWordInOperand(0)); + const auto result_id_map = + fuzzerutil::RepeatedUInt32PairToMap(message_.result_id_map()); + + // Replaces the operand ids with their mapped result ids. + instruction_to_be_inlined->ForEachInId([called_function, + function_call_instruction, + &result_id_map](uint32_t* id) { + // If |id| is mapped, then set it to its mapped value. + if (result_id_map.count(*id)) { + *id = result_id_map.at(*id); + return; + } + + uint32_t parameter_index = 0; + called_function->ForEachParam( + [id, function_call_instruction, + ¶meter_index](opt::Instruction* parameter_instruction) { + // If the id is a function parameter, then set it to the + // parameter value passed in the function call instruction. + if (*id == parameter_instruction->result_id()) { + // We do + 1 because the first in-operand for OpFunctionCall is + // the function id that is being called. + *id = function_call_instruction->GetSingleWordInOperand( + parameter_index + 1); + } + parameter_index++; + }); + }); + + // If |instruction_to_be_inlined| has result id, then set it to its mapped + // value. + if (instruction_to_be_inlined->HasResultId()) { + assert(result_id_map.count(instruction_to_be_inlined->result_id()) && + "Result id must be mapped to a fresh id."); + instruction_to_be_inlined->SetResultId( + result_id_map.at(instruction_to_be_inlined->result_id())); + fuzzerutil::UpdateModuleIdBound(ir_context, + instruction_to_be_inlined->result_id()); + } + + // The return instruction will be changed into an OpBranch to the basic + // block that follows the block containing the function call. + if (spvOpcodeIsReturn(instruction_to_be_inlined->opcode())) { + uint32_t successor_block_id = + ir_context->get_instr_block(function_call_instruction) + ->terminator() + ->GetSingleWordInOperand(0); + switch (instruction_to_be_inlined->opcode()) { + case SpvOpReturn: + instruction_to_be_inlined->AddOperand( + {SPV_OPERAND_TYPE_ID, {successor_block_id}}); + break; + case SpvOpReturnValue: { + instruction_to_be_inlined->InsertBefore(MakeUnique( + ir_context, SpvOpCopyObject, function_call_instruction->type_id(), + function_call_instruction->result_id(), + opt::Instruction::OperandList( + {{SPV_OPERAND_TYPE_ID, + {instruction_to_be_inlined->GetSingleWordOperand(0)}}}))); + instruction_to_be_inlined->SetInOperand(0, {successor_block_id}); + break; + } + default: + break; + } + instruction_to_be_inlined->SetOpcode(SpvOpBranch); + } +} + +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_inline_function.h b/3rdparty/spirv-tools/source/fuzz/transformation_inline_function.h new file mode 100644 index 000000000..29a9ea6fb --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/transformation_inline_function.h @@ -0,0 +1,75 @@ +// Copyright (c) 2020 AndrĂ© Perez Maselco +// +// 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_INLINE_FUNCTION_H_ +#define SOURCE_FUZZ_TRANSFORMATION_INLINE_FUNCTION_H_ + +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" +#include "source/fuzz/transformation.h" +#include "source/fuzz/transformation_context.h" +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace fuzz { + +class TransformationInlineFunction : public Transformation { + public: + explicit TransformationInlineFunction( + const protobufs::TransformationInlineFunction& message); + + TransformationInlineFunction( + uint32_t function_call_id, + const std::map& result_id_map); + + // - |message_.result_id_map| must map the instructions of the called function + // to fresh ids. + // - |message_.function_call_id| must be an OpFunctionCall instruction. + // It must not have an early return and must not use OpUnreachable or + // OpKill. This is to guard against making the module invalid when the + // caller is inside a continue construct. + // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3735): + // Allow functions that use OpKill or OpUnreachable to be inlined if the + // function call is not part of a continue construct. + bool IsApplicable( + opt::IRContext* ir_context, + const TransformationContext& transformation_context) const override; + + // Replaces the OpFunctionCall instruction, identified by + // |message_.function_call_id|, with a copy of the function's body. + // |message_.result_id_map| is used to provide fresh ids for duplicate + // instructions. + void Apply(opt::IRContext* ir_context, + TransformationContext* transformation_context) const override; + + protobufs::Transformation ToMessage() const override; + + // Returns true if |function_call_instruction| is defined, is an + // OpFunctionCall instruction, has no uses if its return type is void, has no + // early returns and has no uses of OpKill or OpUnreachable. + static bool IsSuitableForInlining( + opt::IRContext* ir_context, opt::Instruction* function_call_instruction); + + private: + protobufs::TransformationInlineFunction message_; + + // Inline |instruction_to_be_inlined| by setting its ids to the corresponding + // ids in |result_id_map|. + void AdaptInlinedInstruction(opt::IRContext* ir_context, + opt::Instruction* instruction) const; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_TRANSFORMATION_INLINE_FUNCTION_H_ diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_outline_function.cpp b/3rdparty/spirv-tools/source/fuzz/transformation_outline_function.cpp index 9257209b5..8534507a0 100644 --- a/3rdparty/spirv-tools/source/fuzz/transformation_outline_function.cpp +++ b/3rdparty/spirv-tools/source/fuzz/transformation_outline_function.cpp @@ -48,7 +48,8 @@ TransformationOutlineFunction::TransformationOutlineFunction( } bool TransformationOutlineFunction::IsApplicable( - opt::IRContext* ir_context, const TransformationContext& /*unused*/) const { + opt::IRContext* ir_context, + const TransformationContext& transformation_context) const { std::set ids_used_by_this_transformation; // The various new ids used by the transformation must be fresh and distinct. @@ -234,8 +235,9 @@ bool TransformationOutlineFunction::IsApplicable( fuzzerutil::RepeatedUInt32PairToMap(message_.input_id_to_fresh_id()); for (auto id : GetRegionInputIds(ir_context, region_set, exit_block)) { // There needs to be a corresponding fresh id to be used as a function - // parameter. - if (input_id_to_fresh_id_map.count(id) == 0) { + // parameter, or overflow ids need to be available. + if (input_id_to_fresh_id_map.count(id) == 0 && + !transformation_context.GetOverflowIdSource()->HasOverflowIds()) { return false; } // Furthermore, if the input id has pointer type it must be an OpVariable @@ -263,8 +265,10 @@ bool TransformationOutlineFunction::IsApplicable( for (auto id : GetRegionOutputIds(ir_context, region_set, exit_block)) { if ( // ... there needs to be a corresponding fresh id that can hold the - // value for this id computed in the outlined function, and ... - output_id_to_fresh_id_map.count(id) == 0 + // value for this id computed in the outlined function (or overflow ids + // must be available), and ... + (output_id_to_fresh_id_map.count(id) == 0 && + !transformation_context.GetOverflowIdSource()->HasOverflowIds()) // ... the output id must not have pointer type (to avoid creating a // struct with pointer members to pass data out of the outlined // function) @@ -306,6 +310,23 @@ void TransformationOutlineFunction::Apply( auto output_id_to_fresh_id_map = fuzzerutil::RepeatedUInt32PairToMap(message_.output_id_to_fresh_id()); + // Use overflow ids to augment these maps at any locations where fresh ids are + // required but not provided. + for (uint32_t id : region_input_ids) { + if (input_id_to_fresh_id_map.count(id) == 0) { + input_id_to_fresh_id_map.insert( + {id, + transformation_context->GetOverflowIdSource()->GetNextOverflowId()}); + } + } + for (uint32_t id : region_output_ids) { + if (output_id_to_fresh_id_map.count(id) == 0) { + output_id_to_fresh_id_map.insert( + {id, + transformation_context->GetOverflowIdSource()->GetNextOverflowId()}); + } + } + UpdateModuleIdBoundForFreshIds(ir_context, input_id_to_fresh_id_map, output_id_to_fresh_id_map); @@ -609,7 +630,7 @@ TransformationOutlineFunction::PrepareFunctionPrototype( {function_type_id}}}))); // Add one parameter to the function for each input id, using the fresh ids - // provided in |input_id_to_fresh_id_map|. + // provided in |input_id_to_fresh_id_map|, or overflow ids if needed. for (auto id : region_input_ids) { outlined_function->AddParameter(MakeUnique( ir_context, SpvOpFunctionParameter, diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_replace_constant_with_uniform.h b/3rdparty/spirv-tools/source/fuzz/transformation_replace_constant_with_uniform.h index c507c322b..b27fb6949 100644 --- a/3rdparty/spirv-tools/source/fuzz/transformation_replace_constant_with_uniform.h +++ b/3rdparty/spirv-tools/source/fuzz/transformation_replace_constant_with_uniform.h @@ -17,7 +17,6 @@ #ifndef SOURCE_FUZZ_TRANSFORMATION_REPLACE_CONSTANT_WITH_UNIFORM_H_ #define SOURCE_FUZZ_TRANSFORMATION_REPLACE_CONSTANT_WITH_UNIFORM_H_ -#include "source/fuzz/fact_manager.h" #include "source/fuzz/id_use_descriptor.h" #include "source/fuzz/protobufs/spirvfuzz_protobufs.h" #include "source/fuzz/transformation.h" diff --git a/3rdparty/spirv-tools/source/opt/compact_ids_pass.cpp b/3rdparty/spirv-tools/source/opt/compact_ids_pass.cpp index 68b940f1d..67091531c 100644 --- a/3rdparty/spirv-tools/source/opt/compact_ids_pass.cpp +++ b/3rdparty/spirv-tools/source/opt/compact_ids_pass.cpp @@ -21,6 +21,24 @@ namespace spvtools { namespace opt { +namespace { + +// Returns the remapped id of |id| from |result_id_mapping|. If the remapped +// id does not exist, adds a new one to |result_id_mapping| and returns it. +uint32_t GetRemappedId( + std::unordered_map* result_id_mapping, uint32_t id) { + auto it = result_id_mapping->find(id); + if (it == result_id_mapping->end()) { + const uint32_t new_id = + static_cast(result_id_mapping->size()) + 1; + const auto insertion_result = result_id_mapping->emplace(id, new_id); + it = insertion_result.first; + assert(insertion_result.second); + } + return it->second; +} + +} // namespace Pass::Status CompactIdsPass::Process() { bool modified = false; @@ -34,18 +52,10 @@ Pass::Status CompactIdsPass::Process() { if (spvIsIdType(type)) { assert(operand->words.size() == 1); uint32_t& id = operand->words[0]; - auto it = result_id_mapping.find(id); - if (it == result_id_mapping.end()) { - const uint32_t new_id = - static_cast(result_id_mapping.size()) + 1; - const auto insertion_result = - result_id_mapping.emplace(id, new_id); - it = insertion_result.first; - assert(insertion_result.second); - } - if (id != it->second) { + uint32_t new_id = GetRemappedId(&result_id_mapping, id); + if (id != new_id) { modified = true; - id = it->second; + id = new_id; // Update data cached in the instruction object. if (type == SPV_OPERAND_TYPE_RESULT_ID) { inst->SetResultId(id); @@ -56,6 +66,23 @@ Pass::Status CompactIdsPass::Process() { } ++operand; } + + uint32_t scope_id = inst->GetDebugScope().GetLexicalScope(); + if (scope_id != kNoDebugScope) { + uint32_t new_id = GetRemappedId(&result_id_mapping, scope_id); + if (scope_id != new_id) { + inst->UpdateLexicalScope(new_id); + modified = true; + } + } + uint32_t inlinedat_id = inst->GetDebugInlinedAt(); + if (inlinedat_id != kNoInlinedAt) { + uint32_t new_id = GetRemappedId(&result_id_mapping, inlinedat_id); + if (inlinedat_id != new_id) { + inst->UpdateDebugInlinedAt(new_id); + modified = true; + } + } }, true); diff --git a/3rdparty/spirv-tools/source/opt/debug_info_manager.cpp b/3rdparty/spirv-tools/source/opt/debug_info_manager.cpp index b91ef43cc..84a0acf46 100644 --- a/3rdparty/spirv-tools/source/opt/debug_info_manager.cpp +++ b/3rdparty/spirv-tools/source/opt/debug_info_manager.cpp @@ -99,6 +99,13 @@ void DebugInfoManager::RegisterDbgFunction(Instruction* inst) { assert(inst->GetOpenCL100DebugOpcode() == OpenCLDebugInfo100DebugFunction && "inst is not a DebugFunction"); auto fn_id = inst->GetSingleWordOperand(kDebugFunctionOperandFunctionIndex); + // Do not register function that has been optimized away + auto fn_inst = GetDbgInst(fn_id); + if (fn_inst != nullptr) { + assert(GetDbgInst(fn_id)->GetOpenCL100DebugOpcode() == + OpenCLDebugInfo100DebugInfoNone); + return; + } assert( fn_id_to_dbg_fn_.find(fn_id) == fn_id_to_dbg_fn_.end() && "Register DebugFunction for a function that already has DebugFunction"); diff --git a/3rdparty/spirv-tools/source/opt/inline_pass.cpp b/3rdparty/spirv-tools/source/opt/inline_pass.cpp index ef94d0d6c..6021a7c53 100644 --- a/3rdparty/spirv-tools/source/opt/inline_pass.cpp +++ b/3rdparty/spirv-tools/source/opt/inline_pass.cpp @@ -619,6 +619,14 @@ bool InlinePass::GenInlineCode( assert(resId != 0); AddLoad(calleeTypeId, resId, returnVarId, &new_blk_ptr, call_inst_itr->dbg_line_inst(), call_inst_itr->GetDebugScope()); + } else { + // Even though it is very unlikely, it is possible that the result id of + // the void-function call is used, so we need to generate an instruction + // with that result id. + std::unique_ptr undef_inst( + new Instruction(context(), SpvOpUndef, call_inst_itr->type_id(), + call_inst_itr->result_id(), {})); + context()->AddGlobalValue(std::move(undef_inst)); } // Move instructions of original caller block after call instruction. diff --git a/3rdparty/spirv-tools/source/opt/instruction.cpp b/3rdparty/spirv-tools/source/opt/instruction.cpp index 8cc3d79a8..f46a4fb8d 100644 --- a/3rdparty/spirv-tools/source/opt/instruction.cpp +++ b/3rdparty/spirv-tools/source/opt/instruction.cpp @@ -921,8 +921,10 @@ void DebugScope::ToBinary(uint32_t type_id, uint32_t result_id, static_cast(dbg_opcode), }; binary->insert(binary->end(), operands.begin(), operands.end()); - if (GetLexicalScope() != kNoDebugScope) binary->push_back(GetLexicalScope()); - if (GetInlinedAt() != kNoInlinedAt) binary->push_back(GetInlinedAt()); + if (GetLexicalScope() != kNoDebugScope) { + binary->push_back(GetLexicalScope()); + if (GetInlinedAt() != kNoInlinedAt) binary->push_back(GetInlinedAt()); + } } } // namespace opt diff --git a/3rdparty/spirv-tools/source/opt/instruction.h b/3rdparty/spirv-tools/source/opt/instruction.h index 067f69cfc..3b81e3aac 100644 --- a/3rdparty/spirv-tools/source/opt/instruction.h +++ b/3rdparty/spirv-tools/source/opt/instruction.h @@ -305,6 +305,8 @@ class Instruction : public utils::IntrusiveNodeBase { inline uint32_t GetDebugInlinedAt() const { return dbg_scope_.GetInlinedAt(); } + // Updates lexical scope of DebugScope and OpLine. + inline void UpdateLexicalScope(uint32_t scope); // Updates OpLine and DebugScope based on the information of |from|. inline void UpdateDebugInfoFrom(const Instruction* from); // Remove the |index|-th operand @@ -668,6 +670,13 @@ inline void Instruction::SetDebugScope(const DebugScope& scope) { } } +inline void Instruction::UpdateLexicalScope(uint32_t scope) { + dbg_scope_.SetLexicalScope(scope); + for (auto& i : dbg_line_insts_) { + i.dbg_scope_.SetLexicalScope(scope); + } +} + inline void Instruction::UpdateDebugInlinedAt(uint32_t new_inlined_at) { dbg_scope_.SetInlinedAt(new_inlined_at); for (auto& i : dbg_line_insts_) { diff --git a/3rdparty/spirv-tools/source/opt/ssa_rewrite_pass.cpp b/3rdparty/spirv-tools/source/opt/ssa_rewrite_pass.cpp index d5bba5e21..76f1781aa 100644 --- a/3rdparty/spirv-tools/source/opt/ssa_rewrite_pass.cpp +++ b/3rdparty/spirv-tools/source/opt/ssa_rewrite_pass.cpp @@ -624,11 +624,9 @@ Pass::Status SSARewritePass::Process() { for (auto& fn : *get_module()) { status = CombineStatus(status, SSARewriter(this).RewriteFunctionIntoSSA(&fn)); - if (status == Status::SuccessWithChange) { - // Kill DebugDeclares for target variables. - for (auto var_id : seen_target_vars_) { - context()->get_debug_info_mgr()->KillDebugDeclares(var_id); - } + // Kill DebugDeclares for target variables. + for (auto var_id : seen_target_vars_) { + context()->get_debug_info_mgr()->KillDebugDeclares(var_id); } if (status == Status::Failure) { break; diff --git a/3rdparty/spirv-tools/source/opt/struct_cfg_analysis.cpp b/3rdparty/spirv-tools/source/opt/struct_cfg_analysis.cpp index 57fc49c1c..0da1d6012 100644 --- a/3rdparty/spirv-tools/source/opt/struct_cfg_analysis.cpp +++ b/3rdparty/spirv-tools/source/opt/struct_cfg_analysis.cpp @@ -150,6 +150,19 @@ uint32_t StructuredCFGAnalysis::LoopContinueBlock(uint32_t bb_id) { return merge_inst->GetSingleWordInOperand(kContinueNodeIndex); } +uint32_t StructuredCFGAnalysis::LoopNestingDepth(uint32_t bb_id) { + uint32_t result = 0; + + // Find the merge block of the current loop as long as the block is inside a + // loop, exiting a loop for each iteration. + for (uint32_t merge_block_id = LoopMergeBlock(bb_id); merge_block_id != 0; + merge_block_id = LoopMergeBlock(merge_block_id)) { + result++; + } + + return result; +} + uint32_t StructuredCFGAnalysis::SwitchMergeBlock(uint32_t bb_id) { uint32_t header_id = ContainingSwitch(bb_id); if (header_id == 0) { diff --git a/3rdparty/spirv-tools/source/opt/struct_cfg_analysis.h b/3rdparty/spirv-tools/source/opt/struct_cfg_analysis.h index dfae6d4f2..20c202f06 100644 --- a/3rdparty/spirv-tools/source/opt/struct_cfg_analysis.h +++ b/3rdparty/spirv-tools/source/opt/struct_cfg_analysis.h @@ -74,6 +74,13 @@ class StructuredCFGAnalysis { // construct. uint32_t LoopContinueBlock(uint32_t bb_id); + // Returns the loop nesting depth of |bb_id| within its function, i.e. the + // number of loop constructs in which |bb_id| is contained. As per other + // functions in StructuredCFGAnalysis, a loop header is not regarded as being + // part of the loop that it heads, so that e.g. the nesting depth of an + // outer-most loop header is 0. + uint32_t LoopNestingDepth(uint32_t bb_id); + // Returns the id of the header of the innermost switch construct // that contains |bb_id| as long as there is no intervening loop. Returns |0| // if no such construct exists.