| // 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.h" |
| |
| #include <sstream> |
| #include <unordered_map> |
| #include <unordered_set> |
| |
| #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 { |
| |
| 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<SpvOp>(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<uint32_t> GetConstantsAvailableFromUniformsForType( |
| opt::IRContext* ir_context, uint32_t type_id) const; |
| |
| // See method in FactManager which delegates to this method. |
| const std::vector<protobufs::UniformBufferElementDescriptor> |
| 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<uint32_t> GetTypesForWhichUniformValuesAreKnown() const; |
| |
| // See method in FactManager which delegates to this method. |
| const std::vector<std::pair<protobufs::FactConstantUniform, uint32_t>>& |
| 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<uint32_t> 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<std::pair<protobufs::FactConstantUniform, uint32_t>> |
| 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<uint32_t> FactManager::ConstantUniformFacts::GetConstantWords( |
| const protobufs::FactConstantUniform& constant_uniform_fact) const { |
| std::vector<uint32_t> 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<uint32_t> 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<uint32_t> |
| FactManager::ConstantUniformFacts::GetConstantsAvailableFromUniformsForType( |
| opt::IRContext* ir_context, uint32_t type_id) const { |
| std::vector<uint32_t> result; |
| std::set<uint32_t> 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<protobufs::UniformBufferElementDescriptor> |
| FactManager::ConstantUniformFacts::GetUniformDescriptorsForConstant( |
| opt::IRContext* ir_context, uint32_t constant_id) const { |
| std::vector<protobufs::UniformBufferElementDescriptor> 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<uint32_t> |
| FactManager::ConstantUniformFacts::GetTypesForWhichUniformValuesAreKnown() |
| const { |
| std::vector<uint32_t> 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<uint32_t> 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<uint32_t>(fact.constant_word().size()) != required_words) { |
| return false; |
| } |
| facts_and_type_ids_.emplace_back( |
| std::pair<protobufs::FactConstantUniform, uint32_t>( |
| fact, final_element_type_id)); |
| return true; |
| } |
| |
| const std::vector<std::pair<protobufs::FactConstantUniform, uint32_t>>& |
| 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<const protobufs::DataDescriptor*> 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<uint32_t>(DataDescriptorHash()(operand))); |
| } |
| return std::hash<std::u32string>()(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; |
| for (auto operand : operation.operands) { |
| stream << " " << *operand; |
| } |
| return stream.str(); |
| } |
| |
| // 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<const protobufs::DataDescriptor*> GetSynonymsForDataDescriptor( |
| const protobufs::DataDescriptor& data_descriptor) const; |
| |
| // See method in FactManager which delegates to this method. |
| std::vector<uint32_t> 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<Operation, OperationHash, OperationEquals>; |
| |
| // 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); |
| |
| // 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); |
| |
| // Returns true if and only if |dd1| and |dd2| are valid data descriptors |
| // whose associated data have the same type (modulo integer signedness). |
| bool DataDescriptorsAreWellFormedAndComparable( |
| opt::IRContext* context, const protobufs::DataDescriptor& dd1, |
| const protobufs::DataDescriptor& dd2) const; |
| |
| 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<const protobufs::DataDescriptor*>& rhs_dds, |
| opt::IRContext* context); |
| |
| // The data descriptors that are known to be synonymous with one another are |
| // captured by this equivalence relation. |
| EquivalenceRelation<protobufs::DataDescriptor, DataDescriptorHash, |
| DataDescriptorEquals> |
| 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<const protobufs::DataDescriptor*, OperationSet> |
| id_equations_; |
| }; |
| |
| void FactManager::DataSynonymAndIdEquationFacts::AddFact( |
| const protobufs::FactDataSynonym& fact, opt::IRContext* context) { |
| // 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( |
| const protobufs::FactIdEquation& fact, opt::IRContext* context) { |
| protobufs::DataDescriptor lhs_dd = MakeDataDescriptor(fact.lhs_id(), {}); |
| |
| // Register the LHS in the equivalence relation if needed. |
| if (!synonymous_.Exists(lhs_dd)) { |
| synonymous_.Register(lhs_dd); |
| } |
| |
| // Get equivalence class representatives for all ids used on the RHS of the |
| // equation. |
| std::vector<const protobufs::DataDescriptor*> rhs_dd_ptrs; |
| for (auto rhs_id : fact.rhs_id()) { |
| // Register a data descriptor based on this id in the equivalence relation |
| // if needed, and then record the equivalence class representative. |
| protobufs::DataDescriptor rhs_dd = MakeDataDescriptor(rhs_id, {}); |
| if (!synonymous_.Exists(rhs_dd)) { |
| synonymous_.Register(rhs_dd); |
| } |
| rhs_dd_ptrs.push_back(synonymous_.Find(&rhs_dd)); |
| } |
| |
| // Now add the fact. |
| AddEquationFactRecursive(lhs_dd, static_cast<SpvOp>(fact.opcode()), |
| rhs_dd_ptrs, context); |
| } |
| |
| FactManager::DataSynonymAndIdEquationFacts::OperationSet |
| FactManager::DataSynonymAndIdEquationFacts::GetEquations( |
| const protobufs::DataDescriptor* lhs) const { |
| auto existing = id_equations_.find(lhs); |
| if (existing == id_equations_.end()) { |
| return OperationSet(); |
| } |
| return existing->second; |
| } |
| |
| void FactManager::DataSynonymAndIdEquationFacts::AddEquationFactRecursive( |
| const protobufs::DataDescriptor& lhs_dd, SpvOp opcode, |
| const std::vector<const protobufs::DataDescriptor*>& rhs_dds, |
| opt::IRContext* context) { |
| assert(synonymous_.Exists(lhs_dd) && |
| "The LHS must be known to the equivalence relation."); |
| for (auto rhs_dd : rhs_dds) { |
| // Keep release compilers happy. |
| (void)(rhs_dd); |
| assert(synonymous_.Exists(*rhs_dd) && |
| "The RHS operands must be known to the equivalence relation."); |
| } |
| |
| auto lhs_dd_representative = synonymous_.Find(&lhs_dd); |
| |
| if (id_equations_.count(lhs_dd_representative) == 0) { |
| // We have not seen an equation with this LHS before, so associate the LHS |
| // with an initially empty set. |
| id_equations_.insert({lhs_dd_representative, OperationSet()}); |
| } |
| |
| { |
| auto existing_equations = id_equations_.find(lhs_dd_representative); |
| assert(existing_equations != id_equations_.end() && |
| "A set of operations should be present, even if empty."); |
| |
| Operation new_operation = {opcode, rhs_dds}; |
| if (existing_equations->second.count(new_operation)) { |
| // This equation is known, so there is nothing further to be done. |
| return; |
| } |
| // Add the equation to the set of known equations. |
| existing_equations->second.insert(new_operation); |
| } |
| |
| // Now try to work out corollaries implied by the new equation and existing |
| // facts. |
| switch (opcode) { |
| case SpvOpIAdd: { |
| // Equation form: "a = b + c" |
| for (auto equation : GetEquations(rhs_dds[0])) { |
| if (equation.opcode == SpvOpISub) { |
| // Equation form: "a = (d - e) + c" |
| if (synonymous_.IsEquivalent(*equation.operands[1], *rhs_dds[1])) { |
| // Equation form: "a = (d - c) + c" |
| // We can thus infer "a = d" |
| AddDataSynonymFactRecursive(lhs_dd, *equation.operands[0], context); |
| } |
| if (synonymous_.IsEquivalent(*equation.operands[0], *rhs_dds[1])) { |
| // Equation form: "a = (c - e) + c" |
| // We can thus infer "a = -e" |
| AddEquationFactRecursive(lhs_dd, SpvOpSNegate, |
| {equation.operands[1]}, context); |
| } |
| } |
| } |
| for (auto equation : GetEquations(rhs_dds[1])) { |
| if (equation.opcode == SpvOpISub) { |
| // Equation form: "a = b + (d - e)" |
| if (synonymous_.IsEquivalent(*equation.operands[1], *rhs_dds[0])) { |
| // Equation form: "a = b + (d - b)" |
| // We can thus infer "a = d" |
| AddDataSynonymFactRecursive(lhs_dd, *equation.operands[0], context); |
| } |
| } |
| } |
| break; |
| } |
| case SpvOpISub: { |
| // Equation form: "a = b - c" |
| for (auto equation : GetEquations(rhs_dds[0])) { |
| if (equation.opcode == SpvOpIAdd) { |
| // Equation form: "a = (d + e) - c" |
| if (synonymous_.IsEquivalent(*equation.operands[0], *rhs_dds[1])) { |
| // Equation form: "a = (c + e) - c" |
| // We can thus infer "a = e" |
| AddDataSynonymFactRecursive(lhs_dd, *equation.operands[1], context); |
| } |
| if (synonymous_.IsEquivalent(*equation.operands[1], *rhs_dds[1])) { |
| // Equation form: "a = (d + c) - c" |
| // We can thus infer "a = d" |
| AddDataSynonymFactRecursive(lhs_dd, *equation.operands[0], context); |
| } |
| } |
| |
| if (equation.opcode == SpvOpISub) { |
| // Equation form: "a = (d - e) - c" |
| if (synonymous_.IsEquivalent(*equation.operands[0], *rhs_dds[1])) { |
| // Equation form: "a = (c - e) - c" |
| // We can thus infer "a = -e" |
| AddEquationFactRecursive(lhs_dd, SpvOpSNegate, |
| {equation.operands[1]}, context); |
| } |
| } |
| } |
| |
| for (auto equation : GetEquations(rhs_dds[1])) { |
| if (equation.opcode == SpvOpIAdd) { |
| // Equation form: "a = b - (d + e)" |
| if (synonymous_.IsEquivalent(*equation.operands[0], *rhs_dds[0])) { |
| // Equation form: "a = b - (b + e)" |
| // We can thus infer "a = -e" |
| AddEquationFactRecursive(lhs_dd, SpvOpSNegate, |
| {equation.operands[1]}, context); |
| } |
| if (synonymous_.IsEquivalent(*equation.operands[1], *rhs_dds[0])) { |
| // Equation form: "a = b - (d + b)" |
| // We can thus infer "a = -d" |
| AddEquationFactRecursive(lhs_dd, SpvOpSNegate, |
| {equation.operands[0]}, context); |
| } |
| } |
| if (equation.opcode == SpvOpISub) { |
| // Equation form: "a = b - (d - e)" |
| if (synonymous_.IsEquivalent(*equation.operands[0], *rhs_dds[0])) { |
| // Equation form: "a = b - (b - e)" |
| // We can thus infer "a = e" |
| AddDataSynonymFactRecursive(lhs_dd, *equation.operands[1], context); |
| } |
| } |
| } |
| break; |
| } |
| case SpvOpLogicalNot: |
| case SpvOpSNegate: { |
| // Equation form: "a = !b" or "a = -b" |
| for (auto equation : GetEquations(rhs_dds[0])) { |
| if (equation.opcode == opcode) { |
| // Equation form: "a = !!b" or "a = -(-b)" |
| // We can thus infer "a = b" |
| AddDataSynonymFactRecursive(lhs_dd, *equation.operands[0], context); |
| } |
| } |
| break; |
| } |
| default: |
| break; |
| } |
| } |
| |
| void FactManager::DataSynonymAndIdEquationFacts::AddDataSynonymFactRecursive( |
| const protobufs::DataDescriptor& dd1, const protobufs::DataDescriptor& dd2, |
| opt::IRContext* context) { |
| assert(DataDescriptorsAreWellFormedAndComparable(context, dd1, dd2)); |
| |
| // Record that the data descriptors provided in the fact are equivalent. |
| MakeEquivalent(dd1, dd2); |
| |
| // We now check whether this is a synonym about composite objects. If it is, |
| // we can recursively add synonym facts about their associated sub-components. |
| |
| // Get the type of the object referred to by the first data descriptor in the |
| // synonym fact. |
| uint32_t type_id = fuzzerutil::WalkCompositeTypeIndices( |
| context, context->get_def_use_mgr()->GetDef(dd1.object())->type_id(), |
| dd1.index()); |
| auto type = context->get_type_mgr()->GetType(type_id); |
| auto type_instruction = context->get_def_use_mgr()->GetDef(type_id); |
| assert(type != nullptr && |
| "Invalid data synonym fact: one side has an unknown type."); |
| |
| // Check whether the type is composite, recording the number of elements |
| // associated with the composite if so. |
| uint32_t num_composite_elements; |
| if (type->AsArray()) { |
| num_composite_elements = |
| fuzzerutil::GetArraySize(*type_instruction, context); |
| } else if (type->AsMatrix()) { |
| num_composite_elements = type->AsMatrix()->element_count(); |
| } else if (type->AsStruct()) { |
| num_composite_elements = |
| fuzzerutil::GetNumberOfStructMembers(*type_instruction); |
| } else if (type->AsVector()) { |
| num_composite_elements = type->AsVector()->element_count(); |
| } else { |
| // The type is not a composite, so return. |
| return; |
| } |
| |
| // If the fact has the form: |
| // obj_1[a_1, ..., a_m] == obj_2[b_1, ..., b_n] |
| // then for each composite index i, we add a fact of the form: |
| // obj_1[a_1, ..., a_m, i] == obj_2[b_1, ..., b_n, i] |
| // |
| // However, to avoid adding a large number of synonym facts e.g. in the case |
| // of arrays, we bound the number of composite elements to which this is |
| // applied. Nevertheless, we always add a synonym fact for the final |
| // components, as this may be an interesting edge case. |
| |
| // The bound on the number of indices of the composite pair to note as being |
| // synonymous. |
| const uint32_t kCompositeElementBound = 10; |
| |
| for (uint32_t i = 0; i < num_composite_elements;) { |
| std::vector<uint32_t> extended_indices1 = |
| fuzzerutil::RepeatedFieldToVector(dd1.index()); |
| extended_indices1.push_back(i); |
| std::vector<uint32_t> extended_indices2 = |
| fuzzerutil::RepeatedFieldToVector(dd2.index()); |
| extended_indices2.push_back(i); |
| AddDataSynonymFactRecursive( |
| MakeDataDescriptor(dd1.object(), std::move(extended_indices1)), |
| MakeDataDescriptor(dd2.object(), std::move(extended_indices2)), |
| context); |
| |
| if (i < kCompositeElementBound - 1 || i == num_composite_elements - 1) { |
| // We have not reached the bound yet, or have already skipped ahead to the |
| // last element, so increment the loop counter as standard. |
| i++; |
| } else { |
| // We have reached the bound, so skip ahead to the last element. |
| assert(i == kCompositeElementBound - 1); |
| i = num_composite_elements - 1; |
| } |
| } |
| } |
| |
| void FactManager::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 |
| // the composite type is comprised of k components. |
| // |
| // For example, if m is a mat4x4 and v a vec4, we might consider: |
| // m[2]: describes the 2nd column of m, a vec4 |
| // v[]: describes all of v, a vec4 |
| // |
| // Suppose that we know, for every 0 <= i < k, that the fact: |
| // obj_1[a_1, ..., a_m, i] == obj_2[b_1, ..., b_n, i] |
| // holds - i.e. that the children of the two data descriptors are synonymous. |
| // |
| // Then we can conclude that: |
| // obj_1[a_1, ..., a_m] == obj_2[b_1, ..., b_n] |
| // holds. |
| // |
| // For instance, if we have the facts: |
| // m[2, 0] == v[0] |
| // m[2, 1] == v[1] |
| // m[2, 2] == v[2] |
| // m[2, 3] == v[3] |
| // then we can conclude that: |
| // m[2] == v. |
| // |
| // This method repeatedly searches the equivalence relation of data |
| // descriptors, deducing and adding such facts, until a pass over the |
| // relation leads to no further facts being deduced. |
| |
| // The method relies on working with pairs of data descriptors, and in |
| // particular being able to hash and compare such pairs. |
| |
| using DataDescriptorPair = |
| std::pair<protobufs::DataDescriptor, protobufs::DataDescriptor>; |
| |
| struct DataDescriptorPairHash { |
| std::size_t operator()(const DataDescriptorPair& pair) const { |
| return DataDescriptorHash()(&pair.first) ^ |
| DataDescriptorHash()(&pair.second); |
| } |
| }; |
| |
| struct DataDescriptorPairEquals { |
| bool operator()(const DataDescriptorPair& first, |
| const DataDescriptorPair& second) const { |
| return DataDescriptorEquals()(&first.first, &second.first) && |
| DataDescriptorEquals()(&first.second, &second.second); |
| } |
| }; |
| |
| // This map records, for a given pair of composite data descriptors of the |
| // same type, all the indices at which the data descriptors are known to be |
| // synonymous. A pair is a key to this map only if we have observed that |
| // the pair are synonymous at *some* index, but not at *all* indices. |
| // Once we find that a pair of data descriptors are equivalent at all indices |
| // we record the fact that they are synonymous and remove them from the map. |
| // |
| // Using the m and v example from above, initially the pair (m[2], v) would |
| // not be a key to the map. If we find that m[2, 2] == v[2] holds, we would |
| // add an entry: |
| // (m[2], v) -> [false, false, true, false] |
| // to record that they are synonymous at index 2. If we then find that |
| // m[2, 0] == v[0] holds, we would update this entry to: |
| // (m[2], v) -> [true, false, true, false] |
| // If we then find that m[2, 3] == v[3] holds, we would update this entry to: |
| // (m[2], v) -> [true, false, true, true] |
| // Finally, if we then find that m[2, 1] == v[1] holds, which would make the |
| // boolean vector true at every index, we would add the fact: |
| // m[2] == v |
| // to the equivalence relation and remove (m[2], v) from the map. |
| std::unordered_map<DataDescriptorPair, std::vector<bool>, |
| DataDescriptorPairHash, DataDescriptorPairEquals> |
| candidate_composite_synonyms; |
| |
| // We keep looking for new facts until we perform a complete pass over the |
| // equivalence relation without finding any new facts. |
| while (closure_computation_required_) { |
| // We have not found any new facts yet during this pass; we set this to |
| // 'true' if we do find a new fact. |
| closure_computation_required_ = false; |
| |
| // Consider each class in the equivalence relation. |
| for (auto representative : |
| synonymous_.GetEquivalenceClassRepresentatives()) { |
| auto equivalence_class = synonymous_.GetEquivalenceClass(*representative); |
| |
| if (equivalence_class.size() > maximum_equivalence_class_size) { |
| // This equivalence class is larger than the maximum size we are willing |
| // to consider, so we skip it. This potentially leads to missed fact |
| // deductions, but avoids excessive runtime for closure computation. |
| continue; |
| } |
| |
| // Consider every data descriptor in the equivalence class. |
| for (auto dd1_it = equivalence_class.begin(); |
| dd1_it != equivalence_class.end(); ++dd1_it) { |
| // If this data descriptor has no indices then it does not have the form |
| // obj_1[a_1, ..., a_m, i], so move on. |
| auto dd1 = *dd1_it; |
| if (dd1->index_size() == 0) { |
| continue; |
| } |
| |
| // Consider every other data descriptor later in the equivalence class |
| // (due to symmetry, there is no need to compare with previous data |
| // descriptors). |
| auto dd2_it = dd1_it; |
| for (++dd2_it; dd2_it != equivalence_class.end(); ++dd2_it) { |
| auto dd2 = *dd2_it; |
| // If this data descriptor has no indices then it does not have the |
| // form obj_2[b_1, ..., b_n, i], so move on. |
| if (dd2->index_size() == 0) { |
| continue; |
| } |
| |
| // At this point we know that: |
| // - |dd1| has the form obj_1[a_1, ..., a_m, i] |
| // - |dd2| has the form obj_2[b_1, ..., b_n, j] |
| assert(dd1->index_size() > 0 && dd2->index_size() > 0 && |
| "Control should not reach here if either data descriptor has " |
| "no indices."); |
| |
| // We are only interested if i == j. |
| if (dd1->index(dd1->index_size() - 1) != |
| dd2->index(dd2->index_size() - 1)) { |
| continue; |
| } |
| |
| const uint32_t common_final_index = dd1->index(dd1->index_size() - 1); |
| |
| // Make data descriptors |dd1_prefix| and |dd2_prefix| for |
| // obj_1[a_1, ..., a_m] |
| // and |
| // obj_2[b_1, ..., b_n] |
| // These are the two data descriptors we might be getting closer to |
| // deducing as being synonymous, due to knowing that they are |
| // synonymous when extended by a particular index. |
| protobufs::DataDescriptor dd1_prefix; |
| dd1_prefix.set_object(dd1->object()); |
| for (uint32_t i = 0; i < static_cast<uint32_t>(dd1->index_size() - 1); |
| i++) { |
| dd1_prefix.add_index(dd1->index(i)); |
| } |
| protobufs::DataDescriptor dd2_prefix; |
| dd2_prefix.set_object(dd2->object()); |
| for (uint32_t i = 0; i < static_cast<uint32_t>(dd2->index_size() - 1); |
| i++) { |
| dd2_prefix.add_index(dd2->index(i)); |
| } |
| assert(!DataDescriptorEquals()(&dd1_prefix, &dd2_prefix) && |
| "By construction these prefixes should be different."); |
| |
| // If we already know that these prefixes are synonymous, move on. |
| if (synonymous_.Exists(dd1_prefix) && |
| synonymous_.Exists(dd2_prefix) && |
| synonymous_.IsEquivalent(dd1_prefix, dd2_prefix)) { |
| continue; |
| } |
| |
| // Get the type of obj_1 |
| auto dd1_root_type_id = |
| context->get_def_use_mgr()->GetDef(dd1->object())->type_id(); |
| // Use this type, together with a_1, ..., a_m, to get the type of |
| // obj_1[a_1, ..., a_m]. |
| auto dd1_prefix_type = fuzzerutil::WalkCompositeTypeIndices( |
| context, dd1_root_type_id, dd1_prefix.index()); |
| |
| // Similarly, get the type of obj_2 and use it to get the type of |
| // obj_2[b_1, ..., b_n]. |
| auto dd2_root_type_id = |
| context->get_def_use_mgr()->GetDef(dd2->object())->type_id(); |
| auto dd2_prefix_type = fuzzerutil::WalkCompositeTypeIndices( |
| context, dd2_root_type_id, dd2_prefix.index()); |
| |
| // If the types of dd1_prefix and dd2_prefix are not the same, they |
| // cannot be synonymous. |
| if (dd1_prefix_type != dd2_prefix_type) { |
| continue; |
| } |
| |
| // At this point, we know we have synonymous data descriptors of the |
| // form: |
| // obj_1[a_1, ..., a_m, i] |
| // obj_2[b_1, ..., b_n, i] |
| // with the same last_index i, such that: |
| // obj_1[a_1, ..., a_m] |
| // and |
| // obj_2[b_1, ..., b_n] |
| // have the same type. |
| |
| // Work out how many components there are in the (common) commposite |
| // type associated with obj_1[a_1, ..., a_m] and obj_2[b_1, ..., b_n]. |
| // This depends on whether the composite type is array, matrix, struct |
| // or vector. |
| uint32_t num_components_in_composite; |
| auto composite_type = |
| context->get_type_mgr()->GetType(dd1_prefix_type); |
| auto composite_type_instruction = |
| context->get_def_use_mgr()->GetDef(dd1_prefix_type); |
| if (composite_type->AsArray()) { |
| num_components_in_composite = |
| fuzzerutil::GetArraySize(*composite_type_instruction, context); |
| if (num_components_in_composite == 0) { |
| // This indicates that the array has an unknown size, in which |
| // case we cannot be sure we have matched all of its elements with |
| // synonymous elements of another array. |
| continue; |
| } |
| } else if (composite_type->AsMatrix()) { |
| num_components_in_composite = |
| composite_type->AsMatrix()->element_count(); |
| } else if (composite_type->AsStruct()) { |
| num_components_in_composite = fuzzerutil::GetNumberOfStructMembers( |
| *composite_type_instruction); |
| } else { |
| assert(composite_type->AsVector()); |
| num_components_in_composite = |
| composite_type->AsVector()->element_count(); |
| } |
| |
| // We are one step closer to being able to say that |dd1_prefix| and |
| // |dd2_prefix| are synonymous. |
| DataDescriptorPair candidate_composite_synonym(dd1_prefix, |
| dd2_prefix); |
| |
| // We look up what we already know about this pair. |
| auto existing_entry = |
| candidate_composite_synonyms.find(candidate_composite_synonym); |
| |
| if (existing_entry == candidate_composite_synonyms.end()) { |
| // If this is the first time we have seen the pair, we make a vector |
| // of size |num_components_in_composite| that is 'true' at the |
| // common final index associated with |dd1| and |dd2|, and 'false' |
| // everywhere else, and register this vector as being associated |
| // with the pair. |
| std::vector<bool> entry; |
| for (uint32_t i = 0; i < num_components_in_composite; i++) { |
| entry.push_back(i == common_final_index); |
| } |
| candidate_composite_synonyms[candidate_composite_synonym] = entry; |
| existing_entry = |
| candidate_composite_synonyms.find(candidate_composite_synonym); |
| } else { |
| // We have seen this pair of data descriptors before, and we now |
| // know that they are synonymous at one further index, so we |
| // update the entry to record that. |
| existing_entry->second[common_final_index] = true; |
| } |
| assert(existing_entry != candidate_composite_synonyms.end()); |
| |
| // Check whether |dd1_prefix| and |dd2_prefix| are now known to match |
| // at every sub-component. |
| bool all_components_match = true; |
| for (uint32_t i = 0; i < num_components_in_composite; i++) { |
| if (!existing_entry->second[i]) { |
| all_components_match = false; |
| break; |
| } |
| } |
| if (all_components_match) { |
| // The two prefixes match on all sub-components, so we know that |
| // they are synonymous. We add this fact *non-recursively*, as we |
| // have deduced that |dd1_prefix| and |dd2_prefix| are synonymous |
| // by observing that all their sub-components are already |
| // synonymous. |
| assert(DataDescriptorsAreWellFormedAndComparable( |
| context, dd1_prefix, dd2_prefix)); |
| MakeEquivalent(dd1_prefix, dd2_prefix); |
| // Now that we know this pair of data descriptors are synonymous, |
| // there is no point recording how close they are to being |
| // synonymous. |
| candidate_composite_synonyms.erase(candidate_composite_synonym); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| void FactManager::DataSynonymAndIdEquationFacts::MakeEquivalent( |
| const protobufs::DataDescriptor& dd1, |
| const protobufs::DataDescriptor& dd2) { |
| // Register the data descriptors if they are not already known to the |
| // equivalence relation. |
| for (const auto& dd : {dd1, dd2}) { |
| if (!synonymous_.Exists(dd)) { |
| synonymous_.Register(dd); |
| } |
| } |
| |
| if (synonymous_.IsEquivalent(dd1, dd2)) { |
| // The data descriptors are already known to be equivalent, so there is |
| // nothing to do. |
| return; |
| } |
| |
| // We must make the data descriptors equivalent, and also make sure any |
| // equation facts known about their representatives are merged. |
| |
| // Record the original equivalence class representatives of the data |
| // descriptors. |
| auto dd1_original_representative = synonymous_.Find(&dd1); |
| auto dd2_original_representative = synonymous_.Find(&dd2); |
| |
| // Make the data descriptors equivalent. |
| synonymous_.MakeEquivalent(dd1, dd2); |
| // As we have updated the equivalence relation, we might be able to deduce |
| // more facts by performing a closure computation, so we record that such a |
| // computation is required. |
| closure_computation_required_ = true; |
| |
| // At this point, exactly one of |dd1_original_representative| and |
| // |dd2_original_representative| will be the representative of the combined |
| // equivalence class. We work out which one of them is still the class |
| // representative and which one is no longer the class representative. |
| |
| auto still_representative = synonymous_.Find(dd1_original_representative) == |
| dd1_original_representative |
| ? dd1_original_representative |
| : dd2_original_representative; |
| auto no_longer_representative = |
| still_representative == dd1_original_representative |
| ? dd2_original_representative |
| : dd1_original_representative; |
| |
| assert(no_longer_representative != still_representative && |
| "The current and former representatives cannot be the same."); |
| |
| // We now need to add all equations about |no_longer_representative| to the |
| // set of equations known about |still_representative|. |
| |
| // Get the equations associated with |no_longer_representative|. |
| auto no_longer_representative_id_equations = |
| id_equations_.find(no_longer_representative); |
| if (no_longer_representative_id_equations != id_equations_.end()) { |
| // There are some equations to transfer. There might not yet be any |
| // equations about |still_representative|; create an empty set of equations |
| // if this is the case. |
| if (!id_equations_.count(still_representative)) { |
| id_equations_.insert({still_representative, OperationSet()}); |
| } |
| auto still_representative_id_equations = |
| id_equations_.find(still_representative); |
| assert(still_representative_id_equations != id_equations_.end() && |
| "At this point there must be a set of equations."); |
| // Add all the equations known about |no_longer_representative| to the set |
| // of equations known about |still_representative|. |
| still_representative_id_equations->second.insert( |
| no_longer_representative_id_equations->second.begin(), |
| no_longer_representative_id_equations->second.end()); |
| } |
| // Delete the no longer-relevant equations about |no_longer_representative|. |
| id_equations_.erase(no_longer_representative); |
| } |
| |
| bool FactManager::DataSynonymAndIdEquationFacts:: |
| DataDescriptorsAreWellFormedAndComparable( |
| opt::IRContext* context, const protobufs::DataDescriptor& dd1, |
| const protobufs::DataDescriptor& dd2) const { |
| auto end_type_id_1 = fuzzerutil::WalkCompositeTypeIndices( |
| context, context->get_def_use_mgr()->GetDef(dd1.object())->type_id(), |
| dd1.index()); |
| auto end_type_id_2 = fuzzerutil::WalkCompositeTypeIndices( |
| context, context->get_def_use_mgr()->GetDef(dd2.object())->type_id(), |
| dd2.index()); |
| // The end types of the data descriptors must exist. |
| if (end_type_id_1 == 0 || end_type_id_2 == 0) { |
| 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; |
| } |
| // Otherwise they are only comparable if they are integer scalars or integer |
| // vectors that differ only in signedness. |
| |
| // Get both types. |
| const opt::analysis::Type* type_1 = |
| context->get_type_mgr()->GetType(end_type_id_1); |
| const opt::analysis::Type* type_2 = |
| context->get_type_mgr()->GetType(end_type_id_2); |
| |
| // If the first type is a vector, check that the second type is a vector of |
| // the same width, and drill down to the vector element types. |
| if (type_1->AsVector()) { |
| if (!type_2->AsVector()) { |
| return false; |
| } |
| if (type_1->AsVector()->element_count() != |
| type_2->AsVector()->element_count()) { |
| return false; |
| } |
| type_1 = type_1->AsVector()->element_type(); |
| type_2 = type_2->AsVector()->element_type(); |
| } |
| // Check that type_1 and type_2 are both integer types of the same bit-width |
| // (but with potentially different signedness). |
| auto integer_type_1 = type_1->AsInteger(); |
| auto integer_type_2 = type_2->AsInteger(); |
| return integer_type_1 && integer_type_2 && |
| integer_type_1->width() == integer_type_2->width(); |
| } |
| |
| std::vector<const protobufs::DataDescriptor*> |
| FactManager::DataSynonymAndIdEquationFacts::GetSynonymsForDataDescriptor( |
| const protobufs::DataDescriptor& data_descriptor) const { |
| if (synonymous_.Exists(data_descriptor)) { |
| return synonymous_.GetEquivalenceClass(data_descriptor); |
| } |
| return std::vector<const protobufs::DataDescriptor*>(); |
| } |
| |
| std::vector<uint32_t> |
| FactManager::DataSynonymAndIdEquationFacts::GetIdsForWhichSynonymsAreKnown() |
| const { |
| std::vector<uint32_t> result; |
| for (auto& data_descriptor : synonymous_.GetAllKnownValues()) { |
| if (data_descriptor->index().empty()) { |
| result.push_back(data_descriptor->object()); |
| } |
| } |
| return result; |
| } |
| |
| bool FactManager::DataSynonymAndIdEquationFacts::IsSynonymous( |
| const protobufs::DataDescriptor& data_descriptor1, |
| const protobufs::DataDescriptor& data_descriptor2) const { |
| return synonymous_.Exists(data_descriptor1) && |
| synonymous_.Exists(data_descriptor2) && |
| 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<uint32_t> 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<uint32_t> 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 pointee value facts |
| |
| // The purpose of this class is to group the fields and data used to represent |
| // facts about pointers whose pointee values are irrelevant. |
| class FactManager::IrrelevantPointeeValueFacts { |
| 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. |
| bool PointeeValueIsIrrelevant(uint32_t pointer_id) const; |
| |
| private: |
| std::set<uint32_t> pointers_to_irrelevant_pointees_ids_; |
| }; |
| |
| void FactManager::IrrelevantPointeeValueFacts::AddFact( |
| const protobufs::FactPointeeValueIsIrrelevant& fact) { |
| pointers_to_irrelevant_pointees_ids_.insert(fact.pointer_id()); |
| } |
| |
| bool FactManager::IrrelevantPointeeValueFacts::PointeeValueIsIrrelevant( |
| uint32_t pointer_id) const { |
| return pointers_to_irrelevant_pointees_ids_.count(pointer_id) != 0; |
| } |
| |
| // End of arbitrarily-valued variable facts |
| //============================== |
| |
| FactManager::FactManager() |
| : uniform_constant_facts_(MakeUnique<ConstantUniformFacts>()), |
| data_synonym_and_id_equation_facts_( |
| MakeUnique<DataSynonymAndIdEquationFacts>()), |
| dead_block_facts_(MakeUnique<DeadBlockFacts>()), |
| livesafe_function_facts_(MakeUnique<LivesafeFunctionFacts>()), |
| irrelevant_pointee_value_facts_( |
| MakeUnique<IrrelevantPointeeValueFacts>()) {} |
| |
| 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) { |
| protobufs::FactDataSynonym fact; |
| *fact.mutable_data1() = data1; |
| *fact.mutable_data2() = data2; |
| data_synonym_and_id_equation_facts_->AddFact(fact, context); |
| } |
| |
| std::vector<uint32_t> FactManager::GetConstantsAvailableFromUniformsForType( |
| opt::IRContext* ir_context, uint32_t type_id) const { |
| return uniform_constant_facts_->GetConstantsAvailableFromUniformsForType( |
| ir_context, type_id); |
| } |
| |
| const std::vector<protobufs::UniformBufferElementDescriptor> |
| 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<uint32_t> FactManager::GetTypesForWhichUniformValuesAreKnown() |
| const { |
| return uniform_constant_facts_->GetTypesForWhichUniformValuesAreKnown(); |
| } |
| |
| const std::vector<std::pair<protobufs::FactConstantUniform, uint32_t>>& |
| FactManager::GetConstantUniformFactsAndTypes() const { |
| return uniform_constant_facts_->GetConstantUniformFactsAndTypes(); |
| } |
| |
| std::vector<uint32_t> FactManager::GetIdsForWhichSynonymsAreKnown() const { |
| return data_synonym_and_id_equation_facts_->GetIdsForWhichSynonymsAreKnown(); |
| } |
| |
| std::vector<const protobufs::DataDescriptor*> |
| FactManager::GetSynonymsForDataDescriptor( |
| const protobufs::DataDescriptor& data_descriptor) const { |
| return data_synonym_and_id_equation_facts_->GetSynonymsForDataDescriptor( |
| data_descriptor); |
| } |
| |
| std::vector<const protobufs::DataDescriptor*> 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_pointee_value_facts_->PointeeValueIsIrrelevant(pointer_id); |
| } |
| |
| void FactManager::AddFactValueOfPointeeIsIrrelevant(uint32_t pointer_id) { |
| protobufs::FactPointeeValueIsIrrelevant fact; |
| fact.set_pointer_id(pointer_id); |
| irrelevant_pointee_value_facts_->AddFact(fact); |
| } |
| |
| void FactManager::AddFactIdEquation(uint32_t lhs_id, SpvOp opcode, |
| const std::vector<uint32_t>& 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 |