diff --git a/3rdparty/spirv-tools/include/generated/OpenCLDebugInfo100.h b/3rdparty/spirv-tools/include/generated/OpenCLDebugInfo100.h index 9a68f2761..cc702d8d0 100644 --- a/3rdparty/spirv-tools/include/generated/OpenCLDebugInfo100.h +++ b/3rdparty/spirv-tools/include/generated/OpenCLDebugInfo100.h @@ -67,6 +67,7 @@ enum OpenCLDebugInfo100Instructions { OpenCLDebugInfo100DebugExpression = 31, OpenCLDebugInfo100DebugMacroDef = 32, OpenCLDebugInfo100DebugMacroUndef = 33, + OpenCLDebugInfo100DebugImportedEntity = 34, OpenCLDebugInfo100DebugSource = 35, OpenCLDebugInfo100InstructionsMax = 0x7ffffff }; diff --git a/3rdparty/spirv-tools/include/generated/build-version.inc b/3rdparty/spirv-tools/include/generated/build-version.inc index a22227096..4343766c1 100644 --- a/3rdparty/spirv-tools/include/generated/build-version.inc +++ b/3rdparty/spirv-tools/include/generated/build-version.inc @@ -1 +1 @@ -"v2020.2-dev", "SPIRV-Tools v2020.2-dev b53f48f92c539b6f8561e36023bbf281d8dfeda8" +"v2020.2-dev", "SPIRV-Tools v2020.2-dev 1584e578d04d193f9a242f79666f73f8cba06f10" diff --git a/3rdparty/spirv-tools/include/generated/opencl.debuginfo.100.insts.inc b/3rdparty/spirv-tools/include/generated/opencl.debuginfo.100.insts.inc index 4bde097af..6ba095b48 100644 --- a/3rdparty/spirv-tools/include/generated/opencl.debuginfo.100.insts.inc +++ b/3rdparty/spirv-tools/include/generated/opencl.debuginfo.100.insts.inc @@ -35,5 +35,6 @@ static const spv_ext_inst_desc_t opencl_debuginfo_100_entries[] = { {"DebugExpression", 31, 0, nullptr, {SPV_OPERAND_TYPE_VARIABLE_ID, SPV_OPERAND_TYPE_NONE}}, {"DebugMacroDef", 32, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_OPTIONAL_ID, SPV_OPERAND_TYPE_NONE}}, {"DebugMacroUndef", 33, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"DebugImportedEntity", 34, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_DEBUG_IMPORTED_ENTITY, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, {"DebugSource", 35, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_OPTIONAL_ID, SPV_OPERAND_TYPE_NONE}} }; \ No newline at end of file diff --git a/3rdparty/spirv-tools/include/spirv-tools/instrument.hpp b/3rdparty/spirv-tools/include/spirv-tools/instrument.hpp index 2dcb33316..d3180e444 100644 --- a/3rdparty/spirv-tools/include/spirv-tools/instrument.hpp +++ b/3rdparty/spirv-tools/include/spirv-tools/instrument.hpp @@ -208,6 +208,9 @@ static const int kDebugInputBindingBindless = 1; // The binding for the input buffer read by InstBuffAddrCheckPass. static const int kDebugInputBindingBuffAddr = 2; +// This is the output buffer written by InstDebugPrintfPass. +static const int kDebugOutputPrintfStream = 3; + // Bindless Validation Input Buffer Format // // An input buffer for bindless validation consists of a single array of diff --git a/3rdparty/spirv-tools/include/spirv-tools/libspirv.h b/3rdparty/spirv-tools/include/spirv-tools/libspirv.h index d63f3634d..62337e922 100644 --- a/3rdparty/spirv-tools/include/spirv-tools/libspirv.h +++ b/3rdparty/spirv-tools/include/spirv-tools/libspirv.h @@ -232,6 +232,7 @@ typedef enum spv_operand_type_t { SPV_OPERAND_TYPE_DEBUG_COMPOSITE_TYPE, // DebugInfo Sec 3.4 SPV_OPERAND_TYPE_DEBUG_TYPE_QUALIFIER, // DebugInfo Sec 3.5 SPV_OPERAND_TYPE_DEBUG_OPERATION, // DebugInfo Sec 3.6 + SPV_OPERAND_TYPE_DEBUG_IMPORTED_ENTITY, // The following are concrete enum types from the OpenCL.DebugInfo.100 // extended instruction set. @@ -766,6 +767,9 @@ SPIRV_TOOLS_EXPORT void spvDiagnosticDestroy(spv_diagnostic diagnostic); SPIRV_TOOLS_EXPORT spv_result_t spvDiagnosticPrint(const spv_diagnostic diagnostic); +// Gets the name of an instruction, without the "Op" prefix. +SPIRV_TOOLS_EXPORT const char* spvOpcodeString(const uint32_t opcode); + // The binary parser interface. // A pointer to a function that accepts a parsed SPIR-V header. diff --git a/3rdparty/spirv-tools/include/spirv-tools/optimizer.hpp b/3rdparty/spirv-tools/include/spirv-tools/optimizer.hpp index c31ccef8c..b90492323 100644 --- a/3rdparty/spirv-tools/include/spirv-tools/optimizer.hpp +++ b/3rdparty/spirv-tools/include/spirv-tools/optimizer.hpp @@ -791,6 +791,18 @@ Optimizer::PassToken CreateInstBuffAddrCheckPass(uint32_t desc_set, uint32_t shader_id, uint32_t version = 2); +// Create a pass to instrument OpDebugPrintf instructions. +// This pass replaces all OpDebugPrintf instructions with instructions to write +// a record containing the string id and the all specified values into a special +// printf output buffer (if space allows). This pass is designed to support +// the printf validation in the Vulkan validation layers. +// +// The instrumentation will write buffers in debug descriptor set |desc_set|. +// It will write |shader_id| in each output record to identify the shader +// module which generated the record. +Optimizer::PassToken CreateInstDebugPrintfPass(uint32_t desc_set, + uint32_t shader_id); + // Create a pass to upgrade to the VulkanKHR memory model. // This pass upgrades the Logical GLSL450 memory model to Logical VulkanKHR. // Additionally, it modifies memory, image, atomic and barrier operations to diff --git a/3rdparty/spirv-tools/source/CMakeLists.txt b/3rdparty/spirv-tools/source/CMakeLists.txt index 4e7e10cb6..708ca8483 100644 --- a/3rdparty/spirv-tools/source/CMakeLists.txt +++ b/3rdparty/spirv-tools/source/CMakeLists.txt @@ -126,13 +126,11 @@ macro(spvtools_vendor_tables VENDOR_TABLE SHORT_NAME OPERAND_KIND_PREFIX) endmacro(spvtools_vendor_tables) macro(spvtools_extinst_lang_headers NAME GRAMMAR_FILE) - set(OUTBASE ${spirv-tools_BINARY_DIR}/${NAME}) - set(OUT_H ${OUTBASE}.h) + set(OUT_H ${spirv-tools_BINARY_DIR}/${NAME}.h) add_custom_command(OUTPUT ${OUT_H} COMMAND ${PYTHON_EXECUTABLE} ${LANG_HEADER_PROCESSING_SCRIPT} - --extinst-name=${NAME} --extinst-grammar=${GRAMMAR_FILE} - --extinst-output-base=${OUTBASE} + --extinst-output-path=${OUT_H} DEPENDS ${LANG_HEADER_PROCESSING_SCRIPT} ${GRAMMAR_FILE} COMMENT "Generate language specific header for ${NAME}.") add_custom_target(spirv-tools-header-${NAME} DEPENDS ${OUT_H}) diff --git a/3rdparty/spirv-tools/source/enum_set.h b/3rdparty/spirv-tools/source/enum_set.h index 2e7046d4e..d4d31e332 100644 --- a/3rdparty/spirv-tools/source/enum_set.h +++ b/3rdparty/spirv-tools/source/enum_set.h @@ -93,6 +93,10 @@ class EnumSet { // enum value is already in the set. void Add(EnumType c) { AddWord(ToWord(c)); } + // Removes the given enum value from the set. This has no effect if the + // enum value is not in the set. + void Remove(EnumType c) { RemoveWord(ToWord(c)); } + // Returns true if this enum value is in the set. bool Contains(EnumType c) const { return ContainsWord(ToWord(c)); } @@ -141,6 +145,17 @@ class EnumSet { } } + // Removes the given enum value (as a 32-bit word) from the set. This has no + // effect if the enum value is not in the set. + void RemoveWord(uint32_t word) { + if (auto new_bits = AsMask(word)) { + mask_ &= ~new_bits; + } else { + auto itr = Overflow().find(word); + if (itr != Overflow().end()) Overflow().erase(itr); + } + } + // Returns true if the enum represented as a 32-bit word is in the set. bool ContainsWord(uint32_t word) const { // We shouldn't call Overflow() since this is a const method. diff --git a/3rdparty/spirv-tools/source/extinst.opencl.debuginfo.100.grammar.json b/3rdparty/spirv-tools/source/extinst.opencl.debuginfo.100.grammar.json index 9a6fe2738..08062be4f 100644 --- a/3rdparty/spirv-tools/source/extinst.opencl.debuginfo.100.grammar.json +++ b/3rdparty/spirv-tools/source/extinst.opencl.debuginfo.100.grammar.json @@ -375,6 +375,19 @@ { "kind" : "IdRef", "name" : "'Macro'" } ] }, + { + "opname" : "DebugImportedEntity", + "opcode" : 34, + "operands" : [ + { "kind" : "IdRef", "name" : "'Name'" }, + { "kind" : "DebugImportedEntity", "name" : "'Tag'" }, + { "kind" : "IdRef", "name" : "'Source'" }, + { "kind" : "IdRef", "name" : "'Entity'" }, + { "kind" : "LiteralInteger", "name" : "'Line'" }, + { "kind" : "LiteralInteger", "name" : "'Column'" }, + { "kind" : "IdRef", "name" : "'Parent'" } + ] + }, { "opname" : "DebugSource", "opcode" : 35, diff --git a/3rdparty/spirv-tools/source/fuzz/CMakeLists.txt b/3rdparty/spirv-tools/source/fuzz/CMakeLists.txt index c816f8757..3a9d604c8 100644 --- a/3rdparty/spirv-tools/source/fuzz/CMakeLists.txt +++ b/3rdparty/spirv-tools/source/fuzz/CMakeLists.txt @@ -29,6 +29,7 @@ if(SPIRV_BUILD_FUZZER) ) set(SPIRV_TOOLS_FUZZ_SOURCES + call_graph.h data_descriptor.h equivalence_relation.h fact_manager.h @@ -36,11 +37,18 @@ if(SPIRV_BUILD_FUZZER) fuzzer.h fuzzer_context.h fuzzer_pass.h + fuzzer_pass_add_access_chains.h fuzzer_pass_add_composite_types.h fuzzer_pass_add_dead_blocks.h fuzzer_pass_add_dead_breaks.h fuzzer_pass_add_dead_continues.h + fuzzer_pass_add_equation_instructions.h + fuzzer_pass_add_function_calls.h + fuzzer_pass_add_global_variables.h + fuzzer_pass_add_loads.h + fuzzer_pass_add_local_variables.h fuzzer_pass_add_no_contraction_decorations.h + fuzzer_pass_add_stores.h fuzzer_pass_add_useful_constructs.h fuzzer_pass_adjust_function_controls.h fuzzer_pass_adjust_loop_controls.h @@ -54,7 +62,10 @@ if(SPIRV_BUILD_FUZZER) fuzzer_pass_obfuscate_constants.h fuzzer_pass_outline_functions.h fuzzer_pass_permute_blocks.h + fuzzer_pass_permute_function_parameters.h fuzzer_pass_split_blocks.h + fuzzer_pass_swap_commutable_operands.h + fuzzer_pass_toggle_access_chain_instruction.h fuzzer_util.h id_use_descriptor.h instruction_descriptor.h @@ -65,6 +76,7 @@ if(SPIRV_BUILD_FUZZER) replayer.h shrinker.h transformation.h + transformation_access_chain.h transformation_add_constant_boolean.h transformation_add_constant_composite.h transformation_add_constant_scalar.h @@ -74,6 +86,7 @@ if(SPIRV_BUILD_FUZZER) transformation_add_function.h transformation_add_global_undef.h transformation_add_global_variable.h + transformation_add_local_variable.h transformation_add_no_contraction_decoration.h transformation_add_type_array.h transformation_add_type_boolean.h @@ -87,9 +100,13 @@ if(SPIRV_BUILD_FUZZER) transformation_composite_construct.h transformation_composite_extract.h transformation_copy_object.h + transformation_equation_instruction.h + transformation_function_call.h + transformation_load.h transformation_merge_blocks.h transformation_move_block_down.h transformation_outline_function.h + transformation_permute_function_parameters.h transformation_replace_boolean_constant_with_constant_binary.h transformation_replace_constant_with_uniform.h transformation_replace_id_with_synonym.h @@ -98,21 +115,32 @@ if(SPIRV_BUILD_FUZZER) transformation_set_memory_operands_mask.h transformation_set_selection_control.h transformation_split_block.h + transformation_store.h + transformation_swap_commutable_operands.h + transformation_toggle_access_chain_instruction.h transformation_vector_shuffle.h uniform_buffer_element_descriptor.h ${CMAKE_CURRENT_BINARY_DIR}/protobufs/spvtoolsfuzz.pb.h + call_graph.cpp data_descriptor.cpp fact_manager.cpp force_render_red.cpp fuzzer.cpp fuzzer_context.cpp fuzzer_pass.cpp + fuzzer_pass_add_access_chains.cpp fuzzer_pass_add_composite_types.cpp fuzzer_pass_add_dead_blocks.cpp fuzzer_pass_add_dead_breaks.cpp fuzzer_pass_add_dead_continues.cpp + fuzzer_pass_add_equation_instructions.cpp + fuzzer_pass_add_function_calls.cpp + fuzzer_pass_add_global_variables.cpp + fuzzer_pass_add_loads.cpp + fuzzer_pass_add_local_variables.cpp fuzzer_pass_add_no_contraction_decorations.cpp + fuzzer_pass_add_stores.cpp fuzzer_pass_add_useful_constructs.cpp fuzzer_pass_adjust_function_controls.cpp fuzzer_pass_adjust_loop_controls.cpp @@ -126,7 +154,10 @@ if(SPIRV_BUILD_FUZZER) fuzzer_pass_obfuscate_constants.cpp fuzzer_pass_outline_functions.cpp fuzzer_pass_permute_blocks.cpp + fuzzer_pass_permute_function_parameters.cpp fuzzer_pass_split_blocks.cpp + fuzzer_pass_swap_commutable_operands.cpp + fuzzer_pass_toggle_access_chain_instruction.cpp fuzzer_util.cpp id_use_descriptor.cpp instruction_descriptor.cpp @@ -136,6 +167,7 @@ if(SPIRV_BUILD_FUZZER) replayer.cpp shrinker.cpp transformation.cpp + transformation_access_chain.cpp transformation_add_constant_boolean.cpp transformation_add_constant_composite.cpp transformation_add_constant_scalar.cpp @@ -145,6 +177,7 @@ if(SPIRV_BUILD_FUZZER) transformation_add_function.cpp transformation_add_global_undef.cpp transformation_add_global_variable.cpp + transformation_add_local_variable.cpp transformation_add_no_contraction_decoration.cpp transformation_add_type_array.cpp transformation_add_type_boolean.cpp @@ -158,9 +191,13 @@ if(SPIRV_BUILD_FUZZER) transformation_composite_construct.cpp transformation_composite_extract.cpp transformation_copy_object.cpp + transformation_equation_instruction.cpp + transformation_function_call.cpp + transformation_load.cpp transformation_merge_blocks.cpp transformation_move_block_down.cpp transformation_outline_function.cpp + transformation_permute_function_parameters.cpp transformation_replace_boolean_constant_with_constant_binary.cpp transformation_replace_constant_with_uniform.cpp transformation_replace_id_with_synonym.cpp @@ -169,6 +206,9 @@ if(SPIRV_BUILD_FUZZER) transformation_set_memory_operands_mask.cpp transformation_set_selection_control.cpp transformation_split_block.cpp + transformation_store.cpp + transformation_swap_commutable_operands.cpp + transformation_toggle_access_chain_instruction.cpp transformation_vector_shuffle.cpp uniform_buffer_element_descriptor.cpp ${CMAKE_CURRENT_BINARY_DIR}/protobufs/spvtoolsfuzz.pb.cc diff --git a/3rdparty/spirv-tools/source/fuzz/call_graph.cpp b/3rdparty/spirv-tools/source/fuzz/call_graph.cpp new file mode 100644 index 000000000..15416fe3e --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/call_graph.cpp @@ -0,0 +1,81 @@ +// 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/call_graph.h" + +#include + +namespace spvtools { +namespace fuzz { + +CallGraph::CallGraph(opt::IRContext* context) { + // Initialize function in-degree and call graph edges to 0 and empty. + for (auto& function : *context->module()) { + function_in_degree_[function.result_id()] = 0; + call_graph_edges_[function.result_id()] = std::set(); + } + + // Consider every function. + for (auto& function : *context->module()) { + // Avoid considering the same callee of this function multiple times by + // recording known callees. + std::set known_callees; + // Consider every function call instruction in every block. + for (auto& block : function) { + for (auto& instruction : block) { + if (instruction.opcode() != SpvOpFunctionCall) { + continue; + } + // Get the id of the function being called. + uint32_t callee = instruction.GetSingleWordInOperand(0); + if (known_callees.count(callee)) { + // We have already considered a call to this function - ignore it. + continue; + } + // Increase the callee's in-degree and add an edge to the call graph. + function_in_degree_[callee]++; + call_graph_edges_[function.result_id()].insert(callee); + // Mark the callee as 'known'. + known_callees.insert(callee); + } + } + } +} + +void CallGraph::PushDirectCallees(uint32_t function_id, + std::queue* queue) const { + for (auto callee : GetDirectCallees(function_id)) { + queue->push(callee); + } +} + +std::set CallGraph::GetIndirectCallees(uint32_t function_id) const { + std::set result; + std::queue queue; + PushDirectCallees(function_id, &queue); + + while (!queue.empty()) { + auto next = queue.front(); + queue.pop(); + if (result.count(next)) { + continue; + } + result.insert(next); + PushDirectCallees(next, &queue); + } + return result; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/call_graph.h b/3rdparty/spirv-tools/source/fuzz/call_graph.h new file mode 100644 index 000000000..14cd23b4c --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/call_graph.h @@ -0,0 +1,62 @@ +// 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_CALL_GRAPH_H_ +#define SOURCE_FUZZ_CALL_GRAPH_H_ + +#include +#include + +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace fuzz { + +// Represents the acyclic call graph of a SPIR-V module. +class CallGraph { + public: + // Creates a call graph corresponding to the given SPIR-V module. + explicit CallGraph(opt::IRContext* context); + + // Returns a mapping from each function to its number of distinct callers. + const std::map& GetFunctionInDegree() const { + return function_in_degree_; + } + + // Returns the ids of the functions that |function_id| directly invokes. + const std::set& GetDirectCallees(uint32_t function_id) const { + return call_graph_edges_.at(function_id); + } + + // Returns the ids of the functions that |function_id| directly or indirectly + // invokes. + std::set GetIndirectCallees(uint32_t function_id) const; + + private: + // Pushes the direct callees of |function_id| on to |queue|. + void PushDirectCallees(uint32_t function_id, + std::queue* queue) const; + + // Maps each function id to the ids of its immediate callees. + std::map> call_graph_edges_; + + // For each function id, stores the number of distinct functions that call + // the function. + std::map function_in_degree_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_CALL_GRAPH_H_ diff --git a/3rdparty/spirv-tools/source/fuzz/equivalence_relation.h b/3rdparty/spirv-tools/source/fuzz/equivalence_relation.h index 046536f80..7bb8b6680 100644 --- a/3rdparty/spirv-tools/source/fuzz/equivalence_relation.h +++ b/3rdparty/spirv-tools/source/fuzz/equivalence_relation.h @@ -75,18 +75,8 @@ class EquivalenceRelation { // Register each value if necessary. for (auto value : {value1, value2}) { if (!Exists(value)) { - // Register the value in the equivalence relation. This relies on - // T having a copy constructor. - auto unique_pointer_to_value = MakeUnique(value); - auto pointer_to_value = unique_pointer_to_value.get(); - owned_values_.push_back(std::move(unique_pointer_to_value)); - value_set_.insert(pointer_to_value); - - // Initially say that the value is its own parent and that it has no - // children. - assert(pointer_to_value && "Representatives should never be null."); - parent_[pointer_to_value] = pointer_to_value; - children_[pointer_to_value] = std::vector(); + // Register the value in the equivalence relation. + Register(value); } } @@ -112,6 +102,27 @@ class EquivalenceRelation { } } + // Requires that |value| is not known to the equivalence relation. Registers + // it in its own equivalence class and returns a pointer to the equivalence + // class representative. + const T* Register(T& value) { + assert(!Exists(value)); + + // This relies on T having a copy constructor. + auto unique_pointer_to_value = MakeUnique(value); + auto pointer_to_value = unique_pointer_to_value.get(); + owned_values_.push_back(std::move(unique_pointer_to_value)); + value_set_.insert(pointer_to_value); + + // Initially say that the value is its own parent and that it has no + // children. + assert(pointer_to_value && "Representatives should never be null."); + parent_[pointer_to_value] = pointer_to_value; + children_[pointer_to_value] = std::vector(); + + return pointer_to_value; + } + // Returns exactly one representative per equivalence class. std::vector GetEquivalenceClassRepresentatives() const { std::vector result; @@ -168,7 +179,6 @@ class EquivalenceRelation { return value_set_.find(&value) != value_set_.end(); } - private: // Returns the representative of the equivalence class of |value|, which must // already be known to the equivalence relation. This is the 'Find' operation // in a classic union-find data structure. @@ -207,6 +217,7 @@ class EquivalenceRelation { return result; } + private: // Maps every value to a parent. The representative of an equivalence class // is its own parent. A value's representative can be found by walking its // chain of ancestors. diff --git a/3rdparty/spirv-tools/source/fuzz/fact_manager.cpp b/3rdparty/spirv-tools/source/fuzz/fact_manager.cpp index a7b431120..31d3b947f 100644 --- a/3rdparty/spirv-tools/source/fuzz/fact_manager.cpp +++ b/3rdparty/spirv-tools/source/fuzz/fact_manager.cpp @@ -28,24 +28,13 @@ namespace fuzz { namespace { -std::string ToString(const protobufs::Fact& fact) { - assert(fact.fact_case() == protobufs::Fact::kConstantUniformFact && - "Right now this is the only fact we know how to stringify."); +std::string ToString(const protobufs::FactConstantUniform& fact) { std::stringstream stream; - stream << "(" - << fact.constant_uniform_fact() - .uniform_buffer_element_descriptor() - .descriptor_set() - << ", " - << fact.constant_uniform_fact() - .uniform_buffer_element_descriptor() - .binding() - << ")["; + stream << "(" << fact.uniform_buffer_element_descriptor().descriptor_set() + << ", " << fact.uniform_buffer_element_descriptor().binding() << ")["; bool first = true; - for (auto index : fact.constant_uniform_fact() - .uniform_buffer_element_descriptor() - .index()) { + for (auto index : fact.uniform_buffer_element_descriptor().index()) { if (first) { first = false; } else { @@ -57,7 +46,7 @@ std::string ToString(const protobufs::Fact& fact) { stream << "] == ["; first = true; - for (auto constant_word : fact.constant_uniform_fact().constant_word()) { + for (auto constant_word : fact.constant_word()) { if (first) { first = false; } else { @@ -70,6 +59,36 @@ std::string ToString(const protobufs::Fact& fact) { 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 //======================= @@ -331,15 +350,70 @@ FactManager::ConstantUniformFacts::GetConstantUniformFactsAndTypes() const { //============================== //============================== -// Data synonym 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; + 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. -class FactManager::DataSynonymFacts { +// 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, @@ -355,11 +429,12 @@ class FactManager::DataSynonymFacts { opt::IRContext* context) const; private: - // Adds |fact| to the set of managed facts, and recurses into sub-components - // of the data descriptors referenced in |fact|, if they are composites, to + // 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 AddFactRecursive(const protobufs::FactDataSynonym& fact, - opt::IRContext* context); + void AddDataSynonymFactRecursive(const protobufs::DataDescriptor& dd1, + const protobufs::DataDescriptor& dd2, + opt::IRContext* context); // Inspects all known facts and adds corollary facts; e.g. if we know that // a.x == b.x and a.y == b.y, where a and b have vec2 type, we can record @@ -371,11 +446,21 @@ class FactManager::DataSynonymFacts { void ComputeClosureOfFacts(opt::IRContext* context) const; // Returns true if and only if |dd1| and |dd2| are valid data descriptors - // whose associated data have the same type. + // whose associated data have the same type (modulo integer signedness). bool DataDescriptorsAreWellFormedAndComparable( opt::IRContext* context, const protobufs::DataDescriptor& dd1, const protobufs::DataDescriptor& dd2) const; + // Requires that |lhs_dd| and every element of |rhs_dds| is present in the + // |synonymous_| equivalence relation and is its own representative. Records + // the fact that the equation "|lhs_dd| |opcode| |rhs_dds|" 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. // @@ -396,31 +481,243 @@ class FactManager::DataSynonymFacts { // whether a new fact has been added since the last time such a computation // was performed. // - // It is mutable so faciliate having const methods, that provide answers to + // It is mutable to facilitate having const methods, that provide answers to // questions about data synonym facts, triggering closure computation on // demand. - mutable bool closure_computation_required = false; + mutable 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*, + std::unordered_set> + id_equations_; }; -void FactManager::DataSynonymFacts::AddFact( +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. - AddFactRecursive(fact, context); + AddDataSynonymFactRecursive(fact.data1(), fact.data2(), context); } -void FactManager::DataSynonymFacts::AddFactRecursive( - const protobufs::FactDataSynonym& fact, opt::IRContext* context) { - assert(DataDescriptorsAreWellFormedAndComparable(context, fact.data1(), - fact.data2())); +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, and get a pointer + // to its representative. + if (!synonymous_.Exists(lhs_dd)) { + synonymous_.Register(lhs_dd); + } + const protobufs::DataDescriptor* lhs_dd_ptr = synonymous_.Find(&lhs_dd); + + // Get equivalence class representatives for all ids used on the RHS of the + // equation. + std::vector 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)); + } + // We now have the equation in a form where it refers exclusively to + // equivalence class representatives. Add it to our set of facts and work + // out any follow-on facts. + AddEquationFactRecursive(*lhs_dd_ptr, static_cast(fact.opcode()), + rhs_dd_ptrs, context); +} + +void FactManager::DataSynonymAndIdEquationFacts::AddEquationFactRecursive( + const protobufs::DataDescriptor& lhs_dd, SpvOp opcode, + const std::vector& rhs_dds, + opt::IRContext* context) { + // Precondition: all data descriptors referenced in this equation must be + // equivalence class representatives - i.e. the equation must be in canonical + // form. + assert(synonymous_.Exists(lhs_dd)); + assert(synonymous_.Find(&lhs_dd) == &lhs_dd); + for (auto rhs_dd : rhs_dds) { + (void)(rhs_dd); // Keep compilers happy in release mode. + assert(synonymous_.Exists(*rhs_dd)); + assert(synonymous_.Find(rhs_dd) == rhs_dd); + } + + if (id_equations_.count(&lhs_dd) == 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, + std::unordered_set()}); + } + + { + auto existing_equations = id_equations_.find(&lhs_dd); + 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" + { + auto existing_first_operand_equations = id_equations_.find(rhs_dds[0]); + if (existing_first_operand_equations != id_equations_.end()) { + for (auto equation : existing_first_operand_equations->second) { + if (equation.opcode == SpvOpISub) { + // Equation form: "a = (d - e) + c" + if (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.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); + } + } + } + } + } + { + auto existing_second_operand_equations = id_equations_.find(rhs_dds[1]); + if (existing_second_operand_equations != id_equations_.end()) { + for (auto equation : existing_second_operand_equations->second) { + if (equation.opcode == SpvOpISub) { + // Equation form: "a = b + (d - e)" + if (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" + { + auto existing_first_operand_equations = id_equations_.find(rhs_dds[0]); + if (existing_first_operand_equations != id_equations_.end()) { + for (auto equation : existing_first_operand_equations->second) { + if (equation.opcode == SpvOpIAdd) { + // Equation form: "a = (d + e) - c" + if (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 (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 (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); + } + } + } + } + } + + { + auto existing_second_operand_equations = id_equations_.find(rhs_dds[1]); + if (existing_second_operand_equations != id_equations_.end()) { + for (auto equation : existing_second_operand_equations->second) { + if (equation.opcode == SpvOpIAdd) { + // Equation form: "a = b - (d + e)" + if (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 (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 (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" + auto existing_equations = id_equations_.find(rhs_dds[0]); + if (existing_equations != id_equations_.end()) { + for (auto equation : existing_equations->second) { + 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. - synonymous_.MakeEquivalent(fact.data1(), fact.data2()); + 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; it will be performed next time a method answering // a data synonym fact-related question is invoked. - closure_computation_required = true; + closure_computation_required_ = true; // 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. @@ -428,9 +725,8 @@ void FactManager::DataSynonymFacts::AddFactRecursive( // 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(fact.data1().object())->type_id(), - fact.data1().index()); + 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 && @@ -460,21 +756,19 @@ void FactManager::DataSynonymFacts::AddFactRecursive( // obj_1[a_1, ..., a_m, i] == obj_2[b_1, ..., b_n, i] for (uint32_t i = 0; i < num_composite_elements; i++) { std::vector extended_indices1 = - fuzzerutil::RepeatedFieldToVector(fact.data1().index()); + fuzzerutil::RepeatedFieldToVector(dd1.index()); extended_indices1.push_back(i); std::vector extended_indices2 = - fuzzerutil::RepeatedFieldToVector(fact.data2().index()); + fuzzerutil::RepeatedFieldToVector(dd2.index()); extended_indices2.push_back(i); - protobufs::FactDataSynonym extended_data_synonym_fact; - *extended_data_synonym_fact.mutable_data1() = - MakeDataDescriptor(fact.data1().object(), std::move(extended_indices1)); - *extended_data_synonym_fact.mutable_data2() = - MakeDataDescriptor(fact.data2().object(), std::move(extended_indices2)); - AddFactRecursive(extended_data_synonym_fact, context); + AddDataSynonymFactRecursive( + MakeDataDescriptor(dd1.object(), std::move(extended_indices1)), + MakeDataDescriptor(dd2.object(), std::move(extended_indices2)), + context); } } -void FactManager::DataSynonymFacts::ComputeClosureOfFacts( +void FactManager::DataSynonymAndIdEquationFacts::ComputeClosureOfFacts( opt::IRContext* context) const { // 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 @@ -551,10 +845,10 @@ void FactManager::DataSynonymFacts::ComputeClosureOfFacts( // 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) { + 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; + closure_computation_required_ = false; // Consider each class in the equivalence relation. for (auto representative : @@ -738,7 +1032,7 @@ void FactManager::DataSynonymFacts::ComputeClosureOfFacts( synonymous_.MakeEquivalent(dd1_prefix, dd2_prefix); // As we have added a new synonym fact, we might benefit from doing // another pass over the equivalence relation. - closure_computation_required = true; + closure_computation_required_ = true; // Now that we know this pair of data descriptors are synonymous, // there is no point recording how close they are to being // synonymous. @@ -750,20 +1044,56 @@ void FactManager::DataSynonymFacts::ComputeClosureOfFacts( } } -bool FactManager::DataSynonymFacts::DataDescriptorsAreWellFormedAndComparable( - opt::IRContext* context, const protobufs::DataDescriptor& dd1, - const protobufs::DataDescriptor& dd2) const { - auto end_type_1 = fuzzerutil::WalkCompositeTypeIndices( +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_2 = fuzzerutil::WalkCompositeTypeIndices( + auto end_type_id_2 = fuzzerutil::WalkCompositeTypeIndices( context, context->get_def_use_mgr()->GetDef(dd2.object())->type_id(), dd2.index()); - return end_type_1 && end_type_1 == end_type_2; + // 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 -FactManager::DataSynonymFacts::GetSynonymsForDataDescriptor( +FactManager::DataSynonymAndIdEquationFacts::GetSynonymsForDataDescriptor( const protobufs::DataDescriptor& data_descriptor, opt::IRContext* context) const { ComputeClosureOfFacts(context); @@ -774,7 +1104,7 @@ FactManager::DataSynonymFacts::GetSynonymsForDataDescriptor( } std::vector -FactManager::DataSynonymFacts ::GetIdsForWhichSynonymsAreKnown( +FactManager::DataSynonymAndIdEquationFacts ::GetIdsForWhichSynonymsAreKnown( opt::IRContext* context) const { ComputeClosureOfFacts(context); std::vector result; @@ -786,12 +1116,12 @@ FactManager::DataSynonymFacts ::GetIdsForWhichSynonymsAreKnown( return result; } -bool FactManager::DataSynonymFacts::IsSynonymous( +bool FactManager::DataSynonymAndIdEquationFacts::IsSynonymous( const protobufs::DataDescriptor& data_descriptor1, const protobufs::DataDescriptor& data_descriptor2, opt::IRContext* context) const { - const_cast(this)->ComputeClosureOfFacts( - context); + const_cast(this) + ->ComputeClosureOfFacts(context); return synonymous_.Exists(data_descriptor1) && synonymous_.Exists(data_descriptor2) && synonymous_.IsEquivalent(data_descriptor1, data_descriptor2); @@ -860,30 +1190,30 @@ bool FactManager::LivesafeFunctionFacts::FunctionIsLivesafe( //============================== //============================== -// Arbitrarily-valued variable facts +// Irrelevant pointee value facts // The purpose of this class is to group the fields and data used to represent -// facts about livesafe functions. -class FactManager::ArbitrarilyValuedVaribleFacts { +// 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::FactValueOfVariableIsArbitrary& fact); + void AddFact(const protobufs::FactPointeeValueIsIrrelevant& fact); // See method in FactManager which delegates to this method. - bool VariableValueIsArbitrary(uint32_t variable_id) const; + bool PointeeValueIsIrrelevant(uint32_t pointer_id) const; private: - std::set arbitrary_valued_varible_ids_; + std::set pointers_to_irrelevant_pointees_ids_; }; -void FactManager::ArbitrarilyValuedVaribleFacts::AddFact( - const protobufs::FactValueOfVariableIsArbitrary& fact) { - arbitrary_valued_varible_ids_.insert(fact.variable_id()); +void FactManager::IrrelevantPointeeValueFacts::AddFact( + const protobufs::FactPointeeValueIsIrrelevant& fact) { + pointers_to_irrelevant_pointees_ids_.insert(fact.pointer_id()); } -bool FactManager::ArbitrarilyValuedVaribleFacts::VariableValueIsArbitrary( - uint32_t variable_id) const { - return arbitrary_valued_varible_ids_.count(variable_id) != 0; +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 @@ -891,11 +1221,12 @@ bool FactManager::ArbitrarilyValuedVaribleFacts::VariableValueIsArbitrary( FactManager::FactManager() : uniform_constant_facts_(MakeUnique()), - data_synonym_facts_(MakeUnique()), + data_synonym_and_id_equation_facts_( + MakeUnique()), dead_block_facts_(MakeUnique()), livesafe_function_facts_(MakeUnique()), - arbitrarily_valued_variable_facts_( - MakeUnique()) {} + irrelevant_pointee_value_facts_( + MakeUnique()) {} FactManager::~FactManager() = default; @@ -918,7 +1249,8 @@ bool FactManager::AddFact(const fuzz::protobufs::Fact& fact, return uniform_constant_facts_->AddFact(fact.constant_uniform_fact(), context); case protobufs::Fact::kDataSynonymFact: - data_synonym_facts_->AddFact(fact.data_synonym_fact(), context); + 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()); @@ -938,7 +1270,7 @@ void FactManager::AddFactDataSynonym(const protobufs::DataDescriptor& data1, protobufs::FactDataSynonym fact; *fact.mutable_data1() = data1; *fact.mutable_data2() = data2; - data_synonym_facts_->AddFact(fact, context); + data_synonym_and_id_equation_facts_->AddFact(fact, context); } std::vector FactManager::GetConstantsAvailableFromUniformsForType( @@ -973,15 +1305,16 @@ FactManager::GetConstantUniformFactsAndTypes() const { std::vector FactManager::GetIdsForWhichSynonymsAreKnown( opt::IRContext* context) const { - return data_synonym_facts_->GetIdsForWhichSynonymsAreKnown(context); + return data_synonym_and_id_equation_facts_->GetIdsForWhichSynonymsAreKnown( + context); } std::vector FactManager::GetSynonymsForDataDescriptor( const protobufs::DataDescriptor& data_descriptor, opt::IRContext* context) const { - return data_synonym_facts_->GetSynonymsForDataDescriptor(data_descriptor, - context); + return data_synonym_and_id_equation_facts_->GetSynonymsForDataDescriptor( + data_descriptor, context); } std::vector FactManager::GetSynonymsForId( @@ -993,8 +1326,8 @@ bool FactManager::IsSynonymous( const protobufs::DataDescriptor& data_descriptor1, const protobufs::DataDescriptor& data_descriptor2, opt::IRContext* context) const { - return data_synonym_facts_->IsSynonymous(data_descriptor1, data_descriptor2, - context); + return data_synonym_and_id_equation_facts_->IsSynonymous( + data_descriptor1, data_descriptor2, context); } bool FactManager::BlockIsDead(uint32_t block_id) const { @@ -1017,15 +1350,26 @@ void FactManager::AddFactFunctionIsLivesafe(uint32_t function_id) { livesafe_function_facts_->AddFact(fact); } -bool FactManager::VariableValueIsArbitrary(uint32_t variable_id) const { - return arbitrarily_valued_variable_facts_->VariableValueIsArbitrary( - variable_id); +bool FactManager::PointeeValueIsIrrelevant(uint32_t pointer_id) const { + return irrelevant_pointee_value_facts_->PointeeValueIsIrrelevant(pointer_id); } -void FactManager::AddFactValueOfVariableIsArbitrary(uint32_t variable_id) { - protobufs::FactValueOfVariableIsArbitrary fact; - fact.set_variable_id(variable_id); - arbitrarily_valued_variable_facts_->AddFact(fact); +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& 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); } } // namespace fuzz diff --git a/3rdparty/spirv-tools/source/fuzz/fact_manager.h b/3rdparty/spirv-tools/source/fuzz/fact_manager.h index 117ed1c61..f80d6773a 100644 --- a/3rdparty/spirv-tools/source/fuzz/fact_manager.h +++ b/3rdparty/spirv-tools/source/fuzz/fact_manager.h @@ -64,9 +64,17 @@ class FactManager { // Records the fact that |function_id| is livesafe. void AddFactFunctionIsLivesafe(uint32_t function_id); - // Records the fact that |variable_id| has an arbitrary value and can thus be - // stored to without affecting the module's behaviour. - void AddFactValueOfVariableIsArbitrary(uint32_t variable_id); + // Records the fact that the value of the pointee associated with |pointer_id| + // is irrelevant: it does not affect the observable behaviour of the module. + void AddFactValueOfPointeeIsIrrelevant(uint32_t pointer_id); + + // Records the fact that |lhs_id| is defined by the equation: + // + // |lhs_id| = |opcode| |rhs_id[0]| ... |rhs_id[N-1]| + // + void AddFactIdEquation(uint32_t lhs_id, SpvOp opcode, + const std::vector& rhs_id, + opt::IRContext* context); // The fact manager is responsible for managing a few distinct categories of // facts. In principle there could be different fact managers for each kind @@ -161,12 +169,13 @@ class FactManager { //============================== //============================== - // Querying facts about arbitrarily-valued variables + // Querying facts about pointers with irrelevant pointee values - // Returns true if and ony if |variable_id| is arbitrarily-valued. - bool VariableValueIsArbitrary(uint32_t variable_id) const; + // Returns true if and ony if the value of the pointee associated with + // |pointer_id| is irrelevant. + bool PointeeValueIsIrrelevant(uint32_t pointer_id) const; - // End of arbitrarily-valued variable facts + // End of irrelevant pointee value facts //============================== private: @@ -178,9 +187,10 @@ class FactManager { std::unique_ptr uniform_constant_facts_; // Unique pointer to internal data. - class DataSynonymFacts; // Opaque class for management of data synonym facts. - std::unique_ptr - data_synonym_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 @@ -191,10 +201,10 @@ class FactManager { std::unique_ptr livesafe_function_facts_; // Unique pointer to internal data. - class ArbitrarilyValuedVaribleFacts; // Opaque class for management of - // facts about variables whose values should be expected to be arbitrary. - std::unique_ptr - arbitrarily_valued_variable_facts_; // Unique pointer to internal data. + class IrrelevantPointeeValueFacts; // Opaque class for management of + // facts about pointers whose pointee values do not matter. + std::unique_ptr + irrelevant_pointee_value_facts_; // Unique pointer to internal data. }; } // namespace fuzz diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer.cpp index 27b697c94..119bd3c32 100644 --- a/3rdparty/spirv-tools/source/fuzz/fuzzer.cpp +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer.cpp @@ -21,11 +21,18 @@ #include "fuzzer_pass_adjust_memory_operands_masks.h" #include "source/fuzz/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_types.h" #include "source/fuzz/fuzzer_pass_add_dead_blocks.h" #include "source/fuzz/fuzzer_pass_add_dead_breaks.h" #include "source/fuzz/fuzzer_pass_add_dead_continues.h" +#include "source/fuzz/fuzzer_pass_add_equation_instructions.h" +#include "source/fuzz/fuzzer_pass_add_function_calls.h" +#include "source/fuzz/fuzzer_pass_add_global_variables.h" +#include "source/fuzz/fuzzer_pass_add_loads.h" +#include "source/fuzz/fuzzer_pass_add_local_variables.h" #include "source/fuzz/fuzzer_pass_add_no_contraction_decorations.h" +#include "source/fuzz/fuzzer_pass_add_stores.h" #include "source/fuzz/fuzzer_pass_add_useful_constructs.h" #include "source/fuzz/fuzzer_pass_adjust_function_controls.h" #include "source/fuzz/fuzzer_pass_adjust_loop_controls.h" @@ -38,7 +45,10 @@ #include "source/fuzz/fuzzer_pass_obfuscate_constants.h" #include "source/fuzz/fuzzer_pass_outline_functions.h" #include "source/fuzz/fuzzer_pass_permute_blocks.h" +#include "source/fuzz/fuzzer_pass_permute_function_parameters.h" #include "source/fuzz/fuzzer_pass_split_blocks.h" +#include "source/fuzz/fuzzer_pass_swap_commutable_operands.h" +#include "source/fuzz/fuzzer_pass_toggle_access_chain_instruction.h" #include "source/fuzz/protobufs/spirvfuzz_protobufs.h" #include "source/fuzz/pseudo_random_generator.h" #include "source/opt/build_module.h" @@ -179,6 +189,9 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run( // Apply some semantics-preserving passes. std::vector> passes; while (passes.empty()) { + MaybeAddPass(&passes, ir_context.get(), + &fact_manager, &fuzzer_context, + transformation_sequence_out); MaybeAddPass(&passes, ir_context.get(), &fact_manager, &fuzzer_context, transformation_sequence_out); @@ -191,6 +204,24 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run( MaybeAddPass(&passes, ir_context.get(), &fact_manager, &fuzzer_context, transformation_sequence_out); + MaybeAddPass( + &passes, ir_context.get(), &fact_manager, &fuzzer_context, + transformation_sequence_out); + MaybeAddPass(&passes, ir_context.get(), + &fact_manager, &fuzzer_context, + transformation_sequence_out); + MaybeAddPass(&passes, ir_context.get(), + &fact_manager, &fuzzer_context, + transformation_sequence_out); + MaybeAddPass(&passes, ir_context.get(), &fact_manager, + &fuzzer_context, + transformation_sequence_out); + MaybeAddPass(&passes, ir_context.get(), + &fact_manager, &fuzzer_context, + transformation_sequence_out); + MaybeAddPass(&passes, ir_context.get(), &fact_manager, + &fuzzer_context, + transformation_sequence_out); MaybeAddPass(&passes, ir_context.get(), &fact_manager, &fuzzer_context, transformation_sequence_out); @@ -215,6 +246,9 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run( MaybeAddPass(&passes, ir_context.get(), &fact_manager, &fuzzer_context, transformation_sequence_out); + MaybeAddPass( + &passes, ir_context.get(), &fact_manager, &fuzzer_context, + transformation_sequence_out); MaybeAddPass(&passes, ir_context.get(), &fact_manager, &fuzzer_context, transformation_sequence_out); @@ -252,6 +286,12 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run( MaybeAddPass( &final_passes, ir_context.get(), &fact_manager, &fuzzer_context, transformation_sequence_out); + MaybeAddPass( + &final_passes, ir_context.get(), &fact_manager, &fuzzer_context, + transformation_sequence_out); + MaybeAddPass( + &final_passes, ir_context.get(), &fact_manager, &fuzzer_context, + transformation_sequence_out); for (auto& pass : final_passes) { if (!impl_->ApplyPassAndCheckValidity(pass.get(), *ir_context, tools)) { return Fuzzer::FuzzerResultStatus::kFuzzerPassLedToInvalidModule; diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_context.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer_context.cpp index 916803a7b..2f9fc5af6 100644 --- a/3rdparty/spirv-tools/source/fuzz/fuzzer_context.cpp +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_context.cpp @@ -23,15 +23,22 @@ namespace { // Default pairs of probabilities for applying various // transformations. All values are percentages. Keep them in alphabetical order. +const std::pair kChanceOfAddingAccessChain = {5, 50}; const std::pair kChanceOfAddingAnotherStructField = {20, 90}; const std::pair kChanceOfAddingArrayOrStructType = {20, 90}; const std::pair kChanceOfAddingDeadBlock = {20, 90}; const std::pair kChanceOfAddingDeadBreak = {5, 80}; const std::pair kChanceOfAddingDeadContinue = {5, 80}; +const std::pair kChanceOfAddingEquationInstruction = {5, + 90}; +const std::pair kChanceOfAddingGlobalVariable = {20, 90}; +const std::pair kChanceOfAddingLoad = {5, 50}; +const std::pair kChanceOfAddingLocalVariable = {20, 90}; const std::pair kChanceOfAddingMatrixType = {20, 70}; const std::pair kChanceOfAddingNoContractionDecoration = { 5, 70}; +const std::pair kChanceOfAddingStore = {5, 50}; const std::pair kChanceOfAddingVectorType = {20, 70}; const std::pair kChanceOfAdjustingFunctionControl = {20, 70}; @@ -40,18 +47,24 @@ const std::pair kChanceOfAdjustingMemoryOperandsMask = {20, 90}; const std::pair kChanceOfAdjustingSelectionControl = {20, 90}; +const std::pair kChanceOfCallingFunction = {1, 10}; const std::pair kChanceOfChoosingStructTypeVsArrayType = { 20, 80}; const std::pair kChanceOfConstructingComposite = {20, 50}; const std::pair kChanceOfCopyingObject = {20, 50}; const std::pair kChanceOfDonatingAdditionalModule = {5, 50}; +const std::pair kChanceOfGoingDeeperWhenMakingAccessChain = + {50, 95}; const std::pair kChanceOfMakingDonorLivesafe = {40, 60}; const std::pair kChanceOfMergingBlocks = {20, 95}; const std::pair kChanceOfMovingBlockDown = {20, 50}; const std::pair kChanceOfObfuscatingConstant = {10, 90}; const std::pair kChanceOfOutliningFunction = {10, 90}; +const std::pair kChanceOfPermutingParameters = {30, 90}; const std::pair kChanceOfReplacingIdWithSynonym = {10, 90}; const std::pair kChanceOfSplittingBlock = {40, 95}; +const std::pair kChanceOfTogglingAccessChainInstruction = { + 20, 90}; // Default limits for various quantities that are chosen during fuzzing. // Keep them in alphabetical order. @@ -76,8 +89,14 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator, uint32_t min_fresh_id) : random_generator_(random_generator), next_fresh_id_(min_fresh_id), + max_loop_control_partial_count_(kDefaultMaxLoopControlPartialCount), + max_loop_control_peel_count_(kDefaultMaxLoopControlPeelCount), + max_loop_limit_(kDefaultMaxLoopLimit), + max_new_array_size_limit_(kDefaultMaxNewArraySizeLimit), go_deeper_in_constant_obfuscation_( kDefaultGoDeeperInConstantObfuscation) { + chance_of_adding_access_chain_ = + ChooseBetweenMinAndMax(kChanceOfAddingAccessChain); chance_of_adding_another_struct_field_ = ChooseBetweenMinAndMax(kChanceOfAddingAnotherStructField); chance_of_adding_array_or_struct_type_ = @@ -88,10 +107,18 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator, ChooseBetweenMinAndMax(kChanceOfAddingDeadBreak); chance_of_adding_dead_continue_ = ChooseBetweenMinAndMax(kChanceOfAddingDeadContinue); + chance_of_adding_equation_instruction_ = + ChooseBetweenMinAndMax(kChanceOfAddingEquationInstruction); + chance_of_adding_global_variable_ = + ChooseBetweenMinAndMax(kChanceOfAddingGlobalVariable); + chance_of_adding_load_ = ChooseBetweenMinAndMax(kChanceOfAddingLoad); + chance_of_adding_local_variable_ = + ChooseBetweenMinAndMax(kChanceOfAddingLocalVariable); chance_of_adding_matrix_type_ = ChooseBetweenMinAndMax(kChanceOfAddingMatrixType); chance_of_adding_no_contraction_decoration_ = ChooseBetweenMinAndMax(kChanceOfAddingNoContractionDecoration); + chance_of_adding_store_ = ChooseBetweenMinAndMax(kChanceOfAddingStore); chance_of_adding_vector_type_ = ChooseBetweenMinAndMax(kChanceOfAddingVectorType); chance_of_adjusting_function_control_ = @@ -102,6 +129,8 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator, ChooseBetweenMinAndMax(kChanceOfAdjustingMemoryOperandsMask); chance_of_adjusting_selection_control_ = ChooseBetweenMinAndMax(kChanceOfAdjustingSelectionControl); + chance_of_calling_function_ = + ChooseBetweenMinAndMax(kChanceOfCallingFunction); chance_of_choosing_struct_type_vs_array_type_ = ChooseBetweenMinAndMax(kChanceOfChoosingStructTypeVsArrayType); chance_of_constructing_composite_ = @@ -109,6 +138,8 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator, chance_of_copying_object_ = ChooseBetweenMinAndMax(kChanceOfCopyingObject); chance_of_donating_additional_module_ = ChooseBetweenMinAndMax(kChanceOfDonatingAdditionalModule); + chance_of_going_deeper_when_making_access_chain_ = + ChooseBetweenMinAndMax(kChanceOfGoingDeeperWhenMakingAccessChain); chance_of_making_donor_livesafe_ = ChooseBetweenMinAndMax(kChanceOfMakingDonorLivesafe); chance_of_merging_blocks_ = ChooseBetweenMinAndMax(kChanceOfMergingBlocks); @@ -118,13 +149,13 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator, ChooseBetweenMinAndMax(kChanceOfObfuscatingConstant); chance_of_outlining_function_ = ChooseBetweenMinAndMax(kChanceOfOutliningFunction); + chance_of_permuting_parameters_ = + ChooseBetweenMinAndMax(kChanceOfPermutingParameters); chance_of_replacing_id_with_synonym_ = ChooseBetweenMinAndMax(kChanceOfReplacingIdWithSynonym); chance_of_splitting_block_ = ChooseBetweenMinAndMax(kChanceOfSplittingBlock); - max_loop_control_partial_count_ = kDefaultMaxLoopControlPartialCount; - max_loop_control_peel_count_ = kDefaultMaxLoopControlPeelCount; - max_loop_limit_ = kDefaultMaxLoopLimit; - max_new_array_size_limit_ = kDefaultMaxNewArraySizeLimit; + chance_of_toggling_access_chain_instruction_ = + ChooseBetweenMinAndMax(kChanceOfTogglingAccessChainInstruction); } FuzzerContext::~FuzzerContext() = default; diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_context.h b/3rdparty/spirv-tools/source/fuzz/fuzzer_context.h index d4d6d58fb..152970577 100644 --- a/3rdparty/spirv-tools/source/fuzz/fuzzer_context.h +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_context.h @@ -46,18 +46,65 @@ class FuzzerContext { // method, and which must be non-empty. Typically 'HasSizeMethod' will be an // std::vector. template - uint32_t RandomIndex(const HasSizeMethod& sequence) { + uint32_t RandomIndex(const HasSizeMethod& sequence) const { assert(sequence.size() > 0); return random_generator_->RandomUint32( static_cast(sequence.size())); } + // Selects a random index into |sequence|, removes the element at that index + // and returns it. + template + T RemoveAtRandomIndex(std::vector* sequence) const { + uint32_t index = RandomIndex(*sequence); + T result = sequence->at(index); + sequence->erase(sequence->begin() + index); + return result; + } + + // Randomly shuffles a |sequence| between |lo| and |hi| indices inclusively. + // |lo| and |hi| must be valid indices to the |sequence| + template + void Shuffle(std::vector* sequence, size_t lo, size_t hi) const { + auto& array = *sequence; + + if (array.empty()) { + return; + } + + assert(lo <= hi && hi < array.size() && "lo and/or hi indices are invalid"); + + // i > lo to account for potential infinite loop when lo == 0 + for (size_t i = hi; i > lo; --i) { + auto index = + random_generator_->RandomUint32(static_cast(i - lo + 1)); + + if (lo + index != i) { + // Introduce std::swap to the scope but don't use it + // directly since there might be a better overload + using std::swap; + swap(array[lo + index], array[i]); + } + } + } + + // Ramdomly shuffles a |sequence| + template + void Shuffle(std::vector* sequence) const { + if (!sequence->empty()) { + Shuffle(sequence, 0, sequence->size() - 1); + } + } + // Yields an id that is guaranteed not to be used in the module being fuzzed, // or to have been issued before. uint32_t GetFreshId(); // Probabilities associated with applying various transformations. // Keep them in alphabetical order. + uint32_t GetChanceOfAddingAccessChain() { + return chance_of_adding_access_chain_; + } uint32_t GetChanceOfAddingAnotherStructField() { return chance_of_adding_another_struct_field_; } @@ -69,12 +116,23 @@ class FuzzerContext { uint32_t GetChanceOfAddingDeadContinue() { return chance_of_adding_dead_continue_; } + uint32_t GetChanceOfAddingEquationInstruction() { + return chance_of_adding_equation_instruction_; + } + uint32_t GetChanceOfAddingGlobalVariable() { + return chance_of_adding_global_variable_; + } + uint32_t GetChanceOfAddingLoad() { return chance_of_adding_load_; } + uint32_t GetChanceOfAddingLocalVariable() { + return chance_of_adding_local_variable_; + } uint32_t GetChanceOfAddingMatrixType() { return chance_of_adding_matrix_type_; } uint32_t GetChanceOfAddingNoContractionDecoration() { return chance_of_adding_no_contraction_decoration_; } + uint32_t GetChanceOfAddingStore() { return chance_of_adding_store_; } uint32_t GetChanceOfAddingVectorType() { return chance_of_adding_vector_type_; } @@ -90,6 +148,7 @@ class FuzzerContext { uint32_t GetChanceOfAdjustingSelectionControl() { return chance_of_adjusting_selection_control_; } + uint32_t GetChanceOfCallingFunction() { return chance_of_calling_function_; } uint32_t GetChanceOfChoosingStructTypeVsArrayType() { return chance_of_choosing_struct_type_vs_array_type_; } @@ -100,6 +159,9 @@ class FuzzerContext { uint32_t GetChanceOfDonatingAdditionalModule() { return chance_of_donating_additional_module_; } + uint32_t GetChanceOfGoingDeeperWhenMakingAccessChain() { + return chance_of_going_deeper_when_making_access_chain_; + } uint32_t ChanceOfMakingDonorLivesafe() { return chance_of_making_donor_livesafe_; } @@ -111,10 +173,16 @@ class FuzzerContext { uint32_t GetChanceOfOutliningFunction() { return chance_of_outlining_function_; } + uint32_t GetChanceOfPermutingParameters() { + return chance_of_permuting_parameters_; + } uint32_t GetChanceOfReplacingIdWithSynonym() { return chance_of_replacing_id_with_synonym_; } uint32_t GetChanceOfSplittingBlock() { return chance_of_splitting_block_; } + uint32_t GetChanceOfTogglingAccessChainInstruction() { + return chance_of_toggling_access_chain_instruction_; + } uint32_t GetRandomLoopControlPeelCount() { return random_generator_->RandomUint32(max_loop_control_peel_count_); } @@ -129,8 +197,11 @@ class FuzzerContext { return random_generator_->RandomUint32(max_new_array_size_limit_ - 1) + 1; } - // Functions to control how deeply to recurse. - // Keep them in alphabetical order. + // Other functions to control transformations. Keep them in alphabetical + // order. + uint32_t GetRandomIndexForAccessChain(uint32_t composite_size_bound) { + return random_generator_->RandomUint32(composite_size_bound); + } bool GoDeeperInConstantObfuscation(uint32_t depth) { return go_deeper_in_constant_obfuscation_(depth, random_generator_); } @@ -143,29 +214,39 @@ class FuzzerContext { // Probabilities associated with applying various transformations. // Keep them in alphabetical order. + uint32_t chance_of_adding_access_chain_; uint32_t chance_of_adding_another_struct_field_; uint32_t chance_of_adding_array_or_struct_type_; uint32_t chance_of_adding_dead_block_; uint32_t chance_of_adding_dead_break_; uint32_t chance_of_adding_dead_continue_; + uint32_t chance_of_adding_equation_instruction_; + uint32_t chance_of_adding_global_variable_; + uint32_t chance_of_adding_load_; + uint32_t chance_of_adding_local_variable_; uint32_t chance_of_adding_matrix_type_; uint32_t chance_of_adding_no_contraction_decoration_; + uint32_t chance_of_adding_store_; uint32_t chance_of_adding_vector_type_; uint32_t chance_of_adjusting_function_control_; uint32_t chance_of_adjusting_loop_control_; uint32_t chance_of_adjusting_memory_operands_mask_; uint32_t chance_of_adjusting_selection_control_; + uint32_t chance_of_calling_function_; uint32_t chance_of_choosing_struct_type_vs_array_type_; uint32_t chance_of_constructing_composite_; uint32_t chance_of_copying_object_; uint32_t chance_of_donating_additional_module_; + uint32_t chance_of_going_deeper_when_making_access_chain_; uint32_t chance_of_making_donor_livesafe_; uint32_t chance_of_merging_blocks_; uint32_t chance_of_moving_block_down_; uint32_t chance_of_obfuscating_constant_; uint32_t chance_of_outlining_function_; + uint32_t chance_of_permuting_parameters_; uint32_t chance_of_replacing_id_with_synonym_; uint32_t chance_of_splitting_block_; + uint32_t chance_of_toggling_access_chain_instruction_; // Limits associated with various quantities for which random values are // chosen during fuzzing. diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass.cpp index 9964a6c3f..a76f10d3a 100644 --- a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass.cpp +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass.cpp @@ -14,11 +14,15 @@ #include "source/fuzz/fuzzer_pass.h" +#include "source/fuzz/fuzzer_util.h" #include "source/fuzz/instruction_descriptor.h" +#include "source/fuzz/transformation_add_constant_boolean.h" +#include "source/fuzz/transformation_add_constant_composite.h" #include "source/fuzz/transformation_add_constant_scalar.h" #include "source/fuzz/transformation_add_global_undef.h" #include "source/fuzz/transformation_add_type_boolean.h" #include "source/fuzz/transformation_add_type_float.h" +#include "source/fuzz/transformation_add_type_function.h" #include "source/fuzz/transformation_add_type_int.h" #include "source/fuzz/transformation_add_type_matrix.h" #include "source/fuzz/transformation_add_type_pointer.h" @@ -38,10 +42,10 @@ FuzzerPass::FuzzerPass(opt::IRContext* ir_context, FactManager* fact_manager, FuzzerPass::~FuzzerPass() = default; std::vector FuzzerPass::FindAvailableInstructions( - const opt::Function& function, opt::BasicBlock* block, - opt::BasicBlock::iterator inst_it, + opt::Function* function, opt::BasicBlock* block, + const opt::BasicBlock::iterator& inst_it, std::function - instruction_is_relevant) { + instruction_is_relevant) const { // TODO(afd) The following is (relatively) simple, but may end up being // prohibitively inefficient, as it walks the whole dominator tree for // every instruction that is considered. @@ -54,6 +58,14 @@ std::vector FuzzerPass::FindAvailableInstructions( } } + // Consider all function parameters + function->ForEachParam( + [this, &instruction_is_relevant, &result](opt::Instruction* param) { + if (instruction_is_relevant(GetIRContext(), param)) { + result.push_back(param); + } + }); + // Consider all previous instructions in this block for (auto prev_inst_it = block->begin(); prev_inst_it != inst_it; ++prev_inst_it) { @@ -64,7 +76,7 @@ std::vector FuzzerPass::FindAvailableInstructions( // Walk the dominator tree to consider all instructions from dominating // blocks - auto dominator_analysis = GetIRContext()->GetDominatorAnalysis(&function); + auto dominator_analysis = GetIRContext()->GetDominatorAnalysis(function); for (auto next_dominator = dominator_analysis->ImmediateDominator(block); next_dominator != nullptr; next_dominator = @@ -78,12 +90,12 @@ std::vector FuzzerPass::FindAvailableInstructions( return result; } -void FuzzerPass::MaybeAddTransformationBeforeEachInstruction( +void FuzzerPass::ForEachInstructionWithInstructionDescriptor( std::function< - void(const opt::Function& function, opt::BasicBlock* block, + void(opt::Function* function, opt::BasicBlock* block, opt::BasicBlock::iterator inst_it, const protobufs::InstructionDescriptor& instruction_descriptor)> - maybe_apply_transformation) { + action) { // Consider every block in every function. for (auto& function : *GetIRContext()->module()) { for (auto& block : function) { @@ -121,11 +133,10 @@ void FuzzerPass::MaybeAddTransformationBeforeEachInstruction( const SpvOp opcode = inst_it->opcode(); // Invoke the provided function, which might apply a transformation. - maybe_apply_transformation( - function, &block, inst_it, - MakeInstructionDescriptor( - base, opcode, - skip_count.count(opcode) ? skip_count.at(opcode) : 0)); + action(&function, &block, inst_it, + MakeInstructionDescriptor( + base, opcode, + skip_count.count(opcode) ? skip_count.at(opcode) : 0)); if (!inst_it->HasResultId()) { skip_count[opcode] = @@ -169,6 +180,26 @@ uint32_t FuzzerPass::FindOrCreate32BitFloatType() { return result; } +uint32_t FuzzerPass::FindOrCreateFunctionType( + uint32_t return_type_id, const std::vector& argument_id) { + // FindFunctionType has a sigle argument for OpTypeFunction operands + // so we will have to copy them all in this vector + std::vector type_ids(argument_id.size() + 1); + type_ids[0] = return_type_id; + std::copy(argument_id.begin(), argument_id.end(), type_ids.begin() + 1); + + // Check if type exists + auto existing_id = fuzzerutil::FindFunctionType(GetIRContext(), type_ids); + if (existing_id) { + return existing_id; + } + + auto result = GetFuzzerContext()->GetFreshId(); + ApplyTransformation( + TransformationAddTypeFunction(result, return_type_id, argument_id)); + return result; +} + uint32_t FuzzerPass::FindOrCreateVectorType(uint32_t component_type_id, uint32_t component_count) { assert(component_count >= 2 && component_count <= 4 && @@ -208,21 +239,27 @@ uint32_t FuzzerPass::FindOrCreateMatrixType(uint32_t column_count, return result; } -uint32_t FuzzerPass::FindOrCreatePointerTo32BitIntegerType( - bool is_signed, SpvStorageClass storage_class) { - auto uint32_type_id = FindOrCreate32BitIntegerType(is_signed); - opt::analysis::Pointer pointer_type( - GetIRContext()->get_type_mgr()->GetType(uint32_type_id), storage_class); - auto existing_id = GetIRContext()->get_type_mgr()->GetId(&pointer_type); +uint32_t FuzzerPass::FindOrCreatePointerType(uint32_t base_type_id, + SpvStorageClass storage_class) { + // We do not use the type manager here, due to problems related to isomorphic + // but distinct structs not being regarded as different. + auto existing_id = fuzzerutil::MaybeGetPointerType( + GetIRContext(), base_type_id, storage_class); if (existing_id) { return existing_id; } auto result = GetFuzzerContext()->GetFreshId(); ApplyTransformation( - TransformationAddTypePointer(result, storage_class, uint32_type_id)); + TransformationAddTypePointer(result, storage_class, base_type_id)); return result; } +uint32_t FuzzerPass::FindOrCreatePointerTo32BitIntegerType( + bool is_signed, SpvStorageClass storage_class) { + return FindOrCreatePointerType(FindOrCreate32BitIntegerType(is_signed), + storage_class); +} + uint32_t FuzzerPass::FindOrCreate32BitIntegerConstant(uint32_t word, bool is_signed) { auto uint32_type_id = FindOrCreate32BitIntegerType(is_signed); @@ -243,6 +280,42 @@ uint32_t FuzzerPass::FindOrCreate32BitIntegerConstant(uint32_t word, return result; } +uint32_t FuzzerPass::FindOrCreate32BitFloatConstant(uint32_t word) { + auto float_type_id = FindOrCreate32BitFloatType(); + opt::analysis::FloatConstant float_constant( + GetIRContext()->get_type_mgr()->GetType(float_type_id)->AsFloat(), + {word}); + auto existing_constant = + GetIRContext()->get_constant_mgr()->FindConstant(&float_constant); + if (existing_constant) { + return GetIRContext() + ->get_constant_mgr() + ->GetDefiningInstruction(existing_constant) + ->result_id(); + } + auto result = GetFuzzerContext()->GetFreshId(); + ApplyTransformation( + TransformationAddConstantScalar(result, float_type_id, {word})); + return result; +} + +uint32_t FuzzerPass::FindOrCreateBoolConstant(bool value) { + auto bool_type_id = FindOrCreateBoolType(); + opt::analysis::BoolConstant bool_constant( + GetIRContext()->get_type_mgr()->GetType(bool_type_id)->AsBool(), value); + auto existing_constant = + GetIRContext()->get_constant_mgr()->FindConstant(&bool_constant); + if (existing_constant) { + return GetIRContext() + ->get_constant_mgr() + ->GetDefiningInstruction(existing_constant) + ->result_id(); + } + auto result = GetFuzzerContext()->GetFreshId(); + ApplyTransformation(TransformationAddConstantBoolean(result, value)); + return result; +} + uint32_t FuzzerPass::FindOrCreateGlobalUndef(uint32_t type_id) { for (auto& inst : GetIRContext()->types_values()) { if (inst.opcode() == SpvOpUndef && inst.type_id() == type_id) { @@ -254,5 +327,147 @@ uint32_t FuzzerPass::FindOrCreateGlobalUndef(uint32_t type_id) { return result; } +std::pair, std::map>> +FuzzerPass::GetAvailableBaseTypesAndPointers( + SpvStorageClass storage_class) const { + // Records all of the base types available in the module. + std::vector base_types; + + // For each base type, records all the associated pointer types that target + // that base type and that have |storage_class| as their storage class. + std::map> base_type_to_pointers; + + for (auto& inst : GetIRContext()->types_values()) { + switch (inst.opcode()) { + case SpvOpTypeArray: + case SpvOpTypeBool: + case SpvOpTypeFloat: + case SpvOpTypeInt: + case SpvOpTypeMatrix: + case SpvOpTypeStruct: + case SpvOpTypeVector: + // These types are suitable as pointer base types. Record the type, + // and the fact that we cannot yet have seen any pointers that use this + // as its base type. + base_types.push_back(inst.result_id()); + base_type_to_pointers.insert({inst.result_id(), {}}); + break; + case SpvOpTypePointer: + if (inst.GetSingleWordInOperand(0) == storage_class) { + // The pointer has the desired storage class, so we are interested in + // it. Associate it with its base type. + base_type_to_pointers.at(inst.GetSingleWordInOperand(1)) + .push_back(inst.result_id()); + } + break; + default: + break; + } + } + return {base_types, base_type_to_pointers}; +} + +uint32_t FuzzerPass::FindOrCreateZeroConstant( + uint32_t scalar_or_composite_type_id) { + auto type_instruction = + GetIRContext()->get_def_use_mgr()->GetDef(scalar_or_composite_type_id); + assert(type_instruction && "The type instruction must exist."); + switch (type_instruction->opcode()) { + case SpvOpTypeBool: + return FindOrCreateBoolConstant(false); + case SpvOpTypeFloat: + return FindOrCreate32BitFloatConstant(0); + case SpvOpTypeInt: + return FindOrCreate32BitIntegerConstant( + 0, type_instruction->GetSingleWordInOperand(1) != 0); + case SpvOpTypeArray: { + return GetZeroConstantForHomogeneousComposite( + *type_instruction, type_instruction->GetSingleWordInOperand(0), + fuzzerutil::GetArraySize(*type_instruction, GetIRContext())); + } + case SpvOpTypeMatrix: + case SpvOpTypeVector: { + return GetZeroConstantForHomogeneousComposite( + *type_instruction, type_instruction->GetSingleWordInOperand(0), + type_instruction->GetSingleWordInOperand(1)); + } + case SpvOpTypeStruct: { + std::vector field_zero_constants; + std::vector field_zero_ids; + for (uint32_t index = 0; index < type_instruction->NumInOperands(); + index++) { + uint32_t field_constant_id = FindOrCreateZeroConstant( + type_instruction->GetSingleWordInOperand(index)); + field_zero_ids.push_back(field_constant_id); + field_zero_constants.push_back( + GetIRContext()->get_constant_mgr()->FindDeclaredConstant( + field_constant_id)); + } + return FindOrCreateCompositeConstant( + *type_instruction, field_zero_constants, field_zero_ids); + } + default: + assert(false && "Unknown type."); + return 0; + } +} + +uint32_t FuzzerPass::FindOrCreateCompositeConstant( + const opt::Instruction& composite_type_instruction, + const std::vector& constants, + const std::vector& constant_ids) { + assert(constants.size() == constant_ids.size() && + "Precondition: |constants| and |constant_ids| must be in " + "correspondence."); + + opt::analysis::Type* composite_type = GetIRContext()->get_type_mgr()->GetType( + composite_type_instruction.result_id()); + std::unique_ptr composite_constant; + if (composite_type->AsArray()) { + composite_constant = MakeUnique( + composite_type->AsArray(), constants); + } else if (composite_type->AsMatrix()) { + composite_constant = MakeUnique( + composite_type->AsMatrix(), constants); + } else if (composite_type->AsStruct()) { + composite_constant = MakeUnique( + composite_type->AsStruct(), constants); + } else if (composite_type->AsVector()) { + composite_constant = MakeUnique( + composite_type->AsVector(), constants); + } else { + assert(false && + "Precondition: |composite_type| must declare a composite type."); + return 0; + } + + uint32_t existing_constant = + GetIRContext()->get_constant_mgr()->FindDeclaredConstant( + composite_constant.get(), composite_type_instruction.result_id()); + if (existing_constant) { + return existing_constant; + } + uint32_t result = GetFuzzerContext()->GetFreshId(); + ApplyTransformation(TransformationAddConstantComposite( + result, composite_type_instruction.result_id(), constant_ids)); + return result; +} + +uint32_t FuzzerPass::GetZeroConstantForHomogeneousComposite( + const opt::Instruction& composite_type_instruction, + uint32_t component_type_id, uint32_t num_components) { + std::vector zero_constants; + std::vector zero_ids; + uint32_t zero_component = FindOrCreateZeroConstant(component_type_id); + const opt::analysis::Constant* registered_zero_component = + GetIRContext()->get_constant_mgr()->FindDeclaredConstant(zero_component); + for (uint32_t i = 0; i < num_components; i++) { + zero_constants.push_back(registered_zero_component); + zero_ids.push_back(zero_component); + } + return FindOrCreateCompositeConstant(composite_type_instruction, + zero_constants, zero_ids); +} + } // namespace fuzz } // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass.h b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass.h index e1e8aec25..46ee4080a 100644 --- a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass.h +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass.h @@ -61,33 +61,33 @@ class FuzzerPass { // |instruction_is_relevant| predicate. This, for instance, could ignore all // instructions that have a particular decoration. std::vector FindAvailableInstructions( - const opt::Function& function, opt::BasicBlock* block, - opt::BasicBlock::iterator inst_it, + opt::Function* function, opt::BasicBlock* block, + const opt::BasicBlock::iterator& inst_it, std::function - instruction_is_relevant); + instruction_is_relevant) const; // A helper method that iterates through each instruction in each block, at // all times tracking an instruction descriptor that allows the latest // instruction to be located even if it has no result id. // - // The code to manipulate the instruction descriptor is a bit fiddly, and the + // The code to manipulate the instruction descriptor is a bit fiddly. The // point of this method is to avoiding having to duplicate it in multiple // transformation passes. // - // The function |maybe_apply_transformation| is invoked for each instruction - // |inst_it| in block |block| of function |function| that is encountered. The + // The function |action| is invoked for each instruction |inst_it| in block + // |block| of function |function| that is encountered. The // |instruction_descriptor| parameter to the function object allows |inst_it| // to be identified. // - // The job of |maybe_apply_transformation| is to randomly decide whether to - // try to apply some transformation, and then - if selected - to attempt to - // apply it. - void MaybeAddTransformationBeforeEachInstruction( + // In most intended use cases, the job of |action| is to randomly decide + // whether to try to apply some transformation, and then - if selected - to + // attempt to apply it. + void ForEachInstructionWithInstructionDescriptor( std::function< - void(const opt::Function& function, opt::BasicBlock* block, + void(opt::Function* function, opt::BasicBlock* block, opt::BasicBlock::iterator inst_it, const protobufs::InstructionDescriptor& instruction_descriptor)> - maybe_apply_transformation); + action); // A generic helper for applying a transformation that should be applicable // by construction, and adding it to the sequence of applied transformations. @@ -112,6 +112,12 @@ class FuzzerPass { // instruction does not exist, a transformation is applied to add it. uint32_t FindOrCreate32BitFloatType(); + // Returns the id of an OpTypeFunction % %<...argument_id> + // instruction. If such an instruction doesn't exist, a transformation + // is applied to create a new one. + uint32_t FindOrCreateFunctionType(uint32_t return_type_id, + const std::vector& argument_id); + // Returns the id of an OpTypeVector instruction, with |component_type_id| // (which must already exist) as its base type, and |component_count| // elements (which must be in the range [2, 4]). If such an instruction does @@ -125,6 +131,12 @@ class FuzzerPass { // type itself do not exist, transformations are applied to add them. uint32_t FindOrCreateMatrixType(uint32_t column_count, uint32_t row_count); + // Returns the id of a pointer type with base type |base_type_id| (which must + // already exist) and storage class |storage_class|. A transformation is + // applied to add the pointer if it does not already exist. + uint32_t FindOrCreatePointerType(uint32_t base_type_id, + SpvStorageClass storage_class); + // Returns the id of an OpTypePointer instruction, with a 32-bit integer base // type of signedness specified by |is_signed|. If the pointer type or // required integer base type do not exist, transformations are applied to add @@ -138,12 +150,85 @@ class FuzzerPass { // applied to add them. uint32_t FindOrCreate32BitIntegerConstant(uint32_t word, bool is_signed); + // Returns the id of an OpConstant instruction, with 32-bit floating-point + // type, with |word| as its value. If either the required floating-point type + // or the constant do not exist, transformations are applied to add them. + uint32_t FindOrCreate32BitFloatConstant(uint32_t word); + + // Returns the id of an OpConstantTrue or OpConstantFalse instruction, + // according to |value|. If either the required instruction or the bool + // type do not exist, transformations are applied to add them. + uint32_t FindOrCreateBoolConstant(bool value); + // Returns the result id of an instruction of the form: // %id = OpUndef %|type_id| // If no such instruction exists, a transformation is applied to add it. uint32_t FindOrCreateGlobalUndef(uint32_t type_id); + // Yields a pair, (base_type_ids, base_type_ids_to_pointers), such that: + // - base_type_ids captures every scalar or composite type declared in the + // module (i.e., all int, bool, float, vector, matrix, struct and array + // types + // - base_type_ids_to_pointers maps every such base type to the sequence + // of all pointer types that have storage class |storage_class| and the + // given base type as their pointee type. The sequence may be empty for + // some base types if no pointers to those types are defined for the given + // storage class, and the sequence will have multiple elements if there are + // repeated pointer declarations for the same base type and storage class. + std::pair, std::map>> + GetAvailableBaseTypesAndPointers(SpvStorageClass storage_class) const; + + // Given a type id, |scalar_or_composite_type_id|, which must correspond to + // some scalar or composite type, returns the result id of an instruction + // defining a constant of the given type that is zero or false at everywhere. + // If such an instruction does not yet exist, transformations are applied to + // add it. + // + // Examples: + // --------------+------------------------------- + // TYPE | RESULT is id corresponding to + // --------------+------------------------------- + // bool | false + // --------------+------------------------------- + // bvec4 | (false, false, false, false) + // --------------+------------------------------- + // float | 0.0 + // --------------+------------------------------- + // vec2 | (0.0, 0.0) + // --------------+------------------------------- + // int[3] | [0, 0, 0] + // --------------+------------------------------- + // struct S { | + // int i; | S(0, false, (0u, 0u)) + // bool b; | + // uint2 u; | + // } | + // --------------+------------------------------- + uint32_t FindOrCreateZeroConstant(uint32_t scalar_or_composite_type_id); + private: + // Array, matrix and vector are *homogeneous* composite types in the sense + // that every component of one of these types has the same type. Given a + // homogeneous composite type instruction, |composite_type_instruction|, + // returns the id of a composite constant instruction for which every element + // is zero/false. If such an instruction does not yet exist, transformations + // are applied to add it. + uint32_t GetZeroConstantForHomogeneousComposite( + const opt::Instruction& composite_type_instruction, + uint32_t component_type_id, uint32_t num_components); + + // Helper to find an existing composite constant instruction of the given + // composite type with the given constant components, or to apply + // transformations to create such an instruction if it does not yet exist. + // Parameter |composite_type_instruction| must be a composite type + // instruction. The parameters |constants| and |constant_ids| must have the + // same size, and it must be the case that for each i, |constant_ids[i]| is + // the result id of an instruction that defines |constants[i]|. + uint32_t FindOrCreateCompositeConstant( + const opt::Instruction& composite_type_instruction, + const std::vector& constants, + const std::vector& constant_ids); + opt::IRContext* ir_context_; FactManager* fact_manager_; FuzzerContext* fuzzer_context_; diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_access_chains.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_access_chains.cpp new file mode 100644 index 000000000..cfc2812b4 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_access_chains.cpp @@ -0,0 +1,169 @@ +// 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_access_chains.h" + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/transformation_access_chain.h" + +namespace spvtools { +namespace fuzz { + +FuzzerPassAddAccessChains::FuzzerPassAddAccessChains( + opt::IRContext* ir_context, FactManager* fact_manager, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations) + : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {} + +FuzzerPassAddAccessChains::~FuzzerPassAddAccessChains() = default; + +void FuzzerPassAddAccessChains::Apply() { + ForEachInstructionWithInstructionDescriptor( + [this](opt::Function* function, opt::BasicBlock* block, + opt::BasicBlock::iterator inst_it, + const protobufs::InstructionDescriptor& instruction_descriptor) + -> void { + assert(inst_it->opcode() == + instruction_descriptor.target_instruction_opcode() && + "The opcode of the instruction we might insert before must be " + "the same as the opcode in the descriptor for the instruction"); + + // Check whether it is legitimate to insert an access chain + // instruction before this instruction. + if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpAccessChain, + inst_it)) { + return; + } + + // Randomly decide whether to try inserting a load here. + if (!GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext()->GetChanceOfAddingAccessChain())) { + return; + } + + // Get all of the pointers that are currently in scope, excluding + // explicitly null and undefined pointers. + std::vector relevant_pointer_instructions = + FindAvailableInstructions( + function, block, inst_it, + [](opt::IRContext* context, + opt::Instruction* instruction) -> bool { + if (!instruction->result_id() || !instruction->type_id()) { + // A pointer needs both a result and type id. + return false; + } + switch (instruction->opcode()) { + case SpvOpConstantNull: + case SpvOpUndef: + // Do not allow making an access chain from a null or + // undefined pointer. (We can eliminate these cases + // before actually checking that the instruction is a + // pointer.) + return false; + default: + break; + } + // If the instruction has pointer type, we can legitimately + // make an access chain from it. + return context->get_def_use_mgr() + ->GetDef(instruction->type_id()) + ->opcode() == SpvOpTypePointer; + }); + + // At this point, |relevant_instructions| contains all the pointers + // we might think of making an access chain from. + if (relevant_pointer_instructions.empty()) { + return; + } + + auto chosen_pointer = + relevant_pointer_instructions[GetFuzzerContext()->RandomIndex( + relevant_pointer_instructions)]; + std::vector index_ids; + auto pointer_type = GetIRContext()->get_def_use_mgr()->GetDef( + chosen_pointer->type_id()); + uint32_t subobject_type_id = pointer_type->GetSingleWordInOperand(1); + while (true) { + auto subobject_type = + GetIRContext()->get_def_use_mgr()->GetDef(subobject_type_id); + if (!spvOpcodeIsComposite(subobject_type->opcode())) { + break; + } + if (!GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext() + ->GetChanceOfGoingDeeperWhenMakingAccessChain())) { + break; + } + uint32_t bound; + switch (subobject_type->opcode()) { + case SpvOpTypeArray: + bound = fuzzerutil::GetArraySize(*subobject_type, GetIRContext()); + break; + case SpvOpTypeMatrix: + case SpvOpTypeVector: + bound = subobject_type->GetSingleWordInOperand(1); + break; + case SpvOpTypeStruct: + bound = fuzzerutil::GetNumberOfStructMembers(*subobject_type); + break; + default: + assert(false && "Not a composite type opcode."); + // Set the bound to a value in order to keep release compilers + // happy. + bound = 0; + break; + } + if (bound == 0) { + // It is possible for a composite type to legitimately have zero + // sub-components, at least in the case of a struct, which + // can have no fields. + break; + } + + // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3179) We + // could allow non-constant indices when looking up non-structs, + // using clamping to ensure they are in-bounds. + uint32_t index_value = + GetFuzzerContext()->GetRandomIndexForAccessChain(bound); + index_ids.push_back(FindOrCreate32BitIntegerConstant( + index_value, GetFuzzerContext()->ChooseEven())); + switch (subobject_type->opcode()) { + case SpvOpTypeArray: + case SpvOpTypeMatrix: + case SpvOpTypeVector: + subobject_type_id = subobject_type->GetSingleWordInOperand(0); + break; + case SpvOpTypeStruct: + subobject_type_id = + subobject_type->GetSingleWordInOperand(index_value); + break; + default: + assert(false && "Not a composite type opcode."); + } + } + // The transformation we are about to create will only apply if a + // pointer suitable for the access chain's result type exists, so we + // create one if it does not. + FindOrCreatePointerType(subobject_type_id, + static_cast( + pointer_type->GetSingleWordInOperand(0))); + // Apply the transformation to add an access chain. + ApplyTransformation(TransformationAccessChain( + GetFuzzerContext()->GetFreshId(), chosen_pointer->result_id(), + index_ids, instruction_descriptor)); + }); +} + +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_access_chains.h b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_access_chains.h new file mode 100644 index 000000000..7e8ed6129 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_access_chains.h @@ -0,0 +1,41 @@ +// 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_ACCESS_CHAINS_H_ +#define SOURCE_FUZZ_FUZZER_PASS_ADD_ACCESS_CHAINS_H_ + +#include "source/fuzz/fuzzer_pass.h" + +namespace spvtools { +namespace fuzz { + +// Fuzzer pass that randomly adds access chains based on pointers available in +// the module. Other passes can use these access chains, e.g. by loading from +// them. +class FuzzerPassAddAccessChains : public FuzzerPass { + public: + FuzzerPassAddAccessChains(opt::IRContext* ir_context, + FactManager* fact_manager, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations); + + ~FuzzerPassAddAccessChains(); + + void Apply() override; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_FUZZER_PASS_ADD_ACCESS_CHAINS_H_ diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_dead_breaks.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_dead_breaks.cpp index fa6b09884..aefc2fcd7 100644 --- a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_dead_breaks.cpp +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_dead_breaks.cpp @@ -13,7 +13,7 @@ // limitations under the License. #include "source/fuzz/fuzzer_pass_add_dead_breaks.h" - +#include "source/fuzz/fuzzer_util.h" #include "source/fuzz/transformation_add_dead_break.h" #include "source/opt/ir_context.h" @@ -34,11 +34,16 @@ void FuzzerPassAddDeadBreaks::Apply() { // We consider each function separately. for (auto& function : *GetIRContext()->module()) { // For a given function, we find all the merge blocks in that function. - std::vector merge_block_ids; + std::vector merge_blocks; for (auto& block : function) { auto maybe_merge_id = block.MergeBlockIdIfAny(); if (maybe_merge_id) { - merge_block_ids.push_back(maybe_merge_id); + auto merge_block = + fuzzerutil::MaybeFindBlock(GetIRContext(), maybe_merge_id); + + assert(merge_block && "Merge block can't be null"); + + merge_blocks.push_back(merge_block); } } // We rather aggressively consider the possibility of adding a break from @@ -46,12 +51,34 @@ void FuzzerPassAddDeadBreaks::Apply() { // inapplicable as they would be illegal. That's OK - we later discard the // ones that turn out to be no good. for (auto& block : function) { - for (auto merge_block_id : merge_block_ids) { - // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2856): right - // now we completely ignore OpPhi instructions at merge blocks. This - // will lead to interesting opportunities being missed. + for (auto* merge_block : merge_blocks) { + // Populate this vector with ids that are available at the branch point + // of this basic block. We will use these ids to update OpPhi + // instructions later. + std::vector phi_ids; + + // Determine how we need to adjust OpPhi instructions' operands + // for this transformation to be valid. + // + // If |block| has a branch to |merge_block|, the latter must have all of + // its OpPhi instructions set up correctly - we don't need to adjust + // anything. + if (!block.IsSuccessor(merge_block)) { + merge_block->ForEachPhiInst([this, &phi_ids](opt::Instruction* phi) { + // Add an additional operand for OpPhi instruction. + // + // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3177): + // If we have a way to communicate to the fact manager + // that a specific id use is irrelevant and could be replaced with + // something else, we should add such a fact about the zero + // provided as an OpPhi operand + phi_ids.push_back(FindOrCreateZeroConstant(phi->type_id())); + }); + } + auto candidate_transformation = TransformationAddDeadBreak( - block.id(), merge_block_id, GetFuzzerContext()->ChooseEven(), {}); + block.id(), merge_block->id(), GetFuzzerContext()->ChooseEven(), + std::move(phi_ids)); if (candidate_transformation.IsApplicable(GetIRContext(), *GetFactManager())) { // Only consider a transformation as a candidate if it is applicable. diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_dead_continues.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_dead_continues.cpp index 51bcb91eb..852df3de6 100644 --- a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_dead_continues.cpp +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_dead_continues.cpp @@ -13,7 +13,7 @@ // limitations under the License. #include "source/fuzz/fuzzer_pass_add_dead_continues.h" - +#include "source/fuzz/fuzzer_util.h" #include "source/fuzz/transformation_add_dead_continue.h" #include "source/opt/ir_context.h" @@ -32,15 +32,46 @@ void FuzzerPassAddDeadContinues::Apply() { // Consider every block in every function. for (auto& function : *GetIRContext()->module()) { for (auto& block : function) { + // Get the label id of the continue target of the innermost loop. + auto continue_block_id = + block.IsLoopHeader() + ? block.ContinueBlockId() + : GetIRContext()->GetStructuredCFGAnalysis()->LoopContinueBlock( + block.id()); + + // This transformation is not applicable if current block is not inside a + // loop. + if (continue_block_id == 0) { + continue; + } + + auto* continue_block = + fuzzerutil::MaybeFindBlock(GetIRContext(), continue_block_id); + assert(continue_block && "Continue block is null"); + + // Analyze return type of each OpPhi instruction in the continue target + // and provide an id for the transformation if needed. + std::vector phi_ids; + // Check whether current block has an edge to the continue target. + // If this is the case, we don't need to do anything. + if (!block.IsSuccessor(continue_block)) { + continue_block->ForEachPhiInst([this, &phi_ids](opt::Instruction* phi) { + // Add an additional operand for OpPhi instruction. + // + // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3177): + // If we have a way to communicate to the fact manager + // that a specific id use is irrelevant and could be replaced with + // something else, we should add such a fact about the zero + // provided as an OpPhi operand + phi_ids.push_back(FindOrCreateZeroConstant(phi->type_id())); + }); + } + // Make a transformation to add a dead continue from this node; if the // node turns out to be inappropriate (e.g. by not being in a loop) the // precondition for the transformation will fail and it will be ignored. - // - // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2856): right - // now we completely ignore OpPhi instructions at continue targets. - // This will lead to interesting opportunities being missed. auto candidate_transformation = TransformationAddDeadContinue( - block.id(), GetFuzzerContext()->ChooseEven(), {}); + block.id(), GetFuzzerContext()->ChooseEven(), std::move(phi_ids)); // Probabilistically decide whether to apply the transformation in the // case that it is applicable. if (candidate_transformation.IsApplicable(GetIRContext(), diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_equation_instructions.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_equation_instructions.cpp new file mode 100644 index 000000000..7f34344cc --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_equation_instructions.cpp @@ -0,0 +1,238 @@ +// 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_equation_instructions.h" + +#include + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/transformation_equation_instruction.h" + +namespace spvtools { +namespace fuzz { + +FuzzerPassAddEquationInstructions::FuzzerPassAddEquationInstructions( + opt::IRContext* ir_context, FactManager* fact_manager, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations) + : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {} + +FuzzerPassAddEquationInstructions::~FuzzerPassAddEquationInstructions() = + default; + +void FuzzerPassAddEquationInstructions::Apply() { + ForEachInstructionWithInstructionDescriptor( + [this](opt::Function* function, opt::BasicBlock* block, + opt::BasicBlock::iterator inst_it, + const protobufs::InstructionDescriptor& instruction_descriptor) { + if (!GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext()->GetChanceOfAddingEquationInstruction())) { + return; + } + + // Check that it is OK to add an equation instruction before the given + // instruction in principle - e.g. check that this does not lead to + // inserting before an OpVariable or OpPhi instruction. We use OpIAdd + // as an example opcode for this check, to be representative of *some* + // opcode that defines an equation, even though we may choose a + // different opcode below. + if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpIAdd, inst_it)) { + return; + } + + // Get all available instructions with result ids and types that are not + // OpUndef. + std::vector available_instructions = + FindAvailableInstructions( + function, block, inst_it, + [](opt::IRContext*, opt::Instruction* instruction) -> bool { + return instruction->result_id() && instruction->type_id() && + instruction->opcode() != SpvOpUndef; + }); + + // Try the opcodes for which we know how to make ids at random until + // something works. + std::vector candidate_opcodes = {SpvOpIAdd, SpvOpISub, + SpvOpLogicalNot, SpvOpSNegate}; + do { + auto opcode = + GetFuzzerContext()->RemoveAtRandomIndex(&candidate_opcodes); + switch (opcode) { + case SpvOpIAdd: + case SpvOpISub: { + // Instructions of integer (scalar or vector) result type are + // suitable for these opcodes. + auto integer_instructions = + GetIntegerInstructions(available_instructions); + if (!integer_instructions.empty()) { + // There is at least one such instruction, so pick one at random + // for the LHS of an equation. + auto lhs = integer_instructions.at( + GetFuzzerContext()->RandomIndex(integer_instructions)); + + // For the RHS, we can use any instruction with an integer + // scalar/vector result type of the same number of components + // and the same bit-width for the underlying integer type. + + // Work out the element count and bit-width. + auto lhs_type = + GetIRContext()->get_type_mgr()->GetType(lhs->type_id()); + uint32_t lhs_element_count; + uint32_t lhs_bit_width; + if (lhs_type->AsVector()) { + lhs_element_count = lhs_type->AsVector()->element_count(); + lhs_bit_width = lhs_type->AsVector() + ->element_type() + ->AsInteger() + ->width(); + } else { + lhs_element_count = 1; + lhs_bit_width = lhs_type->AsInteger()->width(); + } + + // Get all the instructions that match on element count and + // bit-width. + auto candidate_rhs_instructions = RestrictToElementBitWidth( + RestrictToVectorWidth(integer_instructions, + lhs_element_count), + lhs_bit_width); + + // Choose a RHS instruction at random; there is guaranteed to + // be at least one choice as the LHS will be available. + auto rhs = candidate_rhs_instructions.at( + GetFuzzerContext()->RandomIndex( + candidate_rhs_instructions)); + + // Add the equation instruction. + ApplyTransformation(TransformationEquationInstruction( + GetFuzzerContext()->GetFreshId(), opcode, + {lhs->result_id(), rhs->result_id()}, + instruction_descriptor)); + return; + } + break; + } + case SpvOpLogicalNot: { + // Choose any available instruction of boolean scalar/vector + // result type and equate its negation with a fresh id. + auto boolean_instructions = + GetBooleanInstructions(available_instructions); + if (!boolean_instructions.empty()) { + ApplyTransformation(TransformationEquationInstruction( + GetFuzzerContext()->GetFreshId(), opcode, + {boolean_instructions + .at(GetFuzzerContext()->RandomIndex( + boolean_instructions)) + ->result_id()}, + instruction_descriptor)); + return; + } + break; + } + case SpvOpSNegate: { + // Similar to OpLogicalNot, but for signed integer negation. + auto integer_instructions = + GetIntegerInstructions(available_instructions); + if (!integer_instructions.empty()) { + ApplyTransformation(TransformationEquationInstruction( + GetFuzzerContext()->GetFreshId(), opcode, + {integer_instructions + .at(GetFuzzerContext()->RandomIndex( + integer_instructions)) + ->result_id()}, + instruction_descriptor)); + return; + } + break; + } + default: + assert(false && "Unexpected opcode."); + break; + } + } while (!candidate_opcodes.empty()); + // Reaching here means that we did not manage to apply any + // transformation at this point of the module. + }); +} + +std::vector +FuzzerPassAddEquationInstructions::GetIntegerInstructions( + const std::vector& instructions) const { + std::vector result; + for (auto& inst : instructions) { + auto type = GetIRContext()->get_type_mgr()->GetType(inst->type_id()); + if (type->AsInteger() || + (type->AsVector() && type->AsVector()->element_type()->AsInteger())) { + result.push_back(inst); + } + } + return result; +} + +std::vector +FuzzerPassAddEquationInstructions::GetBooleanInstructions( + const std::vector& instructions) const { + std::vector result; + for (auto& inst : instructions) { + auto type = GetIRContext()->get_type_mgr()->GetType(inst->type_id()); + if (type->AsBool() || + (type->AsVector() && type->AsVector()->element_type()->AsBool())) { + result.push_back(inst); + } + } + return result; +} + +std::vector +FuzzerPassAddEquationInstructions::RestrictToVectorWidth( + const std::vector& instructions, + uint32_t vector_width) const { + std::vector result; + for (auto& inst : instructions) { + auto type = GetIRContext()->get_type_mgr()->GetType(inst->type_id()); + // Get the vector width of |inst|, which is 1 if |inst| is a scalar and is + // otherwise derived from its vector type. + uint32_t other_vector_width = + type->AsVector() ? type->AsVector()->element_count() : 1; + // Keep |inst| if the vector widths match. + if (vector_width == other_vector_width) { + result.push_back(inst); + } + } + return result; +} + +std::vector +FuzzerPassAddEquationInstructions::RestrictToElementBitWidth( + const std::vector& instructions, + uint32_t bit_width) const { + std::vector result; + for (auto& inst : instructions) { + const opt::analysis::Type* type = + GetIRContext()->get_type_mgr()->GetType(inst->type_id()); + if (type->AsVector()) { + type = type->AsVector()->element_type(); + } + assert(type->AsInteger() && + "Precondition: all input instructions must " + "have integer scalar or vector type."); + if (type->AsInteger()->width() == bit_width) { + result.push_back(inst); + } + } + return result; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_equation_instructions.h b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_equation_instructions.h new file mode 100644 index 000000000..84229c05c --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_equation_instructions.h @@ -0,0 +1,67 @@ +// 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_EQUATION_INSTRUCTIONS_H_ +#define SOURCE_FUZZ_FUZZER_PASS_ADD_EQUATION_INSTRUCTIONS_H_ + +#include + +#include "source/fuzz/fuzzer_pass.h" + +namespace spvtools { +namespace fuzz { + +// Fuzzer pass that sprinkles instructions through the module that define +// equations using various arithmetic and logical operators. +class FuzzerPassAddEquationInstructions : public FuzzerPass { + public: + FuzzerPassAddEquationInstructions( + opt::IRContext* ir_context, FactManager* fact_manager, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations); + + ~FuzzerPassAddEquationInstructions(); + + void Apply() override; + + private: + // Yields those instructions in |instructions| that have integer scalar or + // vector result type. + std::vector GetIntegerInstructions( + const std::vector& instructions) const; + + // Yields those instructions in |instructions| that have boolean scalar or + // vector result type. + std::vector GetBooleanInstructions( + const std::vector& instructions) const; + + // Requires that |instructions| are scalars or vectors of some type. Returns + // only those instructions whose width is |width|. If |width| is 1 this means + // the scalars. + std::vector RestrictToVectorWidth( + const std::vector& instructions, + uint32_t vector_width) const; + + // Requires that |instructions| are integer scalars or vectors. Returns only + // those instructions for which the bit-width of the underlying integer type + // is |bit_width|. + std::vector RestrictToElementBitWidth( + const std::vector& instructions, + uint32_t bit_width) const; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_FUZZER_PASS_ADD_EQUATION_INSTRUCTIONS_H_ diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_function_calls.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_function_calls.cpp new file mode 100644 index 000000000..545aa169f --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_function_calls.cpp @@ -0,0 +1,247 @@ +// 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_function_calls.h" + +#include "source/fuzz/call_graph.h" +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/transformation_add_global_variable.h" +#include "source/fuzz/transformation_add_local_variable.h" +#include "source/fuzz/transformation_function_call.h" + +namespace spvtools { +namespace fuzz { + +FuzzerPassAddFunctionCalls::FuzzerPassAddFunctionCalls( + opt::IRContext* ir_context, FactManager* fact_manager, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations) + : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {} + +FuzzerPassAddFunctionCalls::~FuzzerPassAddFunctionCalls() = default; + +void FuzzerPassAddFunctionCalls::Apply() { + ForEachInstructionWithInstructionDescriptor( + [this](opt::Function* function, opt::BasicBlock* block, + opt::BasicBlock::iterator inst_it, + const protobufs::InstructionDescriptor& instruction_descriptor) + -> void { + // Check whether it is legitimate to insert a function call before the + // instruction. + if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpFunctionCall, + inst_it)) { + return; + } + + // Randomly decide whether to try inserting a function call here. + if (!GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext()->GetChanceOfCallingFunction())) { + return; + } + + // Compute the module's call graph - we don't cache it since it may + // change each time we apply a transformation. If this proves to be + // a bottleneck the call graph data structure could be made updatable. + CallGraph call_graph(GetIRContext()); + + // Gather all the non-entry point functions different from this + // function. It is important to ignore entry points as a function + // cannot be an entry point and the target of an OpFunctionCall + // instruction. We ignore this function to avoid direct recursion. + std::vector candidate_functions; + for (auto& other_function : *GetIRContext()->module()) { + if (&other_function != function && + !fuzzerutil::FunctionIsEntryPoint(GetIRContext(), + other_function.result_id())) { + candidate_functions.push_back(&other_function); + } + } + + // Choose a function to call, at random, by considering candidate + // functions until a suitable one is found. + opt::Function* chosen_function = nullptr; + while (!candidate_functions.empty()) { + opt::Function* candidate_function = + GetFuzzerContext()->RemoveAtRandomIndex(&candidate_functions); + if (!GetFactManager()->BlockIsDead(block->id()) && + !GetFactManager()->FunctionIsLivesafe( + candidate_function->result_id())) { + // Unless in a dead block, only livesafe functions can be invoked + continue; + } + if (call_graph.GetIndirectCallees(candidate_function->result_id()) + .count(function->result_id())) { + // Calling this function could lead to indirect recursion + continue; + } + chosen_function = candidate_function; + break; + } + + if (!chosen_function) { + // No suitable function was found to call. (This can happen, for + // instance, if the current function is the only function in the + // module.) + return; + } + + ApplyTransformation(TransformationFunctionCall( + GetFuzzerContext()->GetFreshId(), chosen_function->result_id(), + ChooseFunctionCallArguments(*chosen_function, function, block, + inst_it), + instruction_descriptor)); + }); +} + +std::map> +FuzzerPassAddFunctionCalls::GetAvailableInstructionsSuitableForActualParameters( + opt::Function* function, opt::BasicBlock* block, + const opt::BasicBlock::iterator& inst_it) { + // Find all instructions in scope that could potentially be used as actual + // parameters. Weed out unsuitable pointer arguments immediately. + std::vector potentially_suitable_instructions = + FindAvailableInstructions( + function, block, inst_it, + [this, block](opt::IRContext* context, + opt::Instruction* inst) -> bool { + if (!inst->HasResultId() || !inst->type_id()) { + // An instruction needs a result id and type in order + // to be suitable as an actual parameter. + return false; + } + if (context->get_def_use_mgr()->GetDef(inst->type_id())->opcode() == + SpvOpTypePointer) { + switch (inst->opcode()) { + case SpvOpFunctionParameter: + case SpvOpVariable: + // Function parameters and variables are the only + // kinds of pointer that can be used as actual + // parameters. + break; + default: + return false; + } + if (!GetFactManager()->BlockIsDead(block->id()) && + !GetFactManager()->PointeeValueIsIrrelevant( + inst->result_id())) { + // We can only pass a pointer as an actual parameter + // if the pointee value for the pointer is irrelevant, + // or if the block from which we would make the + // function call is dead. + return false; + } + } + return true; + }); + + // Group all the instructions that are potentially viable as function actual + // parameters by their result types. + std::map> result; + for (auto inst : potentially_suitable_instructions) { + if (result.count(inst->type_id()) == 0) { + // This is the first instruction of this type we have seen, so populate + // the map with an entry. + result.insert({inst->type_id(), {}}); + } + // Add the instruction to the sequence of instructions already associated + // with this type. + result.at(inst->type_id()).push_back(inst); + } + return result; +} + +std::vector FuzzerPassAddFunctionCalls::ChooseFunctionCallArguments( + const opt::Function& callee, opt::Function* caller_function, + opt::BasicBlock* caller_block, + const opt::BasicBlock::iterator& caller_inst_it) { + auto type_to_available_instructions = + GetAvailableInstructionsSuitableForActualParameters( + caller_function, caller_block, caller_inst_it); + + opt::Instruction* function_type = GetIRContext()->get_def_use_mgr()->GetDef( + callee.DefInst().GetSingleWordInOperand(1)); + assert(function_type->opcode() == SpvOpTypeFunction && + "The function type does not have the expected opcode."); + std::vector result; + for (uint32_t arg_index = 1; arg_index < function_type->NumInOperands(); + arg_index++) { + auto arg_type_id = + GetIRContext() + ->get_def_use_mgr() + ->GetDef(function_type->GetSingleWordInOperand(arg_index)) + ->result_id(); + if (type_to_available_instructions.count(arg_type_id)) { + std::vector& candidate_arguments = + type_to_available_instructions.at(arg_type_id); + // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3177) The value + // selected here is arbitrary. We should consider adding this + // information as a fact so that the passed parameter could be + // transformed/changed. + result.push_back(candidate_arguments[GetFuzzerContext()->RandomIndex( + candidate_arguments)] + ->result_id()); + } else { + // We don't have a suitable id in scope to pass, so we must make + // something up. + auto type_instruction = + GetIRContext()->get_def_use_mgr()->GetDef(arg_type_id); + + if (type_instruction->opcode() == SpvOpTypePointer) { + // In the case of a pointer, we make a new variable, at function + // or global scope depending on the storage class of the + // pointer. + + // Get a fresh id for the new variable. + uint32_t fresh_variable_id = GetFuzzerContext()->GetFreshId(); + + // The id of this variable is what we pass as the parameter to + // the call. + result.push_back(fresh_variable_id); + + // Now bring the variable into existence. + if (type_instruction->GetSingleWordInOperand(0) == + SpvStorageClassFunction) { + // Add a new zero-initialized local variable to the current + // function, noting that its pointee value is irrelevant. + ApplyTransformation(TransformationAddLocalVariable( + fresh_variable_id, arg_type_id, caller_function->result_id(), + FindOrCreateZeroConstant( + type_instruction->GetSingleWordInOperand(1)), + true)); + } else { + assert(type_instruction->GetSingleWordInOperand(0) == + SpvStorageClassPrivate && + "Only Function and Private storage classes are " + "supported at present."); + // Add a new zero-initialized global variable to the module, + // noting that its pointee value is irrelevant. + ApplyTransformation(TransformationAddGlobalVariable( + fresh_variable_id, arg_type_id, + FindOrCreateZeroConstant( + type_instruction->GetSingleWordInOperand(1)), + true)); + } + } else { + // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3177): We use + // constant zero for the parameter, but could consider adding a fact + // to allow further passes to obfuscate it. + result.push_back(FindOrCreateZeroConstant(arg_type_id)); + } + } + } + return result; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_function_calls.h b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_function_calls.h new file mode 100644 index 000000000..5d184fd16 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_function_calls.h @@ -0,0 +1,58 @@ +// 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_FUNCTION_CALLS_H_ +#define SOURCE_FUZZ_FUZZER_PASS_ADD_FUNCTION_CALLS_H_ + +#include "source/fuzz/fuzzer_pass.h" + +namespace spvtools { +namespace fuzz { + +// Fuzzer pass that adds calls at random to (a) livesafe functions, from +// anywhere, and (b) any functions, from dead blocks. +class FuzzerPassAddFunctionCalls : public FuzzerPass { + public: + FuzzerPassAddFunctionCalls( + opt::IRContext* ir_context, FactManager* fact_manager, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations); + + ~FuzzerPassAddFunctionCalls(); + + void Apply() override; + + private: + // Identify all instructions available at |instr_it|, in block |block| of + // |function|, that are potentially suitable as function call actual + // parameters. The results are grouped by type. + std::map> + GetAvailableInstructionsSuitableForActualParameters( + opt::Function* function, opt::BasicBlock* block, + const opt::BasicBlock::iterator& inst_it); + + // Randomly chooses suitable arguments to invoke |callee| right before + // instruction |caller_inst_it| of block |caller_block| in |caller_function|, + // based on both existing available instructions and the addition of new + // instructions to the module. + std::vector ChooseFunctionCallArguments( + const opt::Function& callee, opt::Function* caller_function, + opt::BasicBlock* caller_block, + const opt::BasicBlock::iterator& caller_inst_it); +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_FUZZER_PASS_ADD_FUNCTION_CALLS_H_ diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_global_variables.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_global_variables.cpp new file mode 100644 index 000000000..1371f46c6 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_global_variables.cpp @@ -0,0 +1,75 @@ +// 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_global_variables.h" + +#include "source/fuzz/transformation_add_global_variable.h" +#include "source/fuzz/transformation_add_type_pointer.h" + +namespace spvtools { +namespace fuzz { + +FuzzerPassAddGlobalVariables::FuzzerPassAddGlobalVariables( + opt::IRContext* ir_context, FactManager* fact_manager, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations) + : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {} + +FuzzerPassAddGlobalVariables::~FuzzerPassAddGlobalVariables() = default; + +void FuzzerPassAddGlobalVariables::Apply() { + auto base_type_ids_and_pointers = + GetAvailableBaseTypesAndPointers(SpvStorageClassPrivate); + + // These are the base types that are available to this fuzzer pass. + auto& base_types = base_type_ids_and_pointers.first; + + // These are the pointers to those base types that are *initially* available + // to the fuzzer pass. The fuzzer pass might add pointer types in cases where + // none are available for a given base type. + auto& base_type_to_pointers = base_type_ids_and_pointers.second; + + // Probabilistically keep adding global variables. + while (GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext()->GetChanceOfAddingGlobalVariable())) { + // Choose a random base type; the new variable's type will be a pointer to + // this base type. + uint32_t base_type = + base_types[GetFuzzerContext()->RandomIndex(base_types)]; + uint32_t pointer_type_id; + std::vector& available_pointers_to_base_type = + base_type_to_pointers.at(base_type); + // Determine whether there is at least one pointer to this base type. + if (available_pointers_to_base_type.empty()) { + // There is not. Make one, to use here, and add it to the available + // pointers for the base type so that future variables can potentially + // use it. + pointer_type_id = GetFuzzerContext()->GetFreshId(); + available_pointers_to_base_type.push_back(pointer_type_id); + ApplyTransformation(TransformationAddTypePointer( + pointer_type_id, SpvStorageClassPrivate, base_type)); + } else { + // There is - grab one. + pointer_type_id = + available_pointers_to_base_type[GetFuzzerContext()->RandomIndex( + available_pointers_to_base_type)]; + } + ApplyTransformation(TransformationAddGlobalVariable( + GetFuzzerContext()->GetFreshId(), pointer_type_id, + FindOrCreateZeroConstant(base_type), true)); + } +} + +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_global_variables.h b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_global_variables.h new file mode 100644 index 000000000..c71d14774 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_global_variables.h @@ -0,0 +1,40 @@ +// 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_GLOBAL_VARIABLES_H_ +#define SOURCE_FUZZ_FUZZER_PASS_ADD_GLOBAL_VARIABLES_H_ + +#include "source/fuzz/fuzzer_pass.h" + +namespace spvtools { +namespace fuzz { + +// Fuzzer pass that randomly adds global variables, with Private storage class, +// to the module. +class FuzzerPassAddGlobalVariables : public FuzzerPass { + public: + FuzzerPassAddGlobalVariables( + opt::IRContext* ir_context, FactManager* fact_manager, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations); + + ~FuzzerPassAddGlobalVariables(); + + void Apply() override; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_FUZZER_PASS_ADD_GLOBAL_VARIABLES_H_ diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_loads.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_loads.cpp new file mode 100644 index 000000000..851787fed --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_loads.cpp @@ -0,0 +1,95 @@ +// 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_loads.h" + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/transformation_load.h" + +namespace spvtools { +namespace fuzz { + +FuzzerPassAddLoads::FuzzerPassAddLoads( + opt::IRContext* ir_context, FactManager* fact_manager, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations) + : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {} + +FuzzerPassAddLoads::~FuzzerPassAddLoads() = default; + +void FuzzerPassAddLoads::Apply() { + ForEachInstructionWithInstructionDescriptor( + [this](opt::Function* function, opt::BasicBlock* block, + opt::BasicBlock::iterator inst_it, + const protobufs::InstructionDescriptor& instruction_descriptor) + -> void { + assert(inst_it->opcode() == + instruction_descriptor.target_instruction_opcode() && + "The opcode of the instruction we might insert before must be " + "the same as the opcode in the descriptor for the instruction"); + + // Check whether it is legitimate to insert a load before this + // instruction. + if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpLoad, inst_it)) { + return; + } + + // Randomly decide whether to try inserting a load here. + if (!GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext()->GetChanceOfAddingLoad())) { + return; + } + + std::vector relevant_instructions = + FindAvailableInstructions( + function, block, inst_it, + [](opt::IRContext* context, + opt::Instruction* instruction) -> bool { + if (!instruction->result_id() || !instruction->type_id()) { + return false; + } + switch (instruction->result_id()) { + case SpvOpConstantNull: + case SpvOpUndef: + // Do not allow loading from a null or undefined pointer; + // this might be OK if the block is dead, but for now we + // conservatively avoid it. + return false; + default: + break; + } + return context->get_def_use_mgr() + ->GetDef(instruction->type_id()) + ->opcode() == SpvOpTypePointer; + }); + + // At this point, |relevant_instructions| contains all the pointers + // we might think of loading from. + if (relevant_instructions.empty()) { + return; + } + + // Choose a pointer at random, and create and apply a loading + // transformation based on it. + ApplyTransformation(TransformationLoad( + GetFuzzerContext()->GetFreshId(), + relevant_instructions[GetFuzzerContext()->RandomIndex( + relevant_instructions)] + ->result_id(), + instruction_descriptor)); + }); +} + +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_loads.h b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_loads.h new file mode 100644 index 000000000..125bc5dbf --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_loads.h @@ -0,0 +1,38 @@ +// 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_LOADS_H_ +#define SOURCE_FUZZ_FUZZER_PASS_ADD_LOADS_H_ + +#include "source/fuzz/fuzzer_pass.h" + +namespace spvtools { +namespace fuzz { + +// Fuzzer pass that adds stores, at random, from pointers in the module. +class FuzzerPassAddLoads : public FuzzerPass { + public: + FuzzerPassAddLoads(opt::IRContext* ir_context, FactManager* fact_manager, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations); + + ~FuzzerPassAddLoads(); + + void Apply() override; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_FUZZER_PASS_ADD_LOADS_H_ diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_local_variables.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_local_variables.cpp new file mode 100644 index 000000000..8d6d80d63 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_local_variables.cpp @@ -0,0 +1,79 @@ +// 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_local_variables.h" + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/transformation_add_local_variable.h" +#include "source/fuzz/transformation_add_type_pointer.h" + +namespace spvtools { +namespace fuzz { + +FuzzerPassAddLocalVariables::FuzzerPassAddLocalVariables( + opt::IRContext* ir_context, FactManager* fact_manager, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations) + : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {} + +FuzzerPassAddLocalVariables::~FuzzerPassAddLocalVariables() = default; + +void FuzzerPassAddLocalVariables::Apply() { + auto base_type_ids_and_pointers = + GetAvailableBaseTypesAndPointers(SpvStorageClassFunction); + + // These are the base types that are available to this fuzzer pass. + auto& base_types = base_type_ids_and_pointers.first; + + // These are the pointers to those base types that are *initially* available + // to the fuzzer pass. The fuzzer pass might add pointer types in cases where + // none are available for a given base type. + auto& base_type_to_pointers = base_type_ids_and_pointers.second; + + // Consider every function in the module. + for (auto& function : *GetIRContext()->module()) { + // Probabilistically keep adding random variables to this function. + while (GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext()->GetChanceOfAddingLocalVariable())) { + // Choose a random base type; the new variable's type will be a pointer to + // this base type. + uint32_t base_type = + base_types[GetFuzzerContext()->RandomIndex(base_types)]; + uint32_t pointer_type; + std::vector& available_pointers_to_base_type = + base_type_to_pointers.at(base_type); + // Determine whether there is at least one pointer to this base type. + if (available_pointers_to_base_type.empty()) { + // There is not. Make one, to use here, and add it to the available + // pointers for the base type so that future variables can potentially + // use it. + pointer_type = GetFuzzerContext()->GetFreshId(); + ApplyTransformation(TransformationAddTypePointer( + pointer_type, SpvStorageClassFunction, base_type)); + available_pointers_to_base_type.push_back(pointer_type); + } else { + // There is - grab one. + pointer_type = + available_pointers_to_base_type[GetFuzzerContext()->RandomIndex( + available_pointers_to_base_type)]; + } + ApplyTransformation(TransformationAddLocalVariable( + GetFuzzerContext()->GetFreshId(), pointer_type, function.result_id(), + FindOrCreateZeroConstant(base_type), true)); + } + } +} + +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_local_variables.h b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_local_variables.h new file mode 100644 index 000000000..eed36657f --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_local_variables.h @@ -0,0 +1,40 @@ +// 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_LOCAL_VARIABLES_H_ +#define SOURCE_FUZZ_FUZZER_PASS_ADD_LOCAL_VARIABLES_H_ + +#include "source/fuzz/fuzzer_pass.h" + +namespace spvtools { +namespace fuzz { + +// Fuzzer pass that randomly adds local variables, with Function storage class, +// to the module. +class FuzzerPassAddLocalVariables : public FuzzerPass { + public: + FuzzerPassAddLocalVariables( + opt::IRContext* ir_context, FactManager* fact_manager, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations); + + ~FuzzerPassAddLocalVariables(); + + void Apply() override; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_FUZZER_PASS_ADD_LOCAL_VARIABLES_H_ diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_no_contraction_decorations.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_no_contraction_decorations.cpp index ead8c5cd5..82fb53998 100644 --- a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_no_contraction_decorations.cpp +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_no_contraction_decorations.cpp @@ -44,12 +44,7 @@ void FuzzerPassAddNoContractionDecorations::Apply() { ->GetChanceOfAddingNoContractionDecoration())) { TransformationAddNoContractionDecoration transformation( inst.result_id()); - assert(transformation.IsApplicable(GetIRContext(), - *GetFactManager()) && - "Transformation should be applicable by construction."); - transformation.Apply(GetIRContext(), GetFactManager()); - *GetTransformations()->add_transformation() = - transformation.ToMessage(); + ApplyTransformation(transformation); } } } diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_stores.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_stores.cpp new file mode 100644 index 000000000..794ddc3de --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_stores.cpp @@ -0,0 +1,128 @@ +// 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_stores.h" + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/transformation_store.h" + +namespace spvtools { +namespace fuzz { + +FuzzerPassAddStores::FuzzerPassAddStores( + opt::IRContext* ir_context, FactManager* fact_manager, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations) + : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {} + +FuzzerPassAddStores::~FuzzerPassAddStores() = default; + +void FuzzerPassAddStores::Apply() { + ForEachInstructionWithInstructionDescriptor( + [this](opt::Function* function, opt::BasicBlock* block, + opt::BasicBlock::iterator inst_it, + const protobufs::InstructionDescriptor& instruction_descriptor) + -> void { + assert(inst_it->opcode() == + instruction_descriptor.target_instruction_opcode() && + "The opcode of the instruction we might insert before must be " + "the same as the opcode in the descriptor for the instruction"); + + // Check whether it is legitimate to insert a store before this + // instruction. + if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpStore, + inst_it)) { + return; + } + + // Randomly decide whether to try inserting a store here. + if (!GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext()->GetChanceOfAddingStore())) { + return; + } + + // Look for pointers we might consider storing to. + std::vector relevant_pointers = + FindAvailableInstructions( + function, block, inst_it, + [this, block](opt::IRContext* context, + opt::Instruction* instruction) -> bool { + if (!instruction->result_id() || !instruction->type_id()) { + return false; + } + auto type_inst = context->get_def_use_mgr()->GetDef( + instruction->type_id()); + if (type_inst->opcode() != SpvOpTypePointer) { + // Not a pointer. + return false; + } + if (type_inst->GetSingleWordInOperand(0) == + SpvStorageClassInput) { + // Read-only: cannot store to it. + return false; + } + switch (instruction->result_id()) { + case SpvOpConstantNull: + case SpvOpUndef: + // Do not allow storing to a null or undefined pointer; + // this might be OK if the block is dead, but for now we + // conservatively avoid it. + return false; + default: + break; + } + return GetFactManager()->BlockIsDead(block->id()) || + GetFactManager()->PointeeValueIsIrrelevant( + instruction->result_id()); + }); + + // At this point, |relevant_pointers| contains all the pointers we might + // think of storing to. + if (relevant_pointers.empty()) { + return; + } + + auto pointer = relevant_pointers[GetFuzzerContext()->RandomIndex( + relevant_pointers)]; + + std::vector relevant_values = + FindAvailableInstructions( + function, block, inst_it, + [pointer](opt::IRContext* context, + opt::Instruction* instruction) -> bool { + if (!instruction->result_id() || !instruction->type_id()) { + return false; + } + return instruction->type_id() == + context->get_def_use_mgr() + ->GetDef(pointer->type_id()) + ->GetSingleWordInOperand(1); + }); + + if (relevant_values.empty()) { + return; + } + + // Choose a value at random, and create and apply a storing + // transformation based on it and the pointer. + ApplyTransformation(TransformationStore( + pointer->result_id(), + relevant_values[GetFuzzerContext()->RandomIndex(relevant_values)] + ->result_id(), + instruction_descriptor)); + }); +} + +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_stores.h b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_stores.h new file mode 100644 index 000000000..9daa9e0f2 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_stores.h @@ -0,0 +1,40 @@ +// 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_STORES_H_ +#define SOURCE_FUZZ_FUZZER_PASS_ADD_STORES_H_ + +#include "source/fuzz/fuzzer_pass.h" + +namespace spvtools { +namespace fuzz { + +// Fuzzer pass that adds stores, at random, through pointers in the module, +// either (a) from dead blocks, or (b) through pointers whose pointee values +// are known not to affect the module's overall behaviour. +class FuzzerPassAddStores : public FuzzerPass { + public: + FuzzerPassAddStores(opt::IRContext* ir_context, FactManager* fact_manager, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations); + + ~FuzzerPassAddStores(); + + void Apply() override; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_FUZZER_PASS_ADD_STORES_H_ diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_adjust_function_controls.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_adjust_function_controls.cpp index 2a11988f4..fe229bca4 100644 --- a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_adjust_function_controls.cpp +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_adjust_function_controls.cpp @@ -61,10 +61,7 @@ void FuzzerPassAdjustFunctionControls::Apply() { // Create and add a transformation. TransformationSetFunctionControl transformation( function.DefInst().result_id(), new_function_control_mask); - assert(transformation.IsApplicable(GetIRContext(), *GetFactManager()) && - "Transformation should be applicable by construction."); - transformation.Apply(GetIRContext(), GetFactManager()); - *GetTransformations()->add_transformation() = transformation.ToMessage(); + ApplyTransformation(transformation); } } } diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_adjust_loop_controls.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_adjust_loop_controls.cpp index ac2408aef..c9843d0a2 100644 --- a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_adjust_loop_controls.cpp +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_adjust_loop_controls.cpp @@ -107,11 +107,7 @@ void FuzzerPassAdjustLoopControls::Apply() { // sequence. TransformationSetLoopControl transformation(block.id(), new_mask, peel_count, partial_count); - assert(transformation.IsApplicable(GetIRContext(), *GetFactManager()) && - "Transformation should be applicable by construction."); - transformation.Apply(GetIRContext(), GetFactManager()); - *GetTransformations()->add_transformation() = - transformation.ToMessage(); + ApplyTransformation(transformation); } } } diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_adjust_memory_operands_masks.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_adjust_memory_operands_masks.cpp index a9d4b3243..2d3d67653 100644 --- a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_adjust_memory_operands_masks.cpp +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_adjust_memory_operands_masks.cpp @@ -97,12 +97,7 @@ void FuzzerPassAdjustMemoryOperandsMasks::Apply() { TransformationSetMemoryOperandsMask transformation( MakeInstructionDescriptor(block, inst_it), new_mask, mask_index); - assert( - transformation.IsApplicable(GetIRContext(), *GetFactManager()) && - "Transformation should be applicable by construction."); - transformation.Apply(GetIRContext(), GetFactManager()); - *GetTransformations()->add_transformation() = - transformation.ToMessage(); + ApplyTransformation(transformation); } } } diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_adjust_selection_controls.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_adjust_selection_controls.cpp index 22654f242..397dfedb0 100644 --- a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_adjust_selection_controls.cpp +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_adjust_selection_controls.cpp @@ -62,11 +62,7 @@ void FuzzerPassAdjustSelectionControls::Apply() { // sequence. TransformationSetSelectionControl transformation( block.id(), choices[GetFuzzerContext()->RandomIndex(choices)]); - assert(transformation.IsApplicable(GetIRContext(), *GetFactManager()) && - "Transformation should be applicable by construction."); - transformation.Apply(GetIRContext(), GetFactManager()); - *GetTransformations()->add_transformation() = - transformation.ToMessage(); + ApplyTransformation(transformation); } } } diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_apply_id_synonyms.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_apply_id_synonyms.cpp index 6ff42ca79..5711f3588 100644 --- a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_apply_id_synonyms.cpp +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_apply_id_synonyms.cpp @@ -81,62 +81,65 @@ void FuzzerPassApplyIdSynonyms::Apply() { synonyms_to_try.push_back(data_descriptor); } while (!synonyms_to_try.empty()) { - auto synonym_index = GetFuzzerContext()->RandomIndex(synonyms_to_try); - auto synonym_to_try = synonyms_to_try[synonym_index]; - synonyms_to_try.erase(synonyms_to_try.begin() + synonym_index); + auto synonym_to_try = + GetFuzzerContext()->RemoveAtRandomIndex(&synonyms_to_try); + // If the synonym's |index_size| is zero, the synonym represents an id. + // Otherwise it represents some element of a composite structure, in + // which case we need to be able to add an extract instruction to get + // that element out. if (synonym_to_try->index_size() > 0 && - use_inst->opcode() == SpvOpPhi) { - // We are trying to replace an operand to an OpPhi. This means - // we cannot use a composite synonym, because that requires - // extracting a component from a composite and we cannot insert - // an extract instruction before an OpPhi. - // - // TODO(afd): We could consider inserting the extract instruction - // into the relevant parent block of the OpPhi. + !fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpCompositeExtract, + use_inst) && + use_inst->opcode() != SpvOpPhi) { + // We cannot insert an extract before this instruction, so this + // synonym is no good. continue; } - if (!TransformationReplaceIdWithSynonym::IdsIsAvailableAtUse( - GetIRContext(), use_inst, use_in_operand_index, - synonym_to_try->object())) { + if (!fuzzerutil::IdIsAvailableAtUse(GetIRContext(), use_inst, + use_in_operand_index, + synonym_to_try->object())) { continue; } - // We either replace the use with an id known to be synonymous, or - // an id that will hold the result of extracting a synonym from a - // composite. + // We either replace the use with an id known to be synonymous (when + // the synonym's |index_size| is 0), or an id that will hold the result + // of extracting a synonym from a composite (when the synonym's + // |index_size| is > 0). uint32_t id_with_which_to_replace_use; if (synonym_to_try->index_size() == 0) { id_with_which_to_replace_use = synonym_to_try->object(); } else { id_with_which_to_replace_use = GetFuzzerContext()->GetFreshId(); - protobufs::InstructionDescriptor instruction_to_insert_before = - MakeInstructionDescriptor(GetIRContext(), use_inst); - TransformationCompositeExtract composite_extract_transformation( - instruction_to_insert_before, id_with_which_to_replace_use, - synonym_to_try->object(), - fuzzerutil::RepeatedFieldToVector(synonym_to_try->index())); - assert(composite_extract_transformation.IsApplicable( - GetIRContext(), *GetFactManager()) && - "Transformation should be applicable by construction."); - composite_extract_transformation.Apply(GetIRContext(), - GetFactManager()); - *GetTransformations()->add_transformation() = - composite_extract_transformation.ToMessage(); + opt::Instruction* instruction_to_insert_before = nullptr; + + if (use_inst->opcode() != SpvOpPhi) { + instruction_to_insert_before = use_inst; + } else { + auto parent_block_id = + use_inst->GetSingleWordInOperand(use_in_operand_index + 1); + auto parent_block_instruction = + GetIRContext()->get_def_use_mgr()->GetDef(parent_block_id); + auto parent_block = + GetIRContext()->get_instr_block(parent_block_instruction); + + instruction_to_insert_before = parent_block->GetMergeInst() + ? parent_block->GetMergeInst() + : parent_block->terminator(); + } + + ApplyTransformation(TransformationCompositeExtract( + MakeInstructionDescriptor(GetIRContext(), + instruction_to_insert_before), + id_with_which_to_replace_use, synonym_to_try->object(), + fuzzerutil::RepeatedFieldToVector(synonym_to_try->index()))); } - TransformationReplaceIdWithSynonym replace_id_transformation( + ApplyTransformation(TransformationReplaceIdWithSynonym( MakeIdUseDescriptorFromUse(GetIRContext(), use_inst, use_in_operand_index), - id_with_which_to_replace_use); - - // The transformation should be applicable by construction. - assert(replace_id_transformation.IsApplicable(GetIRContext(), - *GetFactManager())); - replace_id_transformation.Apply(GetIRContext(), GetFactManager()); - *GetTransformations()->add_transformation() = - replace_id_transformation.ToMessage(); + id_with_which_to_replace_use)); break; } } diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_construct_composites.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_construct_composites.cpp index ff0adabcc..330b9cfcf 100644 --- a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_construct_composites.cpp +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_construct_composites.cpp @@ -42,9 +42,9 @@ void FuzzerPassConstructComposites::Apply() { } } - MaybeAddTransformationBeforeEachInstruction( + ForEachInstructionWithInstructionDescriptor( [this, &composite_type_ids]( - const opt::Function& function, opt::BasicBlock* block, + opt::Function* function, opt::BasicBlock* block, opt::BasicBlock::iterator inst_it, const protobufs::InstructionDescriptor& instruction_descriptor) -> void { diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_copy_objects.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_copy_objects.cpp index 35b15a38a..588cfb600 100644 --- a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_copy_objects.cpp +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_copy_objects.cpp @@ -29,8 +29,8 @@ FuzzerPassCopyObjects::FuzzerPassCopyObjects( FuzzerPassCopyObjects::~FuzzerPassCopyObjects() = default; void FuzzerPassCopyObjects::Apply() { - MaybeAddTransformationBeforeEachInstruction( - [this](const opt::Function& function, opt::BasicBlock* block, + ForEachInstructionWithInstructionDescriptor( + [this](opt::Function* function, opt::BasicBlock* block, opt::BasicBlock::iterator inst_it, const protobufs::InstructionDescriptor& instruction_descriptor) -> void { @@ -64,15 +64,11 @@ void FuzzerPassCopyObjects::Apply() { // Choose a copyable instruction at random, and create and apply an // object copying transformation based on it. - uint32_t index = GetFuzzerContext()->RandomIndex(relevant_instructions); - TransformationCopyObject transformation( - relevant_instructions[index]->result_id(), instruction_descriptor, - GetFuzzerContext()->GetFreshId()); - assert(transformation.IsApplicable(GetIRContext(), *GetFactManager()) && - "This transformation should be applicable by construction."); - transformation.Apply(GetIRContext(), GetFactManager()); - *GetTransformations()->add_transformation() = - transformation.ToMessage(); + ApplyTransformation(TransformationCopyObject( + relevant_instructions[GetFuzzerContext()->RandomIndex( + relevant_instructions)] + ->result_id(), + instruction_descriptor, GetFuzzerContext()->GetFreshId())); }); } diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_donate_modules.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_donate_modules.cpp index 75530b10e..27d8a6e5e 100644 --- a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_donate_modules.cpp +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_donate_modules.cpp @@ -18,6 +18,7 @@ #include #include +#include "source/fuzz/call_graph.h" #include "source/fuzz/instruction_message.h" #include "source/fuzz/transformation_add_constant_boolean.h" #include "source/fuzz/transformation_add_constant_composite.h" @@ -61,6 +62,8 @@ void FuzzerPassDonateModules::Apply() { std::unique_ptr donor_ir_context = donor_suppliers_.at( GetFuzzerContext()->RandomIndex(donor_suppliers_))(); assert(donor_ir_context != nullptr && "Supplying of donor failed"); + assert(fuzzerutil::IsValid(donor_ir_context.get()) && + "The donor module must be valid"); // Donate the supplied module. // // Randomly decide whether to make the module livesafe (see @@ -397,19 +400,27 @@ void FuzzerPassDonateModules::HandleTypesAndValues( // storage class global variable, using remapped versions of the result // type and initializer ids for the global variable in the donor. // - // We regard the added variable as having an arbitrary value. This + // We regard the added variable as having an irrelevant value. This // means that future passes can add stores to the variable in any // way they wish, and pass them as pointer parameters to functions // without worrying about whether their data might get modified. new_result_id = GetFuzzerContext()->GetFreshId(); + uint32_t remapped_pointer_type = + original_id_to_donated_id->at(type_or_value.type_id()); + uint32_t initializer_id; + if (type_or_value.NumInOperands() == 1) { + // The variable did not have an initializer; initialize it to zero. + // This is to limit problems associated with uninitialized data. + initializer_id = FindOrCreateZeroConstant( + fuzzerutil::GetPointeeTypeIdFromPointerType( + GetIRContext(), remapped_pointer_type)); + } else { + // The variable already had an initializer; use its remapped id. + initializer_id = original_id_to_donated_id->at( + type_or_value.GetSingleWordInOperand(1)); + } ApplyTransformation(TransformationAddGlobalVariable( - new_result_id, - original_id_to_donated_id->at(type_or_value.type_id()), - type_or_value.NumInOperands() == 1 - ? 0 - : original_id_to_donated_id->at( - type_or_value.GetSingleWordInOperand(1)), - true)); + new_result_id, remapped_pointer_type, initializer_id, true)); } break; case SpvOpUndef: { // It is fine to have multiple Undef instructions of the same type, so @@ -473,53 +484,65 @@ void FuzzerPassDonateModules::HandleFunctions( }); // Consider every instruction of the donor function. - function_to_donate->ForEachInst( - [&donated_instructions, - &original_id_to_donated_id](const opt::Instruction* instruction) { - // Get the instruction's input operands into donation-ready form, - // remapping any id uses in the process. - opt::Instruction::OperandList input_operands; + function_to_donate->ForEachInst([this, &donated_instructions, + &original_id_to_donated_id]( + const opt::Instruction* instruction) { + // Get the instruction's input operands into donation-ready form, + // remapping any id uses in the process. + opt::Instruction::OperandList input_operands; - // Consider each input operand in turn. - for (uint32_t in_operand_index = 0; - in_operand_index < instruction->NumInOperands(); - in_operand_index++) { - std::vector operand_data; - const opt::Operand& in_operand = - instruction->GetInOperand(in_operand_index); - switch (in_operand.type) { - case SPV_OPERAND_TYPE_ID: - case SPV_OPERAND_TYPE_TYPE_ID: - case SPV_OPERAND_TYPE_RESULT_ID: - case SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID: - case SPV_OPERAND_TYPE_SCOPE_ID: - // This is an id operand - it consists of a single word of data, - // which needs to be remapped so that it is replaced with the - // donated form of the id. - operand_data.push_back( - original_id_to_donated_id->at(in_operand.words[0])); - break; - default: - // For non-id operands, we just add each of the data words. - for (auto word : in_operand.words) { - operand_data.push_back(word); - } - break; + // Consider each input operand in turn. + for (uint32_t in_operand_index = 0; + in_operand_index < instruction->NumInOperands(); + in_operand_index++) { + std::vector operand_data; + const opt::Operand& in_operand = + instruction->GetInOperand(in_operand_index); + switch (in_operand.type) { + case SPV_OPERAND_TYPE_ID: + case SPV_OPERAND_TYPE_TYPE_ID: + case SPV_OPERAND_TYPE_RESULT_ID: + case SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID: + case SPV_OPERAND_TYPE_SCOPE_ID: + // This is an id operand - it consists of a single word of data, + // which needs to be remapped so that it is replaced with the + // donated form of the id. + operand_data.push_back( + original_id_to_donated_id->at(in_operand.words[0])); + break; + default: + // For non-id operands, we just add each of the data words. + for (auto word : in_operand.words) { + operand_data.push_back(word); } - input_operands.push_back({in_operand.type, operand_data}); - } - // Remap the result type and result id (if present) of the - // instruction, and turn it into a protobuf message. - donated_instructions.push_back(MakeInstructionMessage( - instruction->opcode(), - instruction->type_id() - ? original_id_to_donated_id->at(instruction->type_id()) - : 0, - instruction->result_id() - ? original_id_to_donated_id->at(instruction->result_id()) - : 0, - input_operands)); - }); + break; + } + input_operands.push_back({in_operand.type, operand_data}); + } + + if (instruction->opcode() == SpvOpVariable && + instruction->NumInOperands() == 1) { + // This is an uninitialized local variable. Initialize it to zero. + input_operands.push_back( + {SPV_OPERAND_TYPE_ID, + {FindOrCreateZeroConstant( + fuzzerutil::GetPointeeTypeIdFromPointerType( + GetIRContext(), + original_id_to_donated_id->at(instruction->type_id())))}}); + } + + // Remap the result type and result id (if present) of the + // instruction, and turn it into a protobuf message. + donated_instructions.push_back(MakeInstructionMessage( + instruction->opcode(), + instruction->type_id() + ? original_id_to_donated_id->at(instruction->type_id()) + : 0, + instruction->result_id() + ? original_id_to_donated_id->at(instruction->result_id()) + : 0, + input_operands)); + }); if (make_livesafe) { // Various types and constants must be in place for a function to be made @@ -647,9 +670,12 @@ void FuzzerPassDonateModules::HandleFunctions( // The return type is void, so we don't need a return value. kill_unreachable_return_value_id = 0; } else { - // We do need a return value; we use OpUndef. + // We do need a return value; we use zero. + assert(function_return_type_inst->opcode() != SpvOpTypePointer && + "Function return type must not be a pointer."); kill_unreachable_return_value_id = - FindOrCreateGlobalUndef(function_return_type_inst->type_id()); + FindOrCreateZeroConstant(original_id_to_donated_id->at( + function_return_type_inst->result_id())); } // Add the function in a livesafe manner. ApplyTransformation(TransformationAddFunction( @@ -666,53 +692,18 @@ void FuzzerPassDonateModules::HandleFunctions( std::vector FuzzerPassDonateModules::GetFunctionsInCallGraphTopologicalOrder( opt::IRContext* context) { + CallGraph call_graph(context); + // This is an implementation of Kahn’s algorithm for topological sorting. - // For each function id, stores the number of distinct functions that call - // the function. - std::map function_in_degree; - - // We first build a call graph for the module, and compute the in-degree for - // each function in the process. - // TODO(afd): If there is functionality elsewhere in the SPIR-V tools - // framework to construct call graphs it could be nice to re-use it here. - std::map> call_graph_edges; - - // Initialize function in-degree and call graph edges to 0 and empty. - for (auto& function : *context->module()) { - function_in_degree[function.result_id()] = 0; - call_graph_edges[function.result_id()] = std::set(); - } - - // Consider every function. - for (auto& function : *context->module()) { - // Avoid considering the same callee of this function multiple times by - // recording known callees. - std::set known_callees; - // Consider every function call instruction in every block. - for (auto& block : function) { - for (auto& instruction : block) { - if (instruction.opcode() != SpvOpFunctionCall) { - continue; - } - // Get the id of the function being called. - uint32_t callee = instruction.GetSingleWordInOperand(0); - if (known_callees.count(callee)) { - // We have already considered a call to this function - ignore it. - continue; - } - // Increase the callee's in-degree and add an edge to the call graph. - function_in_degree[callee]++; - call_graph_edges[function.result_id()].insert(callee); - // Mark the callee as 'known'. - known_callees.insert(callee); - } - } - } - // This is the sorted order of function ids that we will eventually return. std::vector result; + // Get a copy of the initial in-degrees of all functions. The algorithm + // involves decrementing these values, hence why we work on a copy. + std::map function_in_degree = + call_graph.GetFunctionInDegree(); + // Populate a queue with all those function ids with in-degree zero. std::queue queue; for (auto& entry : function_in_degree) { @@ -728,7 +719,7 @@ FuzzerPassDonateModules::GetFunctionsInCallGraphTopologicalOrder( auto next = queue.front(); queue.pop(); result.push_back(next); - for (auto successor : call_graph_edges.at(next)) { + for (auto successor : call_graph.GetDirectCallees(next)) { assert(function_in_degree.at(successor) > 0 && "The in-degree cannot be zero if the function is a successor."); function_in_degree[successor] = function_in_degree.at(successor) - 1; diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_obfuscate_constants.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_obfuscate_constants.cpp index 3df11aeec..2caf0c6b6 100644 --- a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_obfuscate_constants.cpp +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_obfuscate_constants.cpp @@ -416,13 +416,28 @@ void FuzzerPassObfuscateConstants::Apply() { skipped_opcode_count.clear(); } - // Consider each operand of the instruction, and add a constant id use - // for the operand if relevant. - for (uint32_t in_operand_index = 0; - in_operand_index < inst.NumInOperands(); in_operand_index++) { - MaybeAddConstantIdUse(inst, in_operand_index, - base_instruction_result_id, - skipped_opcode_count, &constant_uses); + switch (inst.opcode()) { + case SpvOpPhi: + // The instruction must not be an OpPhi, as we cannot insert + // instructions before an OpPhi. + // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2902): + // there is scope for being less conservative. + break; + case SpvOpVariable: + // The instruction must not be an OpVariable, the only id that an + // OpVariable uses is an initializer id, which has to remain + // constant. + break; + default: + // Consider each operand of the instruction, and add a constant id + // use for the operand if relevant. + for (uint32_t in_operand_index = 0; + in_operand_index < inst.NumInOperands(); in_operand_index++) { + MaybeAddConstantIdUse(inst, in_operand_index, + base_instruction_result_id, + skipped_opcode_count, &constant_uses); + } + break; } if (!inst.HasResultId()) { diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_permute_function_parameters.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_permute_function_parameters.cpp new file mode 100644 index 000000000..2c49860ef --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_permute_function_parameters.cpp @@ -0,0 +1,81 @@ +// Copyright (c) 2020 Vasyl Teliman +// +// 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 +#include + +#include "source/fuzz/fuzzer_context.h" +#include "source/fuzz/fuzzer_pass_permute_function_parameters.h" +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/instruction_descriptor.h" +#include "source/fuzz/transformation_permute_function_parameters.h" + +namespace spvtools { +namespace fuzz { + +FuzzerPassPermuteFunctionParameters::FuzzerPassPermuteFunctionParameters( + opt::IRContext* ir_context, FactManager* fact_manager, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations) + : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {} + +FuzzerPassPermuteFunctionParameters::~FuzzerPassPermuteFunctionParameters() = + default; + +void FuzzerPassPermuteFunctionParameters::Apply() { + for (const auto& function : *GetIRContext()->module()) { + uint32_t function_id = function.result_id(); + + // Skip the function if it is an entry point + if (fuzzerutil::FunctionIsEntryPoint(GetIRContext(), function_id)) { + continue; + } + + if (!GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext()->GetChanceOfPermutingParameters())) { + continue; + } + + // Compute permutation for parameters + auto* function_type = + fuzzerutil::GetFunctionType(GetIRContext(), &function); + assert(function_type && "Function type is null"); + + // Don't take return type into account + uint32_t arg_size = function_type->NumInOperands() - 1; + + // Create a vector, fill it with [0, n-1] values and shuffle it + std::vector permutation(arg_size); + std::iota(permutation.begin(), permutation.end(), 0); + GetFuzzerContext()->Shuffle(&permutation); + + // Create a new OpFunctionType instruction with permuted arguments + // if needed + auto result_type_id = function_type->GetSingleWordInOperand(0); + std::vector argument_ids; + + for (auto index : permutation) { + // +1 to take function's return type into account + argument_ids.push_back(function_type->GetSingleWordInOperand(index + 1)); + } + + // Apply our transformation + ApplyTransformation(TransformationPermuteFunctionParameters( + function_id, FindOrCreateFunctionType(result_type_id, argument_ids), + permutation)); + } +} + +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_permute_function_parameters.h b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_permute_function_parameters.h new file mode 100644 index 000000000..bc7980494 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_permute_function_parameters.h @@ -0,0 +1,45 @@ +// Copyright (c) 2020 Vasyl Teliman +// +// 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_PERMUTE_FUNCTION_PARAMETERS_H_ +#define SOURCE_FUZZ_FUZZER_PASS_PERMUTE_FUNCTION_PARAMETERS_H_ + +#include "source/fuzz/fuzzer_pass.h" + +namespace spvtools { +namespace fuzz { + +// Fuzzer pass that, given a non-entry-point function taking n parameters +// and a permutation of the set [0, n - 1]: +// 1. Introduces a new function type that is the same as the original +// function's type but with the order of arguments permuted +// (only add this if it doesn't already exist) +// 2. Changes the type of the function to this type +// 3. Adjusts all calls to the function so that their arguments are permuted +class FuzzerPassPermuteFunctionParameters : public FuzzerPass { + public: + FuzzerPassPermuteFunctionParameters( + opt::IRContext* ir_context, FactManager* fact_manager, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations); + + ~FuzzerPassPermuteFunctionParameters(); + + void Apply() override; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_FUZZER_PASS_PERMUTE_FUNCTION_PARAMETERS_H_ diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_swap_commutable_operands.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_swap_commutable_operands.cpp new file mode 100644 index 000000000..4df97c94e --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_swap_commutable_operands.cpp @@ -0,0 +1,50 @@ +// 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_swap_commutable_operands.h" + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/instruction_descriptor.h" +#include "source/fuzz/transformation_swap_commutable_operands.h" + +namespace spvtools { +namespace fuzz { + +FuzzerPassSwapCommutableOperands::FuzzerPassSwapCommutableOperands( + opt::IRContext* ir_context, FactManager* fact_manager, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations) + : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {} + +FuzzerPassSwapCommutableOperands::~FuzzerPassSwapCommutableOperands() = default; + +void FuzzerPassSwapCommutableOperands::Apply() { + auto context = GetIRContext(); + // Iterates over the module's instructions and checks whether it is + // commutative. In this case, the transformation is probabilistically applied. + context->module()->ForEachInst( + [this, context](opt::Instruction* instruction) { + if (spvOpcodeIsCommutativeBinaryOperator(instruction->opcode()) && + GetFuzzerContext()->ChooseEven()) { + auto instructionDescriptor = + MakeInstructionDescriptor(context, instruction); + auto transformation = + TransformationSwapCommutableOperands(instructionDescriptor); + ApplyTransformation(transformation); + } + }); +} + +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_swap_commutable_operands.h b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_swap_commutable_operands.h new file mode 100644 index 000000000..b0206de76 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_swap_commutable_operands.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_SWAP_COMMUTABLE_OPERANDS_H_ +#define SOURCE_FUZZ_FUZZER_PASS_SWAP_COMMUTABLE_OPERANDS_H_ + +#include "source/fuzz/fuzzer_pass.h" + +namespace spvtools { +namespace fuzz { + +// This fuzzer pass searches for all commutative instructions in the module, +// probabilistically choosing which of these instructions will have its input +// operands swapped. +class FuzzerPassSwapCommutableOperands : public FuzzerPass { + public: + FuzzerPassSwapCommutableOperands( + opt::IRContext* ir_context, FactManager* fact_manager, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations); + + ~FuzzerPassSwapCommutableOperands(); + + void Apply() override; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_FUZZER_PASS_SWAP_COMMUTABLE_OPERANDS_H_ diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_toggle_access_chain_instruction.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_toggle_access_chain_instruction.cpp new file mode 100644 index 000000000..9fb175ba2 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_toggle_access_chain_instruction.cpp @@ -0,0 +1,54 @@ +// 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_toggle_access_chain_instruction.h" + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/instruction_descriptor.h" +#include "source/fuzz/transformation_toggle_access_chain_instruction.h" + +namespace spvtools { +namespace fuzz { + +FuzzerPassToggleAccessChainInstruction::FuzzerPassToggleAccessChainInstruction( + opt::IRContext* ir_context, FactManager* fact_manager, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations) + : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {} + +FuzzerPassToggleAccessChainInstruction:: + ~FuzzerPassToggleAccessChainInstruction() = default; + +void FuzzerPassToggleAccessChainInstruction::Apply() { + auto context = GetIRContext(); + // Iterates over the module's instructions and checks whether it is + // OpAccessChain or OpInBoundsAccessChain. In this case, the transformation is + // probabilistically applied. + context->module()->ForEachInst([this, + context](opt::Instruction* instruction) { + SpvOp opcode = instruction->opcode(); + if ((opcode == SpvOpAccessChain || opcode == SpvOpInBoundsAccessChain) && + GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext()->GetChanceOfTogglingAccessChainInstruction())) { + auto instructionDescriptor = + MakeInstructionDescriptor(context, instruction); + auto transformation = + TransformationToggleAccessChainInstruction(instructionDescriptor); + ApplyTransformation(transformation); + } + }); +} + +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_toggle_access_chain_instruction.h b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_toggle_access_chain_instruction.h new file mode 100644 index 000000000..ec8c3f78f --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_toggle_access_chain_instruction.h @@ -0,0 +1,40 @@ +// 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_TOGGLE_ACCESS_CHAIN_INSTRUCTION_H_ +#define SOURCE_FUZZ_FUZZER_PASS_TOGGLE_ACCESS_CHAIN_INSTRUCTION_H_ + +#include "source/fuzz/fuzzer_pass.h" + +namespace spvtools { +namespace fuzz { + +// This fuzzer pass searches for all access chain instructions in the module, +// probabilistically choosing which of these instructions will be toggled. +class FuzzerPassToggleAccessChainInstruction : public FuzzerPass { + public: + FuzzerPassToggleAccessChainInstruction( + opt::IRContext* ir_context, FactManager* fact_manager, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations); + + ~FuzzerPassToggleAccessChainInstruction(); + + void Apply() override; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_FUZZER_PASS_TOGGLE_ACCESS_CHAIN_INSTRUCTION_H_ diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_util.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer_util.cpp index f9f9969cd..4bfa19506 100644 --- a/3rdparty/spirv-tools/source/fuzz/fuzzer_util.cpp +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_util.cpp @@ -262,44 +262,48 @@ std::vector RepeatedFieldToVector( return result; } +uint32_t WalkOneCompositeTypeIndex(opt::IRContext* context, + uint32_t base_object_type_id, + uint32_t index) { + auto should_be_composite_type = + context->get_def_use_mgr()->GetDef(base_object_type_id); + assert(should_be_composite_type && "The type should exist."); + switch (should_be_composite_type->opcode()) { + case SpvOpTypeArray: { + auto array_length = GetArraySize(*should_be_composite_type, context); + if (array_length == 0 || index >= array_length) { + return 0; + } + return should_be_composite_type->GetSingleWordInOperand(0); + } + case SpvOpTypeMatrix: + case SpvOpTypeVector: { + auto count = should_be_composite_type->GetSingleWordInOperand(1); + if (index >= count) { + return 0; + } + return should_be_composite_type->GetSingleWordInOperand(0); + } + case SpvOpTypeStruct: { + if (index >= GetNumberOfStructMembers(*should_be_composite_type)) { + return 0; + } + return should_be_composite_type->GetSingleWordInOperand(index); + } + default: + return 0; + } +} + uint32_t WalkCompositeTypeIndices( opt::IRContext* context, uint32_t base_object_type_id, const google::protobuf::RepeatedField& indices) { uint32_t sub_object_type_id = base_object_type_id; for (auto index : indices) { - auto should_be_composite_type = - context->get_def_use_mgr()->GetDef(sub_object_type_id); - assert(should_be_composite_type && "The type should exist."); - switch (should_be_composite_type->opcode()) { - case SpvOpTypeArray: { - auto array_length = GetArraySize(*should_be_composite_type, context); - if (array_length == 0 || index >= array_length) { - return 0; - } - sub_object_type_id = - should_be_composite_type->GetSingleWordInOperand(0); - break; - } - case SpvOpTypeMatrix: - case SpvOpTypeVector: { - auto count = should_be_composite_type->GetSingleWordInOperand(1); - if (index >= count) { - return 0; - } - sub_object_type_id = - should_be_composite_type->GetSingleWordInOperand(0); - break; - } - case SpvOpTypeStruct: { - if (index >= GetNumberOfStructMembers(*should_be_composite_type)) { - return 0; - } - sub_object_type_id = - should_be_composite_type->GetSingleWordInOperand(index); - break; - } - default: - return 0; + sub_object_type_id = + WalkOneCompositeTypeIndex(context, sub_object_type_id, index); + if (!sub_object_type_id) { + return 0; } } return sub_object_type_id; @@ -391,6 +395,148 @@ uint32_t FindFunctionType(opt::IRContext* ir_context, return 0; } +opt::Instruction* GetFunctionType(opt::IRContext* context, + const opt::Function* function) { + uint32_t type_id = function->DefInst().GetSingleWordInOperand(1); + return context->get_def_use_mgr()->GetDef(type_id); +} + +opt::Function* FindFunction(opt::IRContext* ir_context, uint32_t function_id) { + for (auto& function : *ir_context->module()) { + if (function.result_id() == function_id) { + return &function; + } + } + return nullptr; +} + +bool FunctionIsEntryPoint(opt::IRContext* context, uint32_t function_id) { + for (auto& entry_point : context->module()->entry_points()) { + if (entry_point.GetSingleWordInOperand(1) == function_id) { + return true; + } + } + return false; +} + +bool IdIsAvailableAtUse(opt::IRContext* context, + opt::Instruction* use_instruction, + uint32_t use_input_operand_index, uint32_t id) { + auto defining_instruction = context->get_def_use_mgr()->GetDef(id); + auto enclosing_function = + context->get_instr_block(use_instruction)->GetParent(); + // If the id a function parameter, it needs to be associated with the + // function containing the use. + if (defining_instruction->opcode() == SpvOpFunctionParameter) { + return InstructionIsFunctionParameter(defining_instruction, + enclosing_function); + } + if (!context->get_instr_block(id)) { + // The id must be at global scope. + return true; + } + if (defining_instruction == use_instruction) { + // It is not OK for a definition to use itself. + return false; + } + auto dominator_analysis = context->GetDominatorAnalysis(enclosing_function); + if (use_instruction->opcode() == SpvOpPhi) { + // In the case where the use is an operand to OpPhi, it is actually the + // *parent* block associated with the operand that must be dominated by + // the synonym. + auto parent_block = + use_instruction->GetSingleWordInOperand(use_input_operand_index + 1); + return dominator_analysis->Dominates( + context->get_instr_block(defining_instruction)->id(), parent_block); + } + return dominator_analysis->Dominates(defining_instruction, use_instruction); +} + +bool IdIsAvailableBeforeInstruction(opt::IRContext* context, + opt::Instruction* instruction, + uint32_t id) { + auto defining_instruction = context->get_def_use_mgr()->GetDef(id); + auto enclosing_function = context->get_instr_block(instruction)->GetParent(); + // If the id a function parameter, it needs to be associated with the + // function containing the instruction. + if (defining_instruction->opcode() == SpvOpFunctionParameter) { + return InstructionIsFunctionParameter(defining_instruction, + enclosing_function); + } + if (!context->get_instr_block(id)) { + // The id is at global scope. + return true; + } + if (defining_instruction == instruction) { + // The instruction is not available right before its own definition. + return false; + } + return context->GetDominatorAnalysis(enclosing_function) + ->Dominates(defining_instruction, instruction); +} + +bool InstructionIsFunctionParameter(opt::Instruction* instruction, + opt::Function* function) { + if (instruction->opcode() != SpvOpFunctionParameter) { + return false; + } + bool found_parameter = false; + function->ForEachParam( + [instruction, &found_parameter](opt::Instruction* param) { + if (param == instruction) { + found_parameter = true; + } + }); + return found_parameter; +} + +uint32_t GetTypeId(opt::IRContext* context, uint32_t result_id) { + return context->get_def_use_mgr()->GetDef(result_id)->type_id(); +} + +uint32_t GetPointeeTypeIdFromPointerType(opt::Instruction* pointer_type_inst) { + assert(pointer_type_inst && pointer_type_inst->opcode() == SpvOpTypePointer && + "Precondition: |pointer_type_inst| must be OpTypePointer."); + return pointer_type_inst->GetSingleWordInOperand(1); +} + +uint32_t GetPointeeTypeIdFromPointerType(opt::IRContext* context, + uint32_t pointer_type_id) { + return GetPointeeTypeIdFromPointerType( + context->get_def_use_mgr()->GetDef(pointer_type_id)); +} + +SpvStorageClass GetStorageClassFromPointerType( + opt::Instruction* pointer_type_inst) { + assert(pointer_type_inst && pointer_type_inst->opcode() == SpvOpTypePointer && + "Precondition: |pointer_type_inst| must be OpTypePointer."); + return static_cast( + pointer_type_inst->GetSingleWordInOperand(0)); +} + +SpvStorageClass GetStorageClassFromPointerType(opt::IRContext* context, + uint32_t pointer_type_id) { + return GetStorageClassFromPointerType( + context->get_def_use_mgr()->GetDef(pointer_type_id)); +} + +uint32_t MaybeGetPointerType(opt::IRContext* context, uint32_t pointee_type_id, + SpvStorageClass storage_class) { + for (auto& inst : context->types_values()) { + switch (inst.opcode()) { + case SpvOpTypePointer: + if (inst.GetSingleWordInOperand(0) == storage_class && + inst.GetSingleWordInOperand(1) == pointee_type_id) { + return inst.result_id(); + } + break; + default: + break; + } + } + return 0; +} + } // namespace fuzzerutil } // namespace fuzz diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_util.h b/3rdparty/spirv-tools/source/fuzz/fuzzer_util.h index f0a2953fd..7be0d59eb 100644 --- a/3rdparty/spirv-tools/source/fuzz/fuzzer_util.h +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_util.h @@ -98,6 +98,21 @@ bool IsCompositeType(const opt::analysis::Type* type); std::vector RepeatedFieldToVector( const google::protobuf::RepeatedField& repeated_field); +// Given a type id, |base_object_type_id|, returns 0 if the type is not a +// composite type or if |index| is too large to be used as an index into the +// composite. Otherwise returns the type id of the type associated with the +// composite's index. +// +// Example: if |base_object_type_id| is 10, and we have: +// +// %10 = OpTypeStruct %3 %4 %5 +// +// then 3 will be returned if |index| is 0, 5 if |index| is 2, and 0 if index +// is 3 or larger. +uint32_t WalkOneCompositeTypeIndex(opt::IRContext* context, + uint32_t base_object_type_id, + uint32_t index); + // Given a type id, |base_object_type_id|, checks that the given sequence of // |indices| is suitable for indexing into this type. Returns the id of the // type of the final sub-object reached via the indices if they are valid, and @@ -137,6 +152,63 @@ bool IsMergeOrContinue(opt::IRContext* ir_context, uint32_t block_id); uint32_t FindFunctionType(opt::IRContext* ir_context, const std::vector& type_ids); +// Returns a type instruction (OpTypeFunction) for |function|. +// Returns |nullptr| if type is not found. +opt::Instruction* GetFunctionType(opt::IRContext* context, + const opt::Function* function); + +// Returns the function with result id |function_id|, or |nullptr| if no such +// function exists. +opt::Function* FindFunction(opt::IRContext* ir_context, uint32_t function_id); + +// Returns |true| if one of entry points has function id |function_id|. +bool FunctionIsEntryPoint(opt::IRContext* context, uint32_t function_id); + +// Checks whether |id| is available (according to dominance rules) at the use +// point defined by input operand |use_input_operand_index| of +// |use_instruction|. +bool IdIsAvailableAtUse(opt::IRContext* context, + opt::Instruction* use_instruction, + uint32_t use_input_operand_index, uint32_t id); + +// Checks whether |id| is available (according to dominance rules) at the +// program point directly before |instruction|. +bool IdIsAvailableBeforeInstruction(opt::IRContext* context, + opt::Instruction* instruction, uint32_t id); + +// Returns true if and only if |instruction| is an OpFunctionParameter +// associated with |function|. +bool InstructionIsFunctionParameter(opt::Instruction* instruction, + opt::Function* function); + +// Returns the type id of the instruction defined by |result_id|, or 0 if there +// is no such result id. +uint32_t GetTypeId(opt::IRContext* context, uint32_t result_id); + +// Given |pointer_type_inst|, which must be an OpTypePointer instruction, +// returns the id of the associated pointee type. +uint32_t GetPointeeTypeIdFromPointerType(opt::Instruction* pointer_type_inst); + +// Given |pointer_type_id|, which must be the id of a pointer type, returns the +// id of the associated pointee type. +uint32_t GetPointeeTypeIdFromPointerType(opt::IRContext* context, + uint32_t pointer_type_id); + +// Given |pointer_type_inst|, which must be an OpTypePointer instruction, +// returns the associated storage class. +SpvStorageClass GetStorageClassFromPointerType( + opt::Instruction* pointer_type_inst); + +// Given |pointer_type_id|, which must be the id of a pointer type, returns the +// associated storage class. +SpvStorageClass GetStorageClassFromPointerType(opt::IRContext* context, + uint32_t pointer_type_id); + +// Returns the id of a pointer with pointee type |pointee_type_id| and storage +// class |storage_class|, if it exists, and 0 otherwise. +uint32_t MaybeGetPointerType(opt::IRContext* context, uint32_t pointee_type_id, + SpvStorageClass storage_class); + } // namespace fuzzerutil } // namespace fuzz diff --git a/3rdparty/spirv-tools/source/fuzz/protobufs/spvtoolsfuzz.proto b/3rdparty/spirv-tools/source/fuzz/protobufs/spvtoolsfuzz.proto index 52a3a788c..b816e3b0e 100644 --- a/3rdparty/spirv-tools/source/fuzz/protobufs/spvtoolsfuzz.proto +++ b/3rdparty/spirv-tools/source/fuzz/protobufs/spvtoolsfuzz.proto @@ -168,12 +168,22 @@ message Fact { FactDataSynonym data_synonym_fact = 2; FactBlockIsDead block_is_dead_fact = 3; FactFunctionIsLivesafe function_is_livesafe_fact = 4; - FactValueOfVariableIsArbitrary value_of_variable_is_arbitrary = 5; + FactPointeeValueIsIrrelevant pointee_value_is_irrelevant_fact = 5; + FactIdEquation id_equation_fact = 6; } } // Keep fact message types in alphabetical order: +message FactBlockIsDead { + + // Records the fact that a block is guaranteed to be dynamically unreachable. + // This is useful because it informs the fuzzer that rather arbitrary changes + // can be made to this block. + + uint32 block_id = 1; +} + message FactConstantUniform { // Records the fact that a uniform buffer element is guaranteed to be equal @@ -203,15 +213,6 @@ message FactDataSynonym { } -message FactBlockIsDead { - - // Records the fact that a block is guaranteed to be dynamically unreachable. - // This is useful because it informs the fuzzer that rather arbitrary changes - // can be made to this block. - - uint32 block_id = 1; -} - message FactFunctionIsLivesafe { // Records the fact that a function is guaranteed to be "livesafe", meaning @@ -223,17 +224,40 @@ message FactFunctionIsLivesafe { uint32 function_id = 1; } -message FactValueOfVariableIsArbitrary { +message FactIdEquation { - // Records the fact that the value stored in the variable or function - // parameter with the given id can be arbitrary: the module's observable - // behaviour does not depend on it. This means that arbitrary stores can be - // made to the variable, and that nothing can be guaranteed about values - // loaded from the variable. + // Records the fact that the equation: + // + // lhs_id = opcode rhs_id[0] rhs_id[1] ... rhs_id[N-1] + // + // holds; e.g. that the equation: + // + // %12 = OpIAdd %13 %14 + // + // holds in the case where lhs_id is 12, rhs_id is [13, 14], and the opcode is + // OpIAdd. - // The result id of an OpVariable instruction, or an OpFunctionParameter - // instruction with pointer type - uint32 variable_id = 1; + // The left-hand-side of the equation. + uint32 lhs_id = 1; + + // A SPIR-V opcode, from a restricted set of instructions for which equation + // facts make sense. + uint32 opcode = 2; + + // The operands to the right-hand-side of the equation. + repeated uint32 rhs_id = 3; + +} + +message FactPointeeValueIsIrrelevant { + + // Records the fact that value of the data pointed to by a pointer id does + // not influence the observable behaviour of the module. This means that + // arbitrary stores can be made through the pointer, and that nothing can be + // guaranteed about the values that are loaded via the pointer. + + // A result id of pointer type + uint32 pointer_id = 1; } message AccessChainClampingInfo { @@ -339,12 +363,40 @@ message Transformation { TransformationAddGlobalUndef add_global_undef = 32; TransformationAddFunction add_function = 33; TransformationAddDeadBlock add_dead_block = 34; + TransformationAddLocalVariable add_local_variable = 35; + TransformationLoad load = 36; + TransformationStore store = 37; + TransformationFunctionCall function_call = 38; + TransformationAccessChain access_chain = 39; + TransformationEquationInstruction equation_instruction = 40; + TransformationSwapCommutableOperands swap_commutable_operands = 41; + TransformationPermuteFunctionParameters permute_function_parameters = 42; + TransformationToggleAccessChainInstruction toggle_access_chain_instruction = 43; // Add additional option using the next available number. } } // Keep transformation message types in alphabetical order: +message TransformationAccessChain { + + // Adds an access chain instruction based on a given pointer and indices. + + // Result id for the access chain + uint32 fresh_id = 1; + + // The pointer from which the access chain starts + uint32 pointer_id = 2; + + // Zero or more access chain indices + repeated uint32 index_id = 3; + + // A descriptor for an instruction in a block before which the new + // OpAccessChain instruction should be inserted + InstructionDescriptor instruction_to_insert_before = 4; + +} + message TransformationAddConstantBoolean { // Supports adding the constants true and false to a module, which may be @@ -504,15 +556,38 @@ message TransformationAddGlobalVariable { // The type of the global variable uint32 type_id = 2; - // Optional initializer; 0 if there is no initializer + // Initial value of the variable uint32 initializer_id = 3; - // True if and only if the value of the variable should be regarded, in - // general, as totally unknown and subject to change (even if, due to an - // initializer, the original value is known). This is the case for variables - // added when a module is donated, for example, and means that stores to such - // variables can be performed in an arbitrary fashion. - bool value_is_arbitrary = 4; + // True if and only if the behaviour of the module should not depend on the + // value of the variable, in which case stores to the variable can be + // performed in an arbitrary fashion. + bool value_is_irrelevant = 4; + +} + +message TransformationAddLocalVariable { + + // Adds a local variable of the given type (which must be a pointer with + // Function storage class) to the given function, initialized to the given + // id. + + // Fresh id for the local variable + uint32 fresh_id = 1; + + // The type of the local variable + uint32 type_id = 2; + + // The id of the function to which the local variable should be added + uint32 function_id = 3; + + // Initial value of the variable + uint32 initializer_id = 4; + + // True if and only if the behaviour of the module should not depend on the + // value of the variable, in which case stores to the variable can be + // performed in an arbitrary fashion. + bool value_is_irrelevant = 5; } @@ -707,6 +782,67 @@ message TransformationCopyObject { } +message TransformationEquationInstruction { + + // A transformation that adds an instruction to the module that defines an + // equation between its result id and input operand ids, such that the + // equation is guaranteed to hold at any program point where all ids involved + // are available (i.e. at any program point dominated by the instruction). + + // The result id of the new instruction + uint32 fresh_id = 1; + + // The instruction's opcode + uint32 opcode = 2; + + // The input operands to the instruction + repeated uint32 in_operand_id = 3; + + // A descriptor for an instruction in a block before which the new + // instruction should be inserted + InstructionDescriptor instruction_to_insert_before = 4; + +} + +message TransformationFunctionCall { + + // A transformation that introduces an OpFunctionCall instruction. The call + // must not make the module's call graph cyclic. Beyond that, if the call + // is in a dead block it can be to any function with arbitrary suitably-typed + // arguments; otherwise it must be to a livesafe function, with injected + // variables as pointer arguments and arbitrary non-pointer arguments. + + // A fresh id for the result of the call + uint32 fresh_id = 1; + + // Id of the function to be called + uint32 callee_id = 2; + + // Ids for arguments to the function + repeated uint32 argument_id = 3; + + // A descriptor for an instruction in a block before which the new + // OpFunctionCall instruction should be inserted + InstructionDescriptor instruction_to_insert_before = 4; + +} + +message TransformationLoad { + + // Transformation that adds an OpLoad instruction from a pointer into an id. + + // The result of the load instruction + uint32 fresh_id = 1; + + // The pointer to be loaded from + uint32 pointer_id = 2; + + // A descriptor for an instruction in a block before which the new OpLoad + // instruction should be inserted + InstructionDescriptor instruction_to_insert_before = 3; + +} + message TransformationMergeBlocks { // A transformation that merges a block with its predecessor. @@ -773,6 +909,36 @@ message TransformationOutlineFunction { } +message TransformationPermuteFunctionParameters { + + // A transformation that, given a non-entry-point function taking n + // parameters and a permutation of the set [0, n-1]: + // - Introduces a new function type that is the same as the original + // function's type but with the order of arguments permuted + // (only if it doesn't already exist) + // - Changes the type of the function to this type + // - Adjusts all calls to the function so that their arguments are permuted + + // Function, whose parameters will be permuted + uint32 function_id = 1; + + // |new_type_id| is a result id of a valid OpTypeFunction instruction. + // New type is valid if: + // - it has the same number of operands as the old one + // - function's result type is the same as the old one + // - function's arguments are permuted according to |permutation| vector + uint32 new_type_id = 2; + + // An array of size |n|, where |n| is a number of arguments to a function + // with |function_id|. For each i: 0 <= permutation[i] < n. + // + // i-th element of this array contains a position for an i-th + // function's argument (i.e. i-th argument will be permutation[i]-th + // after running this transformation) + repeated uint32 permutation = 3; + +} + message TransformationReplaceBooleanConstantWithConstantBinary { // A transformation to capture replacing a use of a boolean constant with @@ -919,6 +1085,40 @@ message TransformationSplitBlock { } +message TransformationStore { + + // Transformation that adds an OpStore instruction of an id to a pointer. + + // The pointer to be stored to + uint32 pointer_id = 1; + + // The value to be stored + uint32 value_id = 2; + + // A descriptor for an instruction in a block before which the new OpStore + // instruction should be inserted + InstructionDescriptor instruction_to_insert_before = 3; + +} + +message TransformationSwapCommutableOperands { + + // A transformation that swaps the operands of a commutative instruction. + + // A descriptor for a commutative instruction + InstructionDescriptor instruction_descriptor = 1; + +} + +message TransformationToggleAccessChainInstruction { + + // A transformation that toggles an access chain instruction. + + // A descriptor for an access chain instruction + InstructionDescriptor instruction_descriptor = 1; + +} + message TransformationVectorShuffle { // A transformation that adds a vector shuffle instruction. diff --git a/3rdparty/spirv-tools/source/fuzz/transformation.cpp b/3rdparty/spirv-tools/source/fuzz/transformation.cpp index 8037af15e..f18c86bdd 100644 --- a/3rdparty/spirv-tools/source/fuzz/transformation.cpp +++ b/3rdparty/spirv-tools/source/fuzz/transformation.cpp @@ -17,6 +17,7 @@ #include #include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/transformation_access_chain.h" #include "source/fuzz/transformation_add_constant_boolean.h" #include "source/fuzz/transformation_add_constant_composite.h" #include "source/fuzz/transformation_add_constant_scalar.h" @@ -26,6 +27,7 @@ #include "source/fuzz/transformation_add_function.h" #include "source/fuzz/transformation_add_global_undef.h" #include "source/fuzz/transformation_add_global_variable.h" +#include "source/fuzz/transformation_add_local_variable.h" #include "source/fuzz/transformation_add_no_contraction_decoration.h" #include "source/fuzz/transformation_add_type_array.h" #include "source/fuzz/transformation_add_type_boolean.h" @@ -39,9 +41,13 @@ #include "source/fuzz/transformation_composite_construct.h" #include "source/fuzz/transformation_composite_extract.h" #include "source/fuzz/transformation_copy_object.h" +#include "source/fuzz/transformation_equation_instruction.h" +#include "source/fuzz/transformation_function_call.h" +#include "source/fuzz/transformation_load.h" #include "source/fuzz/transformation_merge_blocks.h" #include "source/fuzz/transformation_move_block_down.h" #include "source/fuzz/transformation_outline_function.h" +#include "source/fuzz/transformation_permute_function_parameters.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_replace_id_with_synonym.h" @@ -50,6 +56,9 @@ #include "source/fuzz/transformation_set_memory_operands_mask.h" #include "source/fuzz/transformation_set_selection_control.h" #include "source/fuzz/transformation_split_block.h" +#include "source/fuzz/transformation_store.h" +#include "source/fuzz/transformation_swap_commutable_operands.h" +#include "source/fuzz/transformation_toggle_access_chain_instruction.h" #include "source/fuzz/transformation_vector_shuffle.h" #include "source/util/make_unique.h" @@ -61,6 +70,8 @@ Transformation::~Transformation() = default; std::unique_ptr Transformation::FromMessage( const protobufs::Transformation& message) { switch (message.transformation_case()) { + case protobufs::Transformation::TransformationCase::kAccessChain: + return MakeUnique(message.access_chain()); case protobufs::Transformation::TransformationCase::kAddConstantBoolean: return MakeUnique( message.add_constant_boolean()); @@ -85,6 +96,9 @@ std::unique_ptr Transformation::FromMessage( case protobufs::Transformation::TransformationCase::kAddGlobalVariable: return MakeUnique( message.add_global_variable()); + case protobufs::Transformation::TransformationCase::kAddLocalVariable: + return MakeUnique( + message.add_local_variable()); case protobufs::Transformation::TransformationCase:: kAddNoContractionDecoration: return MakeUnique( @@ -118,6 +132,13 @@ std::unique_ptr Transformation::FromMessage( message.composite_extract()); case protobufs::Transformation::TransformationCase::kCopyObject: return MakeUnique(message.copy_object()); + case protobufs::Transformation::TransformationCase::kEquationInstruction: + return MakeUnique( + message.equation_instruction()); + case protobufs::Transformation::TransformationCase::kFunctionCall: + return MakeUnique(message.function_call()); + case protobufs::Transformation::TransformationCase::kLoad: + return MakeUnique(message.load()); case protobufs::Transformation::TransformationCase::kMergeBlocks: return MakeUnique(message.merge_blocks()); case protobufs::Transformation::TransformationCase::kMoveBlockDown: @@ -125,6 +146,10 @@ std::unique_ptr Transformation::FromMessage( case protobufs::Transformation::TransformationCase::kOutlineFunction: return MakeUnique( message.outline_function()); + case protobufs::Transformation::TransformationCase:: + kPermuteFunctionParameters: + return MakeUnique( + message.permute_function_parameters()); case protobufs::Transformation::TransformationCase:: kReplaceBooleanConstantWithConstantBinary: return MakeUnique( @@ -150,6 +175,15 @@ std::unique_ptr Transformation::FromMessage( message.set_selection_control()); case protobufs::Transformation::TransformationCase::kSplitBlock: return MakeUnique(message.split_block()); + case protobufs::Transformation::TransformationCase::kStore: + return MakeUnique(message.store()); + case protobufs::Transformation::TransformationCase::kSwapCommutableOperands: + return MakeUnique( + message.swap_commutable_operands()); + case protobufs::Transformation::TransformationCase:: + kToggleAccessChainInstruction: + return MakeUnique( + message.toggle_access_chain_instruction()); case protobufs::Transformation::TransformationCase::kVectorShuffle: return MakeUnique(message.vector_shuffle()); case protobufs::Transformation::TRANSFORMATION_NOT_SET: diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_access_chain.cpp b/3rdparty/spirv-tools/source/fuzz/transformation_access_chain.cpp new file mode 100644 index 000000000..8c3100640 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/transformation_access_chain.cpp @@ -0,0 +1,215 @@ +// 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_access_chain.h" + +#include + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/instruction_descriptor.h" + +namespace spvtools { +namespace fuzz { + +TransformationAccessChain::TransformationAccessChain( + const spvtools::fuzz::protobufs::TransformationAccessChain& message) + : message_(message) {} + +TransformationAccessChain::TransformationAccessChain( + uint32_t fresh_id, uint32_t pointer_id, + const std::vector& index_id, + const protobufs::InstructionDescriptor& instruction_to_insert_before) { + message_.set_fresh_id(fresh_id); + message_.set_pointer_id(pointer_id); + for (auto id : index_id) { + message_.add_index_id(id); + } + *message_.mutable_instruction_to_insert_before() = + instruction_to_insert_before; +} + +bool TransformationAccessChain::IsApplicable( + opt::IRContext* context, + const spvtools::fuzz::FactManager& /*unused*/) const { + // The result id must be fresh + if (!fuzzerutil::IsFreshId(context, message_.fresh_id())) { + return false; + } + // The pointer id must exist and have a type. + auto pointer = context->get_def_use_mgr()->GetDef(message_.pointer_id()); + if (!pointer || !pointer->type_id()) { + return false; + } + // The type must indeed be a pointer + auto pointer_type = context->get_def_use_mgr()->GetDef(pointer->type_id()); + if (pointer_type->opcode() != SpvOpTypePointer) { + return false; + } + + // The described instruction to insert before must exist and be a suitable + // point where an OpAccessChain instruction could be inserted. + auto instruction_to_insert_before = + FindInstruction(message_.instruction_to_insert_before(), context); + if (!instruction_to_insert_before) { + return false; + } + if (!fuzzerutil::CanInsertOpcodeBeforeInstruction( + SpvOpAccessChain, instruction_to_insert_before)) { + return false; + } + + // Do not allow making an access chain from a null or undefined pointer, as + // we do not want to allow accessing such pointers. This might be acceptable + // in dead blocks, but we conservatively avoid it. + switch (pointer->opcode()) { + case SpvOpConstantNull: + case SpvOpUndef: + // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3185): When + // fuzzing for real we would like an 'assert(false)' here. But we also + // want to be able to write negative unit tests. + return false; + default: + break; + } + + // The pointer on which the access chain is to be based needs to be available + // (according to dominance rules) at the insertion point. + if (!fuzzerutil::IdIsAvailableBeforeInstruction( + context, instruction_to_insert_before, message_.pointer_id())) { + return false; + } + + // We now need to use the given indices to walk the type structure of the + // base type of the pointer, making sure that (a) the indices correspond to + // integers, and (b) these integer values are in-bounds. + + // Start from the base type of the pointer. + uint32_t subobject_type_id = pointer_type->GetSingleWordInOperand(1); + + // Consider the given index ids in turn. + for (auto index_id : message_.index_id()) { + // Try to get the integer value associated with this index is. The first + // component of the result will be false if the id did not correspond to an + // integer. Otherwise, the integer with which the id is associated is the + // second component. + std::pair maybe_index_value = + GetIndexValue(context, index_id); + if (!maybe_index_value.first) { + // There was no integer: this index is no good. + return false; + } + // Try to walk down the type using this index. This will yield 0 if the + // type is not a composite or the index is out of bounds, and the id of + // the next type otherwise. + subobject_type_id = fuzzerutil::WalkOneCompositeTypeIndex( + context, subobject_type_id, maybe_index_value.second); + if (!subobject_type_id) { + // Either the type was not a composite (so that too many indices were + // provided), or the index was out of bounds. + return false; + } + } + // At this point, |subobject_type_id| is the type of the value targeted by + // the new access chain. The result type of the access chain should be a + // pointer to this type, with the same storage class as for the original + // pointer. Such a pointer type needs to exist in the module. + // + // We do not use the type manager to look up this type, due to problems + // associated with pointers to isomorphic structs being regarded as the same. + return fuzzerutil::MaybeGetPointerType( + context, subobject_type_id, + static_cast( + pointer_type->GetSingleWordInOperand(0))) != 0; +} + +void TransformationAccessChain::Apply( + opt::IRContext* context, spvtools::fuzz::FactManager* fact_manager) const { + // The operands to the access chain are the pointer followed by the indices. + // The result type of the access chain is determined by where the indices + // lead. We thus push the pointer to a sequence of operands, and then follow + // the indices, pushing each to the operand list and tracking the type + // obtained by following it. Ultimately this yields the type of the + // component reached by following all the indices, and the result type is + // a pointer to this component type. + opt::Instruction::OperandList operands; + + // Add the pointer id itself. + operands.push_back({SPV_OPERAND_TYPE_ID, {message_.pointer_id()}}); + + // Start walking the indices, starting with the pointer's base type. + auto pointer_type = context->get_def_use_mgr()->GetDef( + context->get_def_use_mgr()->GetDef(message_.pointer_id())->type_id()); + uint32_t subobject_type_id = pointer_type->GetSingleWordInOperand(1); + + // Go through the index ids in turn. + for (auto index_id : message_.index_id()) { + // Add the index id to the operands. + operands.push_back({SPV_OPERAND_TYPE_ID, {index_id}}); + // Get the integer value associated with the index id. + uint32_t index_value = GetIndexValue(context, index_id).second; + // Walk to the next type in the composite object using this index. + subobject_type_id = fuzzerutil::WalkOneCompositeTypeIndex( + context, subobject_type_id, index_value); + } + // The access chain's result type is a pointer to the composite component that + // was reached after following all indices. The storage class is that of the + // original pointer. + uint32_t result_type = fuzzerutil::MaybeGetPointerType( + context, subobject_type_id, + static_cast(pointer_type->GetSingleWordInOperand(0))); + + // Add the access chain instruction to the module, and update the module's id + // bound. + fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id()); + FindInstruction(message_.instruction_to_insert_before(), context) + ->InsertBefore( + MakeUnique(context, SpvOpAccessChain, result_type, + message_.fresh_id(), operands)); + + // Conservatively invalidate all analyses. + context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone); + + // If the base pointer's pointee value was irrelevant, the same is true of the + // pointee value of the result of this access chain. + if (fact_manager->PointeeValueIsIrrelevant(message_.pointer_id())) { + fact_manager->AddFactValueOfPointeeIsIrrelevant(message_.fresh_id()); + } +} + +protobufs::Transformation TransformationAccessChain::ToMessage() const { + protobufs::Transformation result; + *result.mutable_access_chain() = message_; + return result; +} + +std::pair TransformationAccessChain::GetIndexValue( + opt::IRContext* context, uint32_t index_id) const { + auto index_instruction = context->get_def_use_mgr()->GetDef(index_id); + if (!index_instruction || !spvOpcodeIsConstant(index_instruction->opcode())) { + // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3179) We could + // allow non-constant indices when looking up non-structs, using clamping + // to ensure they are in-bounds. + return {false, 0}; + } + auto index_type = + context->get_def_use_mgr()->GetDef(index_instruction->type_id()); + if (index_type->opcode() != SpvOpTypeInt || + index_type->GetSingleWordInOperand(0) != 32) { + return {false, 0}; + } + return {true, index_instruction->GetSingleWordInOperand(0)}; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_access_chain.h b/3rdparty/spirv-tools/source/fuzz/transformation_access_chain.h new file mode 100644 index 000000000..92d9e6a6e --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/transformation_access_chain.h @@ -0,0 +1,80 @@ +// 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_ACCESS_CHAIN_H_ +#define SOURCE_FUZZ_TRANSFORMATION_ACCESS_CHAIN_H_ + +#include + +#include "source/fuzz/fact_manager.h" +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" +#include "source/fuzz/transformation.h" +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace fuzz { + +class TransformationAccessChain : public Transformation { + public: + explicit TransformationAccessChain( + const protobufs::TransformationAccessChain& message); + + TransformationAccessChain( + uint32_t fresh_id, uint32_t pointer_id, + const std::vector& index_id, + const protobufs::InstructionDescriptor& instruction_to_insert_before); + + // - |message_.fresh_id| must be fresh + // - |message_.instruction_to_insert_before| must identify an instruction + // before which it is legitimate to insert an OpAccessChain instruction + // - |message_.pointer_id| must be a result id with pointer type that is + // available (according to dominance rules) at the insertion point. + // - The pointer must not be OpConstantNull or OpUndef + // - |message_.index_id| must be a sequence of ids of 32-bit integer constants + // such that it is possible to walk the pointee type of + // |message_.pointer_id| using these indices, remaining in-bounds. + // - If type t is the final type reached by walking these indices, the module + // must include an instruction "OpTypePointer SC %t" where SC is the storage + // class associated with |message_.pointer_id| + bool IsApplicable(opt::IRContext* context, + const FactManager& fact_manager) const override; + + // Adds an instruction of the form: + // |message_.fresh_id| = OpAccessChain %ptr |message_.index_id| + // where %ptr is the result if of an instruction declaring a pointer to the + // type reached by walking the pointee type of |message_.pointer_id| using + // the indices in |message_.index_id|, and with the same storage class as + // |message_.pointer_id|. + // + // If |fact_manager| reports that |message_.pointer_id| has an irrelevant + // pointee value, then the fact that |message_.fresh_id| (the result of the + // access chain) also has an irrelevant pointee value is also recorded. + void Apply(opt::IRContext* context, FactManager* fact_manager) const override; + + protobufs::Transformation ToMessage() const override; + + private: + // Returns {false, 0} if |index_id| does not correspond to a 32-bit integer + // constant. Otherwise, returns {true, value}, where value is the value of + // the 32-bit integer constant to which |index_id| corresponds. + std::pair GetIndexValue(opt::IRContext* context, + uint32_t index_id) const; + + protobufs::TransformationAccessChain message_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_TRANSFORMATION_ACCESS_CHAIN_H_ diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_add_dead_continue.cpp b/3rdparty/spirv-tools/source/fuzz/transformation_add_dead_continue.cpp index ffa182e93..3a4875e3d 100644 --- a/3rdparty/spirv-tools/source/fuzz/transformation_add_dead_continue.cpp +++ b/3rdparty/spirv-tools/source/fuzz/transformation_add_dead_continue.cpp @@ -112,7 +112,7 @@ bool TransformationAddDeadContinue::IsApplicable( // clone, and check whether the transformed clone is valid. // // In principle some of the above checks could be removed, with more reliance - // being places on the validator. This should be revisited if we are sure + // being placed on the validator. This should be revisited if we are sure // the validator is complete with respect to checking structured control flow // rules. auto cloned_context = fuzzerutil::CloneIRContext(context); diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_add_function.cpp b/3rdparty/spirv-tools/source/fuzz/transformation_add_function.cpp index b013d9441..8f0d3c920 100644 --- a/3rdparty/spirv-tools/source/fuzz/transformation_add_function.cpp +++ b/3rdparty/spirv-tools/source/fuzz/transformation_add_function.cpp @@ -132,17 +132,27 @@ bool TransformationAddFunction::IsApplicable( return false; } + // Check whether the cloned module is still valid after adding the function. + // If it is not, the transformation is not applicable. + if (!fuzzerutil::IsValid(cloned_module.get())) { + return false; + } + if (message_.is_livesafe()) { - // We make the cloned module livesafe. if (!TryToMakeFunctionLivesafe(cloned_module.get(), fact_manager)) { return false; } + // After making the function livesafe, we check validity of the module + // again. This is because the turning of OpKill, OpUnreachable and OpReturn + // instructions into branches changes control flow graph reachability, which + // has the potential to make the module invalid when it was otherwise valid. + // It is simpler to rely on the validator to guard against this than to + // consider all scenarios when making a function livesafe. + if (!fuzzerutil::IsValid(cloned_module.get())) { + return false; + } } - - // Having managed to add the new function to the cloned module, and - // potentially also made it livesafe, we ascertain whether the cloned module - // is still valid. If it is, the transformation is applicable. - return fuzzerutil::IsValid(cloned_module.get()); + return true; } void TransformationAddFunction::Apply( @@ -155,7 +165,7 @@ void TransformationAddFunction::Apply( // that |success| is not used). // Record the fact that all pointer parameters and variables declared in the - // function should be regarded as having arbitrary values. This allows other + // function should be regarded as having irrelevant values. This allows other // passes to store arbitrarily to such variables, and to pass them freely as // parameters to other functions knowing that it is OK if they get // over-written. @@ -165,12 +175,12 @@ void TransformationAddFunction::Apply( if (context->get_def_use_mgr() ->GetDef(instruction.result_type_id()) ->opcode() == SpvOpTypePointer) { - fact_manager->AddFactValueOfVariableIsArbitrary( + fact_manager->AddFactValueOfPointeeIsIrrelevant( instruction.result_id()); } break; case SpvOpVariable: - fact_manager->AddFactValueOfVariableIsArbitrary( + fact_manager->AddFactValueOfPointeeIsIrrelevant( instruction.result_id()); break; default: diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_add_global_variable.cpp b/3rdparty/spirv-tools/source/fuzz/transformation_add_global_variable.cpp index c08517f9a..e4f9f7ad6 100644 --- a/3rdparty/spirv-tools/source/fuzz/transformation_add_global_variable.cpp +++ b/3rdparty/spirv-tools/source/fuzz/transformation_add_global_variable.cpp @@ -25,11 +25,11 @@ TransformationAddGlobalVariable::TransformationAddGlobalVariable( TransformationAddGlobalVariable::TransformationAddGlobalVariable( uint32_t fresh_id, uint32_t type_id, uint32_t initializer_id, - bool value_is_arbitrary) { + bool value_is_irrelevant) { message_.set_fresh_id(fresh_id); message_.set_type_id(type_id); message_.set_initializer_id(initializer_id); - message_.set_value_is_arbitrary(value_is_arbitrary); + message_.set_value_is_irrelevant(value_is_irrelevant); } bool TransformationAddGlobalVariable::IsApplicable( @@ -53,21 +53,19 @@ bool TransformationAddGlobalVariable::IsApplicable( if (pointer_type->storage_class() != SpvStorageClassPrivate) { return false; } - if (message_.initializer_id()) { - // The initializer id must be the id of a constant. Check this with the - // constant manager. - auto constant_id = context->get_constant_mgr()->GetConstantsFromIds( - {message_.initializer_id()}); - if (constant_id.empty()) { - return false; - } - assert(constant_id.size() == 1 && - "We asked for the constant associated with a single id; we should " - "get a single constant."); - // The type of the constant must match the pointee type of the pointer. - if (pointer_type->pointee_type() != constant_id[0]->type()) { - return false; - } + // The initializer id must be the id of a constant. Check this with the + // constant manager. + auto constant_id = context->get_constant_mgr()->GetConstantsFromIds( + {message_.initializer_id()}); + if (constant_id.empty()) { + return false; + } + assert(constant_id.size() == 1 && + "We asked for the constant associated with a single id; we should " + "get a single constant."); + // The type of the constant must match the pointee type of the pointer. + if (pointer_type->pointee_type() != constant_id[0]->type()) { + return false; } return true; } @@ -101,8 +99,8 @@ void TransformationAddGlobalVariable::Apply( } } - if (message_.value_is_arbitrary()) { - fact_manager->AddFactValueOfVariableIsArbitrary(message_.fresh_id()); + if (message_.value_is_irrelevant()) { + fact_manager->AddFactValueOfPointeeIsIrrelevant(message_.fresh_id()); } // We have added an instruction to the module, so need to be careful about the diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_add_global_variable.h b/3rdparty/spirv-tools/source/fuzz/transformation_add_global_variable.h index 406c91571..920ac45dc 100644 --- a/3rdparty/spirv-tools/source/fuzz/transformation_add_global_variable.h +++ b/3rdparty/spirv-tools/source/fuzz/transformation_add_global_variable.h @@ -30,7 +30,7 @@ class TransformationAddGlobalVariable : public Transformation { TransformationAddGlobalVariable(uint32_t fresh_id, uint32_t type_id, uint32_t initializer_id, - bool value_is_arbitrary); + bool value_is_irrelevant); // - |message_.fresh_id| must be fresh // - |message_.type_id| must be the id of a pointer type with Private storage @@ -44,6 +44,9 @@ class TransformationAddGlobalVariable : public Transformation { // |message_.type_id| and either no initializer or |message_.initializer_id| // as an initializer, depending on whether |message_.initializer_id| is 0. // The global variable has result id |message_.fresh_id|. + // + // If |message_.value_is_irrelevant| holds, adds a corresponding fact to + // |fact_manager|. void Apply(opt::IRContext* context, FactManager* fact_manager) const override; protobufs::Transformation ToMessage() const override; diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_add_local_variable.cpp b/3rdparty/spirv-tools/source/fuzz/transformation_add_local_variable.cpp new file mode 100644 index 000000000..69e536df1 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/transformation_add_local_variable.cpp @@ -0,0 +1,98 @@ +// 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_local_variable.h" + +#include "source/fuzz/fuzzer_util.h" + +namespace spvtools { +namespace fuzz { + +TransformationAddLocalVariable::TransformationAddLocalVariable( + const spvtools::fuzz::protobufs::TransformationAddLocalVariable& message) + : message_(message) {} + +TransformationAddLocalVariable::TransformationAddLocalVariable( + uint32_t fresh_id, uint32_t type_id, uint32_t function_id, + uint32_t initializer_id, bool value_is_irrelevant) { + message_.set_fresh_id(fresh_id); + message_.set_type_id(type_id); + message_.set_function_id(function_id); + message_.set_initializer_id(initializer_id); + message_.set_value_is_irrelevant(value_is_irrelevant); +} + +bool TransformationAddLocalVariable::IsApplicable( + opt::IRContext* context, + const spvtools::fuzz::FactManager& /*unused*/) const { + // The provided id must be fresh. + if (!fuzzerutil::IsFreshId(context, message_.fresh_id())) { + return false; + } + // The pointer type id must indeed correspond to a pointer, and it must have + // function storage class. + auto type_instruction = + context->get_def_use_mgr()->GetDef(message_.type_id()); + if (!type_instruction || type_instruction->opcode() != SpvOpTypePointer || + type_instruction->GetSingleWordInOperand(0) != SpvStorageClassFunction) { + return false; + } + // The initializer must... + auto initializer_instruction = + context->get_def_use_mgr()->GetDef(message_.initializer_id()); + // ... exist, ... + if (!initializer_instruction) { + return false; + } + // ... be a constant, ... + if (!spvOpcodeIsConstant(initializer_instruction->opcode())) { + return false; + } + // ... and have the same type as the pointee type. + if (initializer_instruction->type_id() != + type_instruction->GetSingleWordInOperand(1)) { + return false; + } + // The function to which the local variable is to be added must exist. + return fuzzerutil::FindFunction(context, message_.function_id()); +} + +void TransformationAddLocalVariable::Apply( + opt::IRContext* context, spvtools::fuzz::FactManager* fact_manager) const { + fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id()); + fuzzerutil::FindFunction(context, message_.function_id()) + ->begin() + ->begin() + ->InsertBefore(MakeUnique( + context, SpvOpVariable, message_.type_id(), message_.fresh_id(), + opt::Instruction::OperandList( + {{SPV_OPERAND_TYPE_STORAGE_CLASS, + { + + SpvStorageClassFunction}}, + {SPV_OPERAND_TYPE_ID, {message_.initializer_id()}}}))); + if (message_.value_is_irrelevant()) { + fact_manager->AddFactValueOfPointeeIsIrrelevant(message_.fresh_id()); + } + context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone); +} + +protobufs::Transformation TransformationAddLocalVariable::ToMessage() const { + protobufs::Transformation result; + *result.mutable_add_local_variable() = message_; + return result; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_add_local_variable.h b/3rdparty/spirv-tools/source/fuzz/transformation_add_local_variable.h new file mode 100644 index 000000000..b8e00ddfb --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/transformation_add_local_variable.h @@ -0,0 +1,60 @@ +// 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_LOCAL_VARIABLE_H_ +#define SOURCE_FUZZ_TRANSFORMATION_ADD_LOCAL_VARIABLE_H_ + +#include "source/fuzz/fact_manager.h" +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" +#include "source/fuzz/transformation.h" +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace fuzz { + +class TransformationAddLocalVariable : public Transformation { + public: + explicit TransformationAddLocalVariable( + const protobufs::TransformationAddLocalVariable& message); + + TransformationAddLocalVariable(uint32_t fresh_id, uint32_t type_id, + uint32_t function_id, uint32_t initializer_id, + bool value_is_irrelevant); + + // - |message_.fresh_id| must not be used by the module + // - |message_.type_id| must be the id of a pointer type with Function + // storage class + // - |message_.initializer_id| must be the id of a constant with the same + // type as the pointer's pointee type + // - |message_.function_id| must be the id of a function + bool IsApplicable(opt::IRContext* context, + const FactManager& fact_manager) const override; + + // Adds an instruction to the start of |message_.function_id|, of the form: + // |message_.fresh_id| = OpVariable |message_.type_id| Function + // |message_.initializer_id| + // If |message_.value_is_irrelevant| holds, adds a corresponding fact to + // |fact_manager|. + void Apply(opt::IRContext* context, FactManager* fact_manager) const override; + + protobufs::Transformation ToMessage() const override; + + private: + protobufs::TransformationAddLocalVariable message_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_TRANSFORMATION_ADD_LOCAL_VARIABLE_H_ diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_add_type_function.h b/3rdparty/spirv-tools/source/fuzz/transformation_add_type_function.h index 2b596613a..3880963d0 100644 --- a/3rdparty/spirv-tools/source/fuzz/transformation_add_type_function.h +++ b/3rdparty/spirv-tools/source/fuzz/transformation_add_type_function.h @@ -37,7 +37,7 @@ class TransformationAddTypeFunction : public Transformation { // - |message_.return_type_id| and each element of |message_.argument_type_id| // must be the ids of non-function types // - The module must not contain an OpTypeFunction instruction defining a - // function type with the signature provided by teh given return and + // function type with the signature provided by the given return and // argument types bool IsApplicable(opt::IRContext* context, const FactManager& fact_manager) const override; diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_composite_construct.cpp b/3rdparty/spirv-tools/source/fuzz/transformation_composite_construct.cpp index 7a3aff145..9c63c1d98 100644 --- a/3rdparty/spirv-tools/source/fuzz/transformation_composite_construct.cpp +++ b/3rdparty/spirv-tools/source/fuzz/transformation_composite_construct.cpp @@ -84,27 +84,8 @@ bool TransformationCompositeConstruct::IsApplicable( // Now check whether every component being used to initialize the composite is // available at the desired program point. for (auto& component : message_.component()) { - auto component_inst = context->get_def_use_mgr()->GetDef(component); - if (!context->get_instr_block(component)) { - // The component does not have a block; that means it is in global scope, - // which is OK. (Whether the component actually corresponds to an - // instruction is checked above when determining whether types are - // suitable.) - continue; - } - // Check whether the component is available. - if (insert_before->HasResultId() && - insert_before->result_id() == component) { - // This constitutes trying to use an id right before it is defined. The - // special case is needed due to an instruction always dominating itself. - return false; - } - if (!context - ->GetDominatorAnalysis( - context->get_instr_block(&*insert_before)->GetParent()) - ->Dominates(component_inst, &*insert_before)) { - // The instruction defining the component must dominate the instruction we - // wish to insert the composite before. + if (!fuzzerutil::IdIsAvailableBeforeInstruction(context, insert_before, + component)) { return false; } } diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_copy_object.cpp b/3rdparty/spirv-tools/source/fuzz/transformation_copy_object.cpp index af1e81c37..bfdced37f 100644 --- a/3rdparty/spirv-tools/source/fuzz/transformation_copy_object.cpp +++ b/3rdparty/spirv-tools/source/fuzz/transformation_copy_object.cpp @@ -64,20 +64,10 @@ bool TransformationCopyObject::IsApplicable( return false; } - // |message_object| must be available at the point where we want to add the - // copy. It is available if it is at global scope (in which case it has no - // block), or if it dominates the point of insertion but is different from the - // point of insertion. - // - // The reason why the object needs to be different from the insertion point is - // that the copy will be added *before* this point, and we do not want to - // insert it before the object's defining instruction. - return !context->get_instr_block(object_inst) || - (object_inst != &*insert_before && - context - ->GetDominatorAnalysis( - context->get_instr_block(insert_before)->GetParent()) - ->Dominates(object_inst, &*insert_before)); + // |message_object| must be available directly before the point where we want + // to add the copy. + return fuzzerutil::IdIsAvailableBeforeInstruction(context, insert_before, + message_.object()); } void TransformationCopyObject::Apply(opt::IRContext* context, @@ -105,6 +95,10 @@ void TransformationCopyObject::Apply(opt::IRContext* context, fact_manager->AddFactDataSynonym(MakeDataDescriptor(message_.object(), {}), MakeDataDescriptor(message_.fresh_id(), {}), context); + + if (fact_manager->PointeeValueIsIrrelevant(message_.object())) { + fact_manager->AddFactValueOfPointeeIsIrrelevant(message_.fresh_id()); + } } protobufs::Transformation TransformationCopyObject::ToMessage() const { diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_copy_object.h b/3rdparty/spirv-tools/source/fuzz/transformation_copy_object.h index ac5e978df..9e9c26a34 100644 --- a/3rdparty/spirv-tools/source/fuzz/transformation_copy_object.h +++ b/3rdparty/spirv-tools/source/fuzz/transformation_copy_object.h @@ -57,7 +57,10 @@ class TransformationCopyObject : public Transformation { // is added directly before the instruction at |message_.insert_after_id| + // |message_|.offset, where %ty is the type of |message_.object|. // - The fact that |message_.fresh_id| and |message_.object| are synonyms - // is added to the fact manager. + // is added to |fact_manager|. + // - If |message_.object| is a pointer whose pointee value is known to be + // irrelevant, the analogous fact is added to |fact_manager| about + // |message_.fresh_id|. void Apply(opt::IRContext* context, FactManager* fact_manager) const override; protobufs::Transformation ToMessage() const override; diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_equation_instruction.cpp b/3rdparty/spirv-tools/source/fuzz/transformation_equation_instruction.cpp new file mode 100644 index 000000000..21b67f66e --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/transformation_equation_instruction.cpp @@ -0,0 +1,186 @@ +// 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_equation_instruction.h" + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/instruction_descriptor.h" + +namespace spvtools { +namespace fuzz { + +TransformationEquationInstruction::TransformationEquationInstruction( + const spvtools::fuzz::protobufs::TransformationEquationInstruction& message) + : message_(message) {} + +TransformationEquationInstruction::TransformationEquationInstruction( + uint32_t fresh_id, SpvOp opcode, const std::vector& in_operand_id, + const protobufs::InstructionDescriptor& instruction_to_insert_before) { + message_.set_fresh_id(fresh_id); + message_.set_opcode(opcode); + for (auto id : in_operand_id) { + message_.add_in_operand_id(id); + } + *message_.mutable_instruction_to_insert_before() = + instruction_to_insert_before; +} + +bool TransformationEquationInstruction::IsApplicable( + opt::IRContext* context, + const spvtools::fuzz::FactManager& /*unused*/) const { + // The result id must be fresh. + if (!fuzzerutil::IsFreshId(context, message_.fresh_id())) { + return false; + } + // The instruction to insert before must exist. + auto insert_before = + FindInstruction(message_.instruction_to_insert_before(), context); + if (!insert_before) { + return false; + } + // The input ids must all exist, not be OpUndef, and be available before this + // instruction. + for (auto id : message_.in_operand_id()) { + auto inst = context->get_def_use_mgr()->GetDef(id); + if (!inst) { + return false; + } + if (inst->opcode() == SpvOpUndef) { + return false; + } + if (!fuzzerutil::IdIsAvailableBeforeInstruction(context, insert_before, + id)) { + return false; + } + } + + return MaybeGetResultType(context) != 0; +} + +void TransformationEquationInstruction::Apply( + opt::IRContext* context, spvtools::fuzz::FactManager* fact_manager) const { + fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id()); + + opt::Instruction::OperandList in_operands; + std::vector rhs_id; + for (auto id : message_.in_operand_id()) { + in_operands.push_back({SPV_OPERAND_TYPE_ID, {id}}); + rhs_id.push_back(id); + } + + FindInstruction(message_.instruction_to_insert_before(), context) + ->InsertBefore(MakeUnique( + context, static_cast(message_.opcode()), + MaybeGetResultType(context), message_.fresh_id(), in_operands)); + + context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone); + + fact_manager->AddFactIdEquation(message_.fresh_id(), + static_cast(message_.opcode()), rhs_id, + context); +} + +protobufs::Transformation TransformationEquationInstruction::ToMessage() const { + protobufs::Transformation result; + *result.mutable_equation_instruction() = message_; + return result; +} + +uint32_t TransformationEquationInstruction::MaybeGetResultType( + opt::IRContext* context) const { + switch (static_cast(message_.opcode())) { + case SpvOpIAdd: + case SpvOpISub: { + if (message_.in_operand_id().size() != 2) { + return 0; + } + uint32_t first_operand_width = 0; + uint32_t first_operand_type_id = 0; + for (uint32_t index = 0; index < 2; index++) { + auto operand_inst = + context->get_def_use_mgr()->GetDef(message_.in_operand_id(index)); + if (!operand_inst || !operand_inst->type_id()) { + return 0; + } + auto operand_type = + context->get_type_mgr()->GetType(operand_inst->type_id()); + if (!(operand_type->AsInteger() || + (operand_type->AsVector() && + operand_type->AsVector()->element_type()->AsInteger()))) { + return 0; + } + uint32_t operand_width = + operand_type->AsInteger() + ? 1 + : operand_type->AsVector()->element_count(); + if (index == 0) { + first_operand_width = operand_width; + first_operand_type_id = operand_inst->type_id(); + } else { + assert(first_operand_width != 0 && + "The first operand should have been processed."); + if (operand_width != first_operand_width) { + return 0; + } + } + } + assert(first_operand_type_id != 0 && + "A type must have been found for the first operand."); + return first_operand_type_id; + } + case SpvOpLogicalNot: { + if (message_.in_operand_id().size() != 1) { + return 0; + } + auto operand_inst = + context->get_def_use_mgr()->GetDef(message_.in_operand_id(0)); + if (!operand_inst || !operand_inst->type_id()) { + return 0; + } + auto operand_type = + context->get_type_mgr()->GetType(operand_inst->type_id()); + if (!(operand_type->AsBool() || + (operand_type->AsVector() && + operand_type->AsVector()->element_type()->AsBool()))) { + return 0; + } + return operand_inst->type_id(); + } + case SpvOpSNegate: { + if (message_.in_operand_id().size() != 1) { + return 0; + } + auto operand_inst = + context->get_def_use_mgr()->GetDef(message_.in_operand_id(0)); + if (!operand_inst || !operand_inst->type_id()) { + return 0; + } + auto operand_type = + context->get_type_mgr()->GetType(operand_inst->type_id()); + if (!(operand_type->AsInteger() || + (operand_type->AsVector() && + operand_type->AsVector()->element_type()->AsInteger()))) { + return 0; + } + return operand_inst->type_id(); + } + + default: + assert(false && "Inappropriate opcode for equation instruction."); + return 0; + } +} + +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_equation_instruction.h b/3rdparty/spirv-tools/source/fuzz/transformation_equation_instruction.h new file mode 100644 index 000000000..2456ba509 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/transformation_equation_instruction.h @@ -0,0 +1,76 @@ +// 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_EQUATION_INSTRUCTION_H_ +#define SOURCE_FUZZ_TRANSFORMATION_EQUATION_INSTRUCTION_H_ + +#include + +#include "source/fuzz/fact_manager.h" +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" +#include "source/fuzz/transformation.h" +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace fuzz { + +class TransformationEquationInstruction : public Transformation { + public: + explicit TransformationEquationInstruction( + const protobufs::TransformationEquationInstruction& message); + + TransformationEquationInstruction( + uint32_t fresh_id, SpvOp opcode, + const std::vector& in_operand_id, + const protobufs::InstructionDescriptor& instruction_to_insert_before); + + // - |message_.fresh_id| must be fresh. + // - |message_.instruction_to_insert_before| must identify an instruction + // before which an equation instruction can legitimately be inserted. + // - Each id in |message_.in_operand_id| must exist, not be an OpUndef, and + // be available before |message_.instruction_to_insert_before|. + // - |message_.opcode| must be an opcode for which we know how to handle + // equations, the types of the ids in |message_.in_operand_id| must be + // suitable for use with this opcode, and the module must contain an + // appropriate result type id. + bool IsApplicable(opt::IRContext* context, + const FactManager& fact_manager) const override; + + // Adds an instruction to the module, right before + // |message_.instruction_to_insert_before|, of the form: + // + // |message_.fresh_id| = |message_.opcode| %type |message_.in_operand_ids| + // + // where %type is a type id that already exists in the module and that is + // compatible with the opcode and input operands. + // + // The fact manager is also updated to inform it of this equation fact. + void Apply(opt::IRContext* context, FactManager* fact_manager) const override; + + protobufs::Transformation ToMessage() const override; + + private: + // A helper that, in one fell swoop, checks that |message_.opcode| and the ids + // in |message_.in_operand_id| are compatible, and that the module contains + // an appropriate result type id. If all is well, the result type id is + // returned. Otherwise, 0 is returned. + uint32_t MaybeGetResultType(opt::IRContext* context) const; + + protobufs::TransformationEquationInstruction message_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_TRANSFORMATION_EQUATION_INSTRUCTION_H_ diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_function_call.cpp b/3rdparty/spirv-tools/source/fuzz/transformation_function_call.cpp new file mode 100644 index 000000000..cea853715 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/transformation_function_call.cpp @@ -0,0 +1,185 @@ +// 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_function_call.h" + +#include "source/fuzz/call_graph.h" +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/instruction_descriptor.h" + +namespace spvtools { +namespace fuzz { + +TransformationFunctionCall::TransformationFunctionCall( + const spvtools::fuzz::protobufs::TransformationFunctionCall& message) + : message_(message) {} + +TransformationFunctionCall::TransformationFunctionCall( + uint32_t fresh_id, uint32_t callee_id, + const std::vector& argument_id, + const protobufs::InstructionDescriptor& instruction_to_insert_before) { + message_.set_fresh_id(fresh_id); + message_.set_callee_id(callee_id); + for (auto argument : argument_id) { + message_.add_argument_id(argument); + } + *message_.mutable_instruction_to_insert_before() = + instruction_to_insert_before; +} + +bool TransformationFunctionCall::IsApplicable( + opt::IRContext* context, + const spvtools::fuzz::FactManager& fact_manager) const { + // The result id must be fresh + if (!fuzzerutil::IsFreshId(context, message_.fresh_id())) { + return false; + } + + // The function must exist + auto callee_inst = context->get_def_use_mgr()->GetDef(message_.callee_id()); + if (!callee_inst || callee_inst->opcode() != SpvOpFunction) { + return false; + } + + // The function must not be an entry point + if (fuzzerutil::FunctionIsEntryPoint(context, message_.callee_id())) { + return false; + } + + auto callee_type_inst = context->get_def_use_mgr()->GetDef( + callee_inst->GetSingleWordInOperand(1)); + assert(callee_type_inst->opcode() == SpvOpTypeFunction && + "Bad function type."); + + // The number of expected function arguments must match the number of given + // arguments. The number of expected arguments is one less than the function + // type's number of input operands, as one operand is for the return type. + if (callee_type_inst->NumInOperands() - 1 != + static_cast(message_.argument_id().size())) { + return false; + } + + // The instruction descriptor must refer to a position where it is valid to + // insert the call + auto insert_before = + FindInstruction(message_.instruction_to_insert_before(), context); + if (!insert_before) { + return false; + } + if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpFunctionCall, + insert_before)) { + return false; + } + + auto block = context->get_instr_block(insert_before); + auto enclosing_function = block->GetParent(); + + // If the block is not dead, the function must be livesafe + bool block_is_dead = fact_manager.BlockIsDead(block->id()); + if (!block_is_dead && + !fact_manager.FunctionIsLivesafe(message_.callee_id())) { + return false; + } + + // The ids must all match and have the right types and satisfy rules on + // pointers. If the block is not dead, pointers must be arbitrary. + for (uint32_t arg_index = 0; + arg_index < static_cast(message_.argument_id().size()); + arg_index++) { + opt::Instruction* arg_inst = + context->get_def_use_mgr()->GetDef(message_.argument_id(arg_index)); + if (!arg_inst) { + // The given argument does not correspond to an instruction. + return false; + } + if (!arg_inst->type_id()) { + // The given argument does not have a type; it is thus not suitable. + } + if (arg_inst->type_id() != + callee_type_inst->GetSingleWordInOperand(arg_index + 1)) { + // Argument type mismatch. + return false; + } + opt::Instruction* arg_type_inst = + context->get_def_use_mgr()->GetDef(arg_inst->type_id()); + if (arg_type_inst->opcode() == SpvOpTypePointer) { + switch (arg_inst->opcode()) { + case SpvOpFunctionParameter: + case SpvOpVariable: + // These are OK + break; + default: + // Other pointer ids cannot be passed as parameters + return false; + } + if (!block_is_dead && + !fact_manager.PointeeValueIsIrrelevant(arg_inst->result_id())) { + // This is not a dead block, so pointer parameters passed to the called + // function might really have their contents modified. We thus require + // such pointers to be to arbitrary-valued variables, which this is not. + return false; + } + } + + // The argument id needs to be available (according to dominance rules) at + // the point where the call will occur. + if (!fuzzerutil::IdIsAvailableBeforeInstruction(context, insert_before, + arg_inst->result_id())) { + return false; + } + } + + // Introducing the call must not lead to recursion. + if (message_.callee_id() == enclosing_function->result_id()) { + // This would be direct recursion. + return false; + } + // Ensure the call would not lead to indirect recursion. + return !CallGraph(context) + .GetIndirectCallees(message_.callee_id()) + .count(block->GetParent()->result_id()); +} + +void TransformationFunctionCall::Apply( + opt::IRContext* context, spvtools::fuzz::FactManager* /*unused*/) const { + // Update the module's bound to reflect the fresh id for the result of the + // function call. + fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id()); + // Get the return type of the function being called. + uint32_t return_type = + context->get_def_use_mgr()->GetDef(message_.callee_id())->type_id(); + // Populate the operands to the call instruction, with the function id and the + // arguments. + opt::Instruction::OperandList operands; + operands.push_back({SPV_OPERAND_TYPE_ID, {message_.callee_id()}}); + for (auto arg : message_.argument_id()) { + operands.push_back({SPV_OPERAND_TYPE_ID, {arg}}); + } + // Insert the function call before the instruction specified in the message. + FindInstruction(message_.instruction_to_insert_before(), context) + ->InsertBefore( + MakeUnique(context, SpvOpFunctionCall, return_type, + message_.fresh_id(), operands)); + // Invalidate all analyses since we have changed the module. + context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone); +} + +protobufs::Transformation TransformationFunctionCall::ToMessage() const { + protobufs::Transformation result; + *result.mutable_function_call() = message_; + return result; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_function_call.h b/3rdparty/spirv-tools/source/fuzz/transformation_function_call.h new file mode 100644 index 000000000..a9ae5bee3 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/transformation_function_call.h @@ -0,0 +1,65 @@ +// 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_FUNCTION_CALL_H_ +#define SOURCE_FUZZ_TRANSFORMATION_FUNCTION_CALL_H_ + +#include "source/fuzz/fact_manager.h" +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" +#include "source/fuzz/transformation.h" +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace fuzz { + +class TransformationFunctionCall : public Transformation { + public: + explicit TransformationFunctionCall( + const protobufs::TransformationFunctionCall& message); + + TransformationFunctionCall( + uint32_t fresh_id, uint32_t callee_id, + const std::vector& argument_id, + const protobufs::InstructionDescriptor& instruction_to_insert_before); + + // - |message_.fresh_id| must be fresh + // - |message_.instruction_to_insert_before| must identify an instruction + // before which an OpFunctionCall can be legitimately inserted + // - |message_.function_id| must be the id of a function, and calling the + // function before the identified instruction must not introduce recursion + // - |message_.arg_id| must provide suitable arguments for the function call + // (they must have the right types and be available according to dominance + // rules) + // - If the insertion point is not in a dead block then |message_function_id| + // must refer to a livesafe function, and every pointer argument in + // |message_.arg_id| must refer to an arbitrary-valued variable + bool IsApplicable(opt::IRContext* context, + const FactManager& fact_manager) const override; + + // Adds an instruction of the form: + // |fresh_id| = OpFunctionCall %type |callee_id| |arg_id...| + // before |instruction_to_insert_before|, where %type is the return type of + // |callee_id|. + void Apply(opt::IRContext* context, FactManager* fact_manager) const override; + + protobufs::Transformation ToMessage() const override; + + private: + protobufs::TransformationFunctionCall message_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_TRANSFORMATION_FUNCTION_CALL_H_ diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_load.cpp b/3rdparty/spirv-tools/source/fuzz/transformation_load.cpp new file mode 100644 index 000000000..4cba37da4 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/transformation_load.cpp @@ -0,0 +1,103 @@ +// 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_load.h" + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/instruction_descriptor.h" + +namespace spvtools { +namespace fuzz { + +TransformationLoad::TransformationLoad( + const spvtools::fuzz::protobufs::TransformationLoad& message) + : message_(message) {} + +TransformationLoad::TransformationLoad( + uint32_t fresh_id, uint32_t pointer_id, + const protobufs::InstructionDescriptor& instruction_to_insert_before) { + message_.set_fresh_id(fresh_id); + message_.set_pointer_id(pointer_id); + *message_.mutable_instruction_to_insert_before() = + instruction_to_insert_before; +} + +bool TransformationLoad::IsApplicable( + opt::IRContext* context, + const spvtools::fuzz::FactManager& /*unused*/) const { + // The result id must be fresh. + if (!fuzzerutil::IsFreshId(context, message_.fresh_id())) { + return false; + } + + // The pointer must exist and have a type. + auto pointer = context->get_def_use_mgr()->GetDef(message_.pointer_id()); + if (!pointer || !pointer->type_id()) { + return false; + } + // The type must indeed be a pointer type. + auto pointer_type = context->get_def_use_mgr()->GetDef(pointer->type_id()); + assert(pointer_type && "Type id must be defined."); + if (pointer_type->opcode() != SpvOpTypePointer) { + return false; + } + // We do not want to allow loading from null or undefined pointers, as it is + // not clear how punishing the consequences of doing so are from a semantics + // point of view. + switch (pointer->opcode()) { + case SpvOpConstantNull: + case SpvOpUndef: + return false; + default: + break; + } + + // Determine which instruction we should be inserting before. + auto insert_before = + FindInstruction(message_.instruction_to_insert_before(), context); + // It must exist, ... + if (!insert_before) { + return false; + } + // ... and it must be legitimate to insert a store before it. + if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpLoad, insert_before)) { + return false; + } + + // The pointer needs to be available at the insertion point. + return fuzzerutil::IdIsAvailableBeforeInstruction(context, insert_before, + message_.pointer_id()); +} + +void TransformationLoad::Apply(opt::IRContext* context, + spvtools::fuzz::FactManager* /*unused*/) const { + uint32_t result_type = fuzzerutil::GetPointeeTypeIdFromPointerType( + context, fuzzerutil::GetTypeId(context, message_.pointer_id())); + fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id()); + FindInstruction(message_.instruction_to_insert_before(), context) + ->InsertBefore(MakeUnique( + context, SpvOpLoad, result_type, message_.fresh_id(), + opt::Instruction::OperandList( + {{SPV_OPERAND_TYPE_ID, {message_.pointer_id()}}}))); + context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone); +} + +protobufs::Transformation TransformationLoad::ToMessage() const { + protobufs::Transformation result; + *result.mutable_load() = message_; + return result; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_load.h b/3rdparty/spirv-tools/source/fuzz/transformation_load.h new file mode 100644 index 000000000..ff9901673 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/transformation_load.h @@ -0,0 +1,59 @@ +// 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_LOAD_H_ +#define SOURCE_FUZZ_TRANSFORMATION_LOAD_H_ + +#include "source/fuzz/fact_manager.h" +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" +#include "source/fuzz/transformation.h" +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace fuzz { + +class TransformationLoad : public Transformation { + public: + explicit TransformationLoad(const protobufs::TransformationLoad& message); + + TransformationLoad( + uint32_t fresh_id, uint32_t pointer_id, + const protobufs::InstructionDescriptor& instruction_to_insert_before); + + // - |message_.fresh_id| must be fresh + // - |message_.pointer_id| must be the id of a pointer + // - The pointer must not be OpConstantNull or OpUndef + // - |message_.instruction_to_insert_before| must identify an instruction + // before which it is valid to insert an OpLoad, and where + // |message_.pointer_id| is available (according to dominance rules) + bool IsApplicable(opt::IRContext* context, + const FactManager& fact_manager) const override; + + // Adds an instruction of the form: + // |message_.fresh_id| = OpLoad %type |message_.pointer_id| + // before the instruction identified by + // |message_.instruction_to_insert_before|, where %type is the pointer's + // pointee type. + void Apply(opt::IRContext* context, FactManager* fact_manager) const override; + + protobufs::Transformation ToMessage() const override; + + private: + protobufs::TransformationLoad message_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_TRANSFORMATION_LOAD_H_ diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_outline_function.cpp b/3rdparty/spirv-tools/source/fuzz/transformation_outline_function.cpp index 7bbac5483..01d1c4583 100644 --- a/3rdparty/spirv-tools/source/fuzz/transformation_outline_function.cpp +++ b/3rdparty/spirv-tools/source/fuzz/transformation_outline_function.cpp @@ -617,10 +617,10 @@ TransformationOutlineFunction::PrepareFunctionPrototype( context, SpvOpFunctionParameter, context->get_def_use_mgr()->GetDef(id)->type_id(), input_id_to_fresh_id_map.at(id), opt::Instruction::OperandList())); - // If the input id is an arbitrary-valued variable, the same should be true + // If the input id is an irrelevant-valued variable, the same should be true // of the corresponding parameter. - if (fact_manager->VariableValueIsArbitrary(id)) { - fact_manager->AddFactValueOfVariableIsArbitrary( + if (fact_manager->PointeeValueIsIrrelevant(id)) { + fact_manager->AddFactValueOfPointeeIsIrrelevant( input_id_to_fresh_id_map.at(id)); } } diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_permute_function_parameters.cpp b/3rdparty/spirv-tools/source/fuzz/transformation_permute_function_parameters.cpp new file mode 100644 index 000000000..214153355 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/transformation_permute_function_parameters.cpp @@ -0,0 +1,184 @@ +// Copyright (c) 2020 Vasyl Teliman +// +// 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 +#include + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/transformation_permute_function_parameters.h" + +namespace spvtools { +namespace fuzz { + +TransformationPermuteFunctionParameters:: + TransformationPermuteFunctionParameters( + const spvtools::fuzz::protobufs:: + TransformationPermuteFunctionParameters& message) + : message_(message) {} + +TransformationPermuteFunctionParameters:: + TransformationPermuteFunctionParameters( + uint32_t function_id, uint32_t new_type_id, + const std::vector& permutation) { + message_.set_function_id(function_id); + message_.set_new_type_id(new_type_id); + + for (auto index : permutation) { + message_.add_permutation(index); + } +} + +bool TransformationPermuteFunctionParameters::IsApplicable( + opt::IRContext* context, const FactManager& /*unused*/) const { + // Check that function exists + const auto* function = + fuzzerutil::FindFunction(context, message_.function_id()); + if (!function || function->DefInst().opcode() != SpvOpFunction || + fuzzerutil::FunctionIsEntryPoint(context, function->result_id())) { + return false; + } + + // Check that permutation has valid indices + const auto* function_type = fuzzerutil::GetFunctionType(context, function); + assert(function_type && "Function type is null"); + + const auto& permutation = message_.permutation(); + + // Don't take return type into account + auto arg_size = function_type->NumInOperands() - 1; + + // |permutation| vector should be equal to the number of arguments + if (static_cast(permutation.size()) != arg_size) { + return false; + } + + // Check that all indices are valid + // and unique integers from the [0, n-1] set + std::unordered_set unique_indices; + for (auto index : permutation) { + // We don't compare |index| with 0 since it's an unsigned integer + if (index >= arg_size) { + return false; + } + + unique_indices.insert(index); + } + + // Check that permutation doesn't have duplicated values + assert(unique_indices.size() == arg_size && "Permutation has duplicates"); + + // Check that new function's type is valid: + // - Has the same number of operands + // - Has the same result type as the old one + // - Order of arguments is permuted + auto new_type_id = message_.new_type_id(); + const auto* new_type = context->get_def_use_mgr()->GetDef(new_type_id); + + if (!new_type || new_type->opcode() != SpvOpTypeFunction || + new_type->NumInOperands() != function_type->NumInOperands()) { + return false; + } + + // Check that both instructions have the same result type + if (new_type->GetSingleWordInOperand(0) != + function_type->GetSingleWordInOperand(0)) { + return false; + } + + // Check that new function type has its arguments permuted + for (int i = 0, n = static_cast(permutation.size()); i < n; ++i) { + // +1 to take return type into account + if (new_type->GetSingleWordInOperand(i + 1) != + function_type->GetSingleWordInOperand(permutation[i] + 1)) { + return false; + } + } + + return true; +} + +void TransformationPermuteFunctionParameters::Apply( + opt::IRContext* context, FactManager* /*unused*/) const { + // Retrieve all data from the message + uint32_t function_id = message_.function_id(); + uint32_t new_type_id = message_.new_type_id(); + const auto& permutation = message_.permutation(); + + // Find the function that will be transformed + auto* function = fuzzerutil::FindFunction(context, function_id); + assert(function && "Can't find the function"); + + // Change function's type + function->DefInst().SetInOperand(1, {new_type_id}); + + // Adjust OpFunctionParameter instructions + + // Collect ids and types from OpFunctionParameter instructions + std::vector param_id, param_type; + function->ForEachParam( + [¶m_id, ¶m_type](const opt::Instruction* param) { + param_id.push_back(param->result_id()); + param_type.push_back(param->type_id()); + }); + + // Permute parameters' ids and types + std::vector permuted_param_id, permuted_param_type; + for (auto index : permutation) { + permuted_param_id.push_back(param_id[index]); + permuted_param_type.push_back(param_type[index]); + } + + // Set OpFunctionParameter instructions to point to new parameters + size_t i = 0; + function->ForEachParam( + [&i, &permuted_param_id, &permuted_param_type](opt::Instruction* param) { + param->SetResultType(permuted_param_type[i]); + param->SetResultId(permuted_param_id[i]); + ++i; + }); + + // Fix all OpFunctionCall instructions + context->get_def_use_mgr()->ForEachUser( + &function->DefInst(), + [function_id, &permutation](opt::Instruction* call) { + if (call->opcode() != SpvOpFunctionCall || + call->GetSingleWordInOperand(0) != function_id) { + return; + } + + opt::Instruction::OperandList call_operands = { + call->GetInOperand(0) // Function id + }; + + for (auto index : permutation) { + // Take function id into account + call_operands.push_back(call->GetInOperand(index + 1)); + } + + call->SetInOperands(std::move(call_operands)); + }); + + // Make sure our changes are analyzed + context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone); +} + +protobufs::Transformation TransformationPermuteFunctionParameters::ToMessage() + const { + protobufs::Transformation result; + *result.mutable_permute_function_parameters() = message_; + return result; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_permute_function_parameters.h b/3rdparty/spirv-tools/source/fuzz/transformation_permute_function_parameters.h new file mode 100644 index 000000000..c67a73569 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/transformation_permute_function_parameters.h @@ -0,0 +1,61 @@ +// Copyright (c) 2020 Vasyl Teliman +// +// 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_PERMUTE_FUNCTION_PARAMETERS_H_ +#define SOURCE_FUZZ_TRANSFORMATION_PERMUTE_FUNCTION_PARAMETERS_H_ + +#include "source/fuzz/fact_manager.h" +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" +#include "source/fuzz/transformation.h" +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace fuzz { + +class TransformationPermuteFunctionParameters : public Transformation { + public: + explicit TransformationPermuteFunctionParameters( + const protobufs::TransformationPermuteFunctionParameters& message); + + TransformationPermuteFunctionParameters( + uint32_t function_id, uint32_t new_type_id, + const std::vector& permutation); + + // - |function_id| is a valid non-entry-point OpFunction instruction + // - |new_type_id| is a result id of a valid OpTypeFunction instruction. + // New type is valid if: + // - it has the same number of operands as the old one + // - function's result type is the same as the old one + // - function's arguments are permuted according to |permutation| vector + // - |permutation| is a set of [0..(n - 1)], where n is a number of arguments + // to the function + bool IsApplicable(opt::IRContext* context, + const FactManager& fact_manager) const override; + + // - OpFunction instruction with |result_id == function_id| is changed. + // Its arguments are permuted according to the |permutation| vector + // - Changed function gets a new type specified by |type_id| + // - Calls to the function are adjusted accordingly + void Apply(opt::IRContext* context, FactManager* fact_manager) const override; + + protobufs::Transformation ToMessage() const override; + + private: + protobufs::TransformationPermuteFunctionParameters message_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_TRANSFORMATION_PERMUTE_FUNCTION_PARAMETERS_H_ diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_replace_boolean_constant_with_constant_binary.cpp b/3rdparty/spirv-tools/source/fuzz/transformation_replace_boolean_constant_with_constant_binary.cpp index b097767fb..72d9b228d 100644 --- a/3rdparty/spirv-tools/source/fuzz/transformation_replace_boolean_constant_with_constant_binary.cpp +++ b/3rdparty/spirv-tools/source/fuzz/transformation_replace_boolean_constant_with_constant_binary.cpp @@ -243,11 +243,22 @@ bool TransformationReplaceBooleanConstantWithConstantBinary::IsApplicable( return false; } - // The instruction must not be an OpPhi, as we cannot insert a binary - // operator instruction before an OpPhi. - // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2902): there is - // scope for being less conservative. - return instruction->opcode() != SpvOpPhi; + switch (instruction->opcode()) { + case SpvOpPhi: + // The instruction must not be an OpPhi, as we cannot insert a binary + // operator instruction before an OpPhi. + // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2902): there is + // scope for being less conservative. + return false; + case SpvOpVariable: + // The instruction must not be an OpVariable, because (a) we cannot insert + // a binary operator before an OpVariable, but in any case (b) the + // constant we would be replacing is the initializer constant of the + // OpVariable, and this cannot be the result of a binary operation. + return false; + default: + return true; + } } void TransformationReplaceBooleanConstantWithConstantBinary::Apply( diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_replace_constant_with_uniform.cpp b/3rdparty/spirv-tools/source/fuzz/transformation_replace_constant_with_uniform.cpp index 405776ec0..8e0e4e5a6 100644 --- a/3rdparty/spirv-tools/source/fuzz/transformation_replace_constant_with_uniform.cpp +++ b/3rdparty/spirv-tools/source/fuzz/transformation_replace_constant_with_uniform.cpp @@ -154,6 +154,12 @@ bool TransformationReplaceConstantWithUniform::IsApplicable( return false; } + // The use must not be a variable initializer; these are required to be + // constants, so it would be illegal to replace one with a uniform access. + if (instruction_using_constant->opcode() == SpvOpVariable) { + return false; + } + // The module needs to have a uniform pointer type suitable for indexing into // the uniform variable, i.e. matching the type of the constant we wish to // replace with a uniform. diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_replace_id_with_synonym.cpp b/3rdparty/spirv-tools/source/fuzz/transformation_replace_id_with_synonym.cpp index 79ba01282..88c977a24 100644 --- a/3rdparty/spirv-tools/source/fuzz/transformation_replace_id_with_synonym.cpp +++ b/3rdparty/spirv-tools/source/fuzz/transformation_replace_id_with_synonym.cpp @@ -65,9 +65,9 @@ bool TransformationReplaceIdWithSynonym::IsApplicable( // The transformation is applicable if the synonymous id is available at the // use point. - return IdsIsAvailableAtUse(context, use_instruction, - message_.id_use_descriptor().in_operand_index(), - message_.synonymous_id()); + return fuzzerutil::IdIsAvailableAtUse( + context, use_instruction, message_.id_use_descriptor().in_operand_index(), + message_.synonymous_id()); } void TransformationReplaceIdWithSynonym::Apply( @@ -88,30 +88,6 @@ protobufs::Transformation TransformationReplaceIdWithSynonym::ToMessage() return result; } -bool TransformationReplaceIdWithSynonym::IdsIsAvailableAtUse( - opt::IRContext* context, opt::Instruction* use_instruction, - uint32_t use_input_operand_index, uint32_t id) { - if (!context->get_instr_block(id)) { - return true; - } - auto defining_instruction = context->get_def_use_mgr()->GetDef(id); - if (defining_instruction == use_instruction) { - return false; - } - auto dominator_analysis = context->GetDominatorAnalysis( - context->get_instr_block(use_instruction)->GetParent()); - if (use_instruction->opcode() == SpvOpPhi) { - // In the case where the use is an operand to OpPhi, it is actually the - // *parent* block associated with the operand that must be dominated by - // the synonym. - auto parent_block = - use_instruction->GetSingleWordInOperand(use_input_operand_index + 1); - return dominator_analysis->Dominates( - context->get_instr_block(defining_instruction)->id(), parent_block); - } - return dominator_analysis->Dominates(defining_instruction, use_instruction); -} - bool TransformationReplaceIdWithSynonym::UseCanBeReplacedWithSynonym( opt::IRContext* context, opt::Instruction* use_instruction, uint32_t use_in_operand_index) { diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_replace_id_with_synonym.h b/3rdparty/spirv-tools/source/fuzz/transformation_replace_id_with_synonym.h index c21673dcd..48132c1e0 100644 --- a/3rdparty/spirv-tools/source/fuzz/transformation_replace_id_with_synonym.h +++ b/3rdparty/spirv-tools/source/fuzz/transformation_replace_id_with_synonym.h @@ -51,14 +51,6 @@ class TransformationReplaceIdWithSynonym : public Transformation { protobufs::Transformation ToMessage() const override; - // Checks whether the |id| is available (according to dominance rules) at the - // use point defined by input operand |use_input_operand_index| of - // |use_instruction|. - static bool IdsIsAvailableAtUse(opt::IRContext* context, - opt::Instruction* use_instruction, - uint32_t use_input_operand_index, - uint32_t id); - // Checks whether various conditions hold related to the acceptability of // replacing the id use at |use_in_operand_index| of |use_instruction| with // a synonym. In particular, this checks that: diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_store.cpp b/3rdparty/spirv-tools/source/fuzz/transformation_store.cpp new file mode 100644 index 000000000..7cb761126 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/transformation_store.cpp @@ -0,0 +1,128 @@ +// 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_store.h" + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/instruction_descriptor.h" + +namespace spvtools { +namespace fuzz { + +TransformationStore::TransformationStore( + const spvtools::fuzz::protobufs::TransformationStore& message) + : message_(message) {} + +TransformationStore::TransformationStore( + uint32_t pointer_id, uint32_t value_id, + const protobufs::InstructionDescriptor& instruction_to_insert_before) { + message_.set_pointer_id(pointer_id); + message_.set_value_id(value_id); + *message_.mutable_instruction_to_insert_before() = + instruction_to_insert_before; +} + +bool TransformationStore::IsApplicable( + opt::IRContext* context, + const spvtools::fuzz::FactManager& fact_manager) const { + // The pointer must exist and have a type. + auto pointer = context->get_def_use_mgr()->GetDef(message_.pointer_id()); + if (!pointer || !pointer->type_id()) { + return false; + } + + // The pointer type must indeed be a pointer. + auto pointer_type = context->get_def_use_mgr()->GetDef(pointer->type_id()); + assert(pointer_type && "Type id must be defined."); + if (pointer_type->opcode() != SpvOpTypePointer) { + return false; + } + + // The pointer must not be read only. + if (pointer_type->GetSingleWordInOperand(0) == SpvStorageClassInput) { + return false; + } + + // We do not want to allow storing to null or undefined pointers. + switch (pointer->opcode()) { + case SpvOpConstantNull: + case SpvOpUndef: + return false; + default: + break; + } + + // Determine which instruction we should be inserting before. + auto insert_before = + FindInstruction(message_.instruction_to_insert_before(), context); + // It must exist, ... + if (!insert_before) { + return false; + } + // ... and it must be legitimate to insert a store before it. + if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpStore, + insert_before)) { + return false; + } + + // The block we are inserting into needs to be dead, or else the pointee type + // of the pointer we are storing to needs to be irrelevant (otherwise the + // store could impact on the observable behaviour of the module). + if (!fact_manager.BlockIsDead( + context->get_instr_block(insert_before)->id()) && + !fact_manager.PointeeValueIsIrrelevant(message_.pointer_id())) { + return false; + } + + // The value being stored needs to exist and have a type. + auto value = context->get_def_use_mgr()->GetDef(message_.value_id()); + if (!value || !value->type_id()) { + return false; + } + + // The type of the value must match the pointee type. + if (pointer_type->GetSingleWordInOperand(1) != value->type_id()) { + return false; + } + + // The pointer needs to be available at the insertion point. + if (!fuzzerutil::IdIsAvailableBeforeInstruction(context, insert_before, + message_.pointer_id())) { + return false; + } + + // The value needs to be available at the insertion point. + return fuzzerutil::IdIsAvailableBeforeInstruction(context, insert_before, + message_.value_id()); +} + +void TransformationStore::Apply(opt::IRContext* context, + spvtools::fuzz::FactManager* /*unused*/) const { + FindInstruction(message_.instruction_to_insert_before(), context) + ->InsertBefore(MakeUnique( + context, SpvOpStore, 0, 0, + opt::Instruction::OperandList( + {{SPV_OPERAND_TYPE_ID, {message_.pointer_id()}}, + {SPV_OPERAND_TYPE_ID, {message_.value_id()}}}))); + context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone); +} + +protobufs::Transformation TransformationStore::ToMessage() const { + protobufs::Transformation result; + *result.mutable_store() = message_; + return result; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_store.h b/3rdparty/spirv-tools/source/fuzz/transformation_store.h new file mode 100644 index 000000000..699afdda0 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/transformation_store.h @@ -0,0 +1,63 @@ +// 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_STORE_H_ +#define SOURCE_FUZZ_TRANSFORMATION_STORE_H_ + +#include "source/fuzz/fact_manager.h" +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" +#include "source/fuzz/transformation.h" +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace fuzz { + +class TransformationStore : public Transformation { + public: + explicit TransformationStore(const protobufs::TransformationStore& message); + + TransformationStore( + uint32_t pointer_id, uint32_t value_id, + const protobufs::InstructionDescriptor& instruction_to_insert_before); + + // - |message_.pointer_id| must be the id of a pointer + // - The pointer type must not have read-only storage class + // - The pointer must not be OpConstantNull or OpUndef + // - |message_.value_id| must be an instruction result id that has the same + // type as the pointee type of |message_.pointer_id| + // - |message_.instruction_to_insert_before| must identify an instruction + // before which it is valid to insert an OpStore, and where both + // |message_.pointer_id| and |message_.value_id| are available (according + // to dominance rules) + // - Either the insertion point must be in a dead block, or it must be known + // that the pointee value of |message_.pointer_id| is irrelevant + bool IsApplicable(opt::IRContext* context, + const FactManager& fact_manager) const override; + + // Adds an instruction of the form: + // OpStore |pointer_id| |value_id| + // before the instruction identified by + // |message_.instruction_to_insert_before|. + void Apply(opt::IRContext* context, FactManager* fact_manager) const override; + + protobufs::Transformation ToMessage() const override; + + private: + protobufs::TransformationStore message_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_TRANSFORMATION_STORE_H_ diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_swap_commutable_operands.cpp b/3rdparty/spirv-tools/source/fuzz/transformation_swap_commutable_operands.cpp new file mode 100644 index 000000000..49d9de832 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/transformation_swap_commutable_operands.cpp @@ -0,0 +1,66 @@ +// 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_swap_commutable_operands.h" + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/instruction_descriptor.h" + +namespace spvtools { +namespace fuzz { + +TransformationSwapCommutableOperands::TransformationSwapCommutableOperands( + const spvtools::fuzz::protobufs::TransformationSwapCommutableOperands& + message) + : message_(message) {} + +TransformationSwapCommutableOperands::TransformationSwapCommutableOperands( + const protobufs::InstructionDescriptor& instruction_descriptor) { + *message_.mutable_instruction_descriptor() = instruction_descriptor; +} + +bool TransformationSwapCommutableOperands::IsApplicable( + opt::IRContext* context, const spvtools::fuzz::FactManager& /*unused*/ + ) const { + auto instruction = + FindInstruction(message_.instruction_descriptor(), context); + if (instruction == nullptr) return false; + + SpvOp opcode = static_cast( + message_.instruction_descriptor().target_instruction_opcode()); + assert(instruction->opcode() == opcode && + "The located instruction must have the same opcode as in the " + "descriptor."); + return spvOpcodeIsCommutativeBinaryOperator(opcode); +} + +void TransformationSwapCommutableOperands::Apply( + opt::IRContext* context, spvtools::fuzz::FactManager* /*unused*/ + ) const { + auto instruction = + FindInstruction(message_.instruction_descriptor(), context); + // By design, the instructions defined to be commutative have exactly two + // input parameters. + std::swap(instruction->GetInOperand(0), instruction->GetInOperand(1)); +} + +protobufs::Transformation TransformationSwapCommutableOperands::ToMessage() + const { + protobufs::Transformation result; + *result.mutable_swap_commutable_operands() = message_; + return result; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_swap_commutable_operands.h b/3rdparty/spirv-tools/source/fuzz/transformation_swap_commutable_operands.h new file mode 100644 index 000000000..061e92dac --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/transformation_swap_commutable_operands.h @@ -0,0 +1,51 @@ +// 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_SWAP_COMMUTABLE_OPERANDS_H_ +#define SOURCE_FUZZ_TRANSFORMATION_SWAP_COMMUTABLE_OPERANDS_H_ + +#include "source/fuzz/fact_manager.h" +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" +#include "source/fuzz/transformation.h" +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace fuzz { + +class TransformationSwapCommutableOperands : public Transformation { + public: + explicit TransformationSwapCommutableOperands( + const protobufs::TransformationSwapCommutableOperands& message); + + TransformationSwapCommutableOperands( + const protobufs::InstructionDescriptor& instruction_descriptor); + + // - |message_.instruction_descriptor| must identify an existing + // commutative instruction + bool IsApplicable(opt::IRContext* context, + const FactManager& fact_manager) const override; + + // Swaps the commutable operands. + void Apply(opt::IRContext* context, FactManager* fact_manager) const override; + + protobufs::Transformation ToMessage() const override; + + private: + protobufs::TransformationSwapCommutableOperands message_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_TRANSFORMATION_SWAP_COMMUTABLE_OPERANDS_H_ diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_toggle_access_chain_instruction.cpp b/3rdparty/spirv-tools/source/fuzz/transformation_toggle_access_chain_instruction.cpp new file mode 100644 index 000000000..ace331aa2 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/transformation_toggle_access_chain_instruction.cpp @@ -0,0 +1,83 @@ +// 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_toggle_access_chain_instruction.h" + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/instruction_descriptor.h" + +namespace spvtools { +namespace fuzz { + +TransformationToggleAccessChainInstruction:: + TransformationToggleAccessChainInstruction( + const spvtools::fuzz::protobufs:: + TransformationToggleAccessChainInstruction& message) + : message_(message) {} + +TransformationToggleAccessChainInstruction:: + TransformationToggleAccessChainInstruction( + const protobufs::InstructionDescriptor& instruction_descriptor) { + *message_.mutable_instruction_descriptor() = instruction_descriptor; +} + +bool TransformationToggleAccessChainInstruction::IsApplicable( + opt::IRContext* context, const spvtools::fuzz::FactManager& /*unused*/ + ) const { + auto instruction = + FindInstruction(message_.instruction_descriptor(), context); + if (instruction == nullptr) { + return false; + } + + SpvOp opcode = static_cast( + message_.instruction_descriptor().target_instruction_opcode()); + + assert(instruction->opcode() == opcode && + "The located instruction must have the same opcode as in the " + "descriptor."); + + if (opcode == SpvOpAccessChain || opcode == SpvOpInBoundsAccessChain) { + return true; + } + + return false; +} + +void TransformationToggleAccessChainInstruction::Apply( + opt::IRContext* context, spvtools::fuzz::FactManager* /*unused*/ + ) const { + auto instruction = + FindInstruction(message_.instruction_descriptor(), context); + SpvOp opcode = instruction->opcode(); + + if (opcode == SpvOpAccessChain) { + instruction->SetOpcode(SpvOpInBoundsAccessChain); + } else { + assert(opcode == SpvOpInBoundsAccessChain && + "The located instruction must be an OpInBoundsAccessChain " + "instruction."); + instruction->SetOpcode(SpvOpAccessChain); + } +} + +protobufs::Transformation +TransformationToggleAccessChainInstruction::ToMessage() const { + protobufs::Transformation result; + *result.mutable_toggle_access_chain_instruction() = message_; + return result; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_toggle_access_chain_instruction.h b/3rdparty/spirv-tools/source/fuzz/transformation_toggle_access_chain_instruction.h new file mode 100644 index 000000000..125e1ab03 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/transformation_toggle_access_chain_instruction.h @@ -0,0 +1,51 @@ +// 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_TOGGLE_ACCESS_CHAIN_INSTRUCTION_H_ +#define SOURCE_FUZZ_TRANSFORMATION_TOGGLE_ACCESS_CHAIN_INSTRUCTION_H_ + +#include "source/fuzz/fact_manager.h" +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" +#include "source/fuzz/transformation.h" +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace fuzz { + +class TransformationToggleAccessChainInstruction : public Transformation { + public: + explicit TransformationToggleAccessChainInstruction( + const protobufs::TransformationToggleAccessChainInstruction& message); + + TransformationToggleAccessChainInstruction( + const protobufs::InstructionDescriptor& instruction_descriptor); + + // - |message_.instruction_descriptor| must identify an existing + // access chain instruction + bool IsApplicable(opt::IRContext* context, + const FactManager& fact_manager) const override; + + // Toggles the access chain instruction. + void Apply(opt::IRContext* context, FactManager* fact_manager) const override; + + protobufs::Transformation ToMessage() const override; + + private: + protobufs::TransformationToggleAccessChainInstruction message_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_TRANSFORMATION_TOGGLE_ACCESS_CHAIN_INSTRUCTION_H_ diff --git a/3rdparty/spirv-tools/source/opcode.cpp b/3rdparty/spirv-tools/source/opcode.cpp index 7f91a0ffd..b38b5d4a0 100644 --- a/3rdparty/spirv-tools/source/opcode.cpp +++ b/3rdparty/spirv-tools/source/opcode.cpp @@ -181,11 +181,15 @@ void spvInstructionCopy(const uint32_t* words, const SpvOp opcode, } } -const char* spvOpcodeString(const SpvOp opcode) { +const char* spvOpcodeString(const uint32_t opcode) { const auto beg = kOpcodeTableEntries; const auto end = kOpcodeTableEntries + ARRAY_SIZE(kOpcodeTableEntries); - spv_opcode_desc_t needle = {"", opcode, 0, nullptr, 0, {}, - false, false, 0, nullptr, ~0u, ~0u}; + spv_opcode_desc_t needle = {"", static_cast(opcode), + 0, nullptr, + 0, {}, + false, false, + 0, nullptr, + ~0u, ~0u}; auto comp = [](const spv_opcode_desc_t& lhs, const spv_opcode_desc_t& rhs) { return lhs.opcode < rhs.opcode; }; @@ -608,6 +612,39 @@ bool spvOpcodeIsDebug(SpvOp opcode) { } } +bool spvOpcodeIsCommutativeBinaryOperator(SpvOp opcode) { + switch (opcode) { + case SpvOpPtrEqual: + case SpvOpPtrNotEqual: + case SpvOpIAdd: + case SpvOpFAdd: + case SpvOpIMul: + case SpvOpFMul: + case SpvOpDot: + case SpvOpIAddCarry: + case SpvOpUMulExtended: + case SpvOpSMulExtended: + case SpvOpBitwiseOr: + case SpvOpBitwiseXor: + case SpvOpBitwiseAnd: + case SpvOpOrdered: + case SpvOpUnordered: + case SpvOpLogicalEqual: + case SpvOpLogicalNotEqual: + case SpvOpLogicalOr: + case SpvOpLogicalAnd: + case SpvOpIEqual: + case SpvOpINotEqual: + case SpvOpFOrdEqual: + case SpvOpFUnordEqual: + case SpvOpFOrdNotEqual: + case SpvOpFUnordNotEqual: + return true; + default: + return false; + } +} + std::vector spvOpcodeMemorySemanticsOperandIndices(SpvOp opcode) { switch (opcode) { case SpvOpMemoryBarrier: diff --git a/3rdparty/spirv-tools/source/opcode.h b/3rdparty/spirv-tools/source/opcode.h index ed64f1b5a..b4f02718f 100644 --- a/3rdparty/spirv-tools/source/opcode.h +++ b/3rdparty/spirv-tools/source/opcode.h @@ -56,9 +56,6 @@ void spvInstructionCopy(const uint32_t* words, const SpvOp opcode, const uint16_t word_count, const spv_endianness_t endian, spv_instruction_t* inst); -// Gets the name of an instruction, without the "Op" prefix. -const char* spvOpcodeString(const SpvOp opcode); - // Determine if the given opcode is a scalar type. Returns zero if false, // non-zero otherwise. int32_t spvOpcodeIsScalarType(const SpvOp opcode); @@ -133,6 +130,10 @@ bool spvOpcodeIsScalarizable(SpvOp opcode); // Returns true if the given opcode is a debug instruction. bool spvOpcodeIsDebug(SpvOp opcode); +// Returns true for opcodes that are binary operators, +// where the order of the operands is irrelevant. +bool spvOpcodeIsCommutativeBinaryOperator(SpvOp opcode); + // Returns a vector containing the indices of the memory semantics // operands for |opcode|. std::vector spvOpcodeMemorySemanticsOperandIndices(SpvOp opcode); diff --git a/3rdparty/spirv-tools/source/opt/CMakeLists.txt b/3rdparty/spirv-tools/source/opt/CMakeLists.txt index 0f719cb90..1428c7465 100644 --- a/3rdparty/spirv-tools/source/opt/CMakeLists.txt +++ b/3rdparty/spirv-tools/source/opt/CMakeLists.txt @@ -58,6 +58,7 @@ set(SPIRV_TOOLS_OPT_SOURCES inline_pass.h inst_bindless_check_pass.h inst_buff_addr_check_pass.h + inst_debug_printf_pass.h instruction.h instruction_list.h instrument_pass.h @@ -164,6 +165,7 @@ set(SPIRV_TOOLS_OPT_SOURCES inline_pass.cpp inst_bindless_check_pass.cpp inst_buff_addr_check_pass.cpp + inst_debug_printf_pass.cpp instruction.cpp instruction_list.cpp instrument_pass.cpp diff --git a/3rdparty/spirv-tools/source/opt/feature_manager.cpp b/3rdparty/spirv-tools/source/opt/feature_manager.cpp index 63d50b6d7..b4d6f1ba5 100644 --- a/3rdparty/spirv-tools/source/opt/feature_manager.cpp +++ b/3rdparty/spirv-tools/source/opt/feature_manager.cpp @@ -47,6 +47,11 @@ void FeatureManager::AddExtension(Instruction* ext) { } } +void FeatureManager::RemoveExtension(Extension ext) { + if (!extensions_.Contains(ext)) return; + extensions_.Remove(ext); +} + void FeatureManager::AddCapability(SpvCapability cap) { if (capabilities_.Contains(cap)) return; @@ -60,6 +65,11 @@ void FeatureManager::AddCapability(SpvCapability cap) { } } +void FeatureManager::RemoveCapability(SpvCapability cap) { + if (!capabilities_.Contains(cap)) return; + capabilities_.Remove(cap); +} + void FeatureManager::AddCapabilities(Module* module) { for (Instruction& inst : module->capabilities()) { AddCapability(static_cast(inst.GetSingleWordInOperand(0))); diff --git a/3rdparty/spirv-tools/source/opt/feature_manager.h b/3rdparty/spirv-tools/source/opt/feature_manager.h index 2fe329108..881d5e601 100644 --- a/3rdparty/spirv-tools/source/opt/feature_manager.h +++ b/3rdparty/spirv-tools/source/opt/feature_manager.h @@ -30,11 +30,17 @@ class FeatureManager { // Returns true if |ext| is an enabled extension in the module. bool HasExtension(Extension ext) const { return extensions_.Contains(ext); } + // Removes the given |extension| from the current FeatureManager. + void RemoveExtension(Extension extension); + // Returns true if |cap| is an enabled capability in the module. bool HasCapability(SpvCapability cap) const { return capabilities_.Contains(cap); } + // Removes the given |capability| from the current FeatureManager. + void RemoveCapability(SpvCapability capability); + // Analyzes |module| and records enabled extensions and capabilities. void Analyze(Module* module); diff --git a/3rdparty/spirv-tools/source/opt/inst_debug_printf_pass.cpp b/3rdparty/spirv-tools/source/opt/inst_debug_printf_pass.cpp new file mode 100644 index 000000000..c0e6bc3f0 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/inst_debug_printf_pass.cpp @@ -0,0 +1,266 @@ +// Copyright (c) 2020 The Khronos Group Inc. +// Copyright (c) 2020 Valve Corporation +// Copyright (c) 2020 LunarG Inc. +// +// 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 "inst_debug_printf_pass.h" + +#include "spirv/unified1/NonSemanticDebugPrintf.h" + +namespace spvtools { +namespace opt { + +void InstDebugPrintfPass::GenOutputValues(Instruction* val_inst, + std::vector* val_ids, + InstructionBuilder* builder) { + uint32_t val_ty_id = val_inst->type_id(); + analysis::TypeManager* type_mgr = context()->get_type_mgr(); + analysis::Type* val_ty = type_mgr->GetType(val_ty_id); + switch (val_ty->kind()) { + case analysis::Type::kVector: { + analysis::Vector* v_ty = val_ty->AsVector(); + const analysis::Type* c_ty = v_ty->element_type(); + uint32_t c_ty_id = type_mgr->GetId(c_ty); + for (uint32_t c = 0; c < v_ty->element_count(); ++c) { + Instruction* c_inst = builder->AddIdLiteralOp( + c_ty_id, SpvOpCompositeExtract, val_inst->result_id(), c); + GenOutputValues(c_inst, val_ids, builder); + } + return; + } + case analysis::Type::kBool: { + // Select between uint32 zero or one + uint32_t zero_id = builder->GetUintConstantId(0); + uint32_t one_id = builder->GetUintConstantId(1); + Instruction* sel_inst = builder->AddTernaryOp( + GetUintId(), SpvOpSelect, val_inst->result_id(), one_id, zero_id); + val_ids->push_back(sel_inst->result_id()); + return; + } + case analysis::Type::kFloat: { + analysis::Float* f_ty = val_ty->AsFloat(); + switch (f_ty->width()) { + case 16: { + // Convert float16 to float32 and recurse + Instruction* f32_inst = builder->AddUnaryOp( + GetFloatId(), SpvOpFConvert, val_inst->result_id()); + GenOutputValues(f32_inst, val_ids, builder); + return; + } + case 64: { + // Bitcast float64 to uint64 and recurse + Instruction* ui64_inst = builder->AddUnaryOp( + GetUint64Id(), SpvOpBitcast, val_inst->result_id()); + GenOutputValues(ui64_inst, val_ids, builder); + return; + } + case 32: { + // Bitcase float32 to uint32 + Instruction* bc_inst = builder->AddUnaryOp(GetUintId(), SpvOpBitcast, + val_inst->result_id()); + val_ids->push_back(bc_inst->result_id()); + return; + } + default: + assert(false && "unsupported float width"); + return; + } + } + case analysis::Type::kInteger: { + analysis::Integer* i_ty = val_ty->AsInteger(); + switch (i_ty->width()) { + case 64: { + Instruction* ui64_inst = val_inst; + if (i_ty->IsSigned()) { + // Bitcast sint64 to uint64 + ui64_inst = builder->AddUnaryOp(GetUint64Id(), SpvOpBitcast, + val_inst->result_id()); + } + // Break uint64 into 2x uint32 + Instruction* lo_ui64_inst = builder->AddUnaryOp( + GetUintId(), SpvOpUConvert, ui64_inst->result_id()); + Instruction* rshift_ui64_inst = builder->AddBinaryOp( + GetUint64Id(), SpvOpShiftRightLogical, ui64_inst->result_id(), + builder->GetUintConstantId(32)); + Instruction* hi_ui64_inst = builder->AddUnaryOp( + GetUintId(), SpvOpUConvert, rshift_ui64_inst->result_id()); + val_ids->push_back(lo_ui64_inst->result_id()); + val_ids->push_back(hi_ui64_inst->result_id()); + return; + } + case 8: { + Instruction* ui8_inst = val_inst; + if (i_ty->IsSigned()) { + // Bitcast sint8 to uint8 + ui8_inst = builder->AddUnaryOp(GetUint8Id(), SpvOpBitcast, + val_inst->result_id()); + } + // Convert uint8 to uint32 + Instruction* ui32_inst = builder->AddUnaryOp( + GetUintId(), SpvOpUConvert, ui8_inst->result_id()); + val_ids->push_back(ui32_inst->result_id()); + return; + } + case 32: { + Instruction* ui32_inst = val_inst; + if (i_ty->IsSigned()) { + // Bitcast sint32 to uint32 + ui32_inst = builder->AddUnaryOp(GetUintId(), SpvOpBitcast, + val_inst->result_id()); + } + // uint32 needs no further processing + val_ids->push_back(ui32_inst->result_id()); + return; + } + default: + // TODO(greg-lunarg): Support non-32-bit int + assert(false && "unsupported int width"); + return; + } + } + default: + assert(false && "unsupported type"); + return; + } +} + +void InstDebugPrintfPass::GenOutputCode( + Instruction* printf_inst, uint32_t stage_idx, + std::vector>* new_blocks) { + BasicBlock* back_blk_ptr = &*new_blocks->back(); + InstructionBuilder builder( + context(), back_blk_ptr, + IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping); + // Gen debug printf record validation-specific values. The format string + // will have its id written. Vectors will need to be broken down into + // component values. float16 will need to be converted to float32. Pointer + // and uint64 will need to be converted to two uint32 values. float32 will + // need to be bitcast to uint32. int32 will need to be bitcast to uint32. + std::vector val_ids; + bool is_first_operand = false; + printf_inst->ForEachInId( + [&is_first_operand, &val_ids, &builder, this](const uint32_t* iid) { + // skip set operand + if (!is_first_operand) { + is_first_operand = true; + return; + } + Instruction* opnd_inst = get_def_use_mgr()->GetDef(*iid); + if (opnd_inst->opcode() == SpvOpString) { + uint32_t string_id_id = builder.GetUintConstantId(*iid); + val_ids.push_back(string_id_id); + } else { + GenOutputValues(opnd_inst, &val_ids, &builder); + } + }); + GenDebugStreamWrite(uid2offset_[printf_inst->unique_id()], stage_idx, val_ids, + &builder); + context()->KillInst(printf_inst); +} + +void InstDebugPrintfPass::GenDebugPrintfCode( + BasicBlock::iterator ref_inst_itr, + UptrVectorIterator ref_block_itr, uint32_t stage_idx, + std::vector>* new_blocks) { + // If not DebugPrintf OpExtInst, return. + Instruction* printf_inst = &*ref_inst_itr; + if (printf_inst->opcode() != SpvOpExtInst) return; + if (printf_inst->GetSingleWordInOperand(0) != ext_inst_printf_id_) return; + if (printf_inst->GetSingleWordInOperand(1) != + NonSemanticDebugPrintfDebugPrintf) + return; + // Initialize DefUse manager before dismantling module + (void)get_def_use_mgr(); + // Move original block's preceding instructions into first new block + std::unique_ptr new_blk_ptr; + MovePreludeCode(ref_inst_itr, ref_block_itr, &new_blk_ptr); + new_blocks->push_back(std::move(new_blk_ptr)); + // Generate instructions to output printf args to printf buffer + GenOutputCode(printf_inst, stage_idx, new_blocks); + // Caller expects at least two blocks with last block containing remaining + // code, so end block after instrumentation, create remainder block, and + // branch to it + uint32_t rem_blk_id = TakeNextId(); + std::unique_ptr rem_label(NewLabel(rem_blk_id)); + BasicBlock* back_blk_ptr = &*new_blocks->back(); + InstructionBuilder builder( + context(), back_blk_ptr, + IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping); + (void)builder.AddBranch(rem_blk_id); + // Gen remainder block + new_blk_ptr.reset(new BasicBlock(std::move(rem_label))); + builder.SetInsertPoint(&*new_blk_ptr); + // Move original block's remaining code into remainder block and add + // to new blocks + MovePostludeCode(ref_block_itr, &*new_blk_ptr); + new_blocks->push_back(std::move(new_blk_ptr)); +} + +void InstDebugPrintfPass::InitializeInstDebugPrintf() { + // Initialize base class + InitializeInstrument(); +} + +Pass::Status InstDebugPrintfPass::ProcessImpl() { + // Perform printf instrumentation on each entry point function in module + InstProcessFunction pfn = + [this](BasicBlock::iterator ref_inst_itr, + UptrVectorIterator ref_block_itr, uint32_t stage_idx, + std::vector>* new_blocks) { + return GenDebugPrintfCode(ref_inst_itr, ref_block_itr, stage_idx, + new_blocks); + }; + (void)InstProcessEntryPointCallTree(pfn); + // Remove DebugPrintf OpExtInstImport instruction + Instruction* ext_inst_import_inst = + get_def_use_mgr()->GetDef(ext_inst_printf_id_); + context()->KillInst(ext_inst_import_inst); + // If no remaining non-semantic instruction sets, remove non-semantic debug + // info extension from module and feature manager + bool non_sem_set_seen = false; + for (auto c_itr = context()->module()->ext_inst_import_begin(); + c_itr != context()->module()->ext_inst_import_end(); ++c_itr) { + const char* set_name = + reinterpret_cast(&c_itr->GetInOperand(0).words[0]); + const char* non_sem_str = "NonSemantic."; + if (!strncmp(set_name, non_sem_str, strlen(non_sem_str))) { + non_sem_set_seen = true; + break; + } + } + if (!non_sem_set_seen) { + for (auto c_itr = context()->module()->extension_begin(); + c_itr != context()->module()->extension_end(); ++c_itr) { + const char* ext_name = + reinterpret_cast(&c_itr->GetInOperand(0).words[0]); + if (!strcmp(ext_name, "SPV_KHR_non_semantic_info")) { + context()->KillInst(&*c_itr); + break; + } + } + context()->get_feature_mgr()->RemoveExtension(kSPV_KHR_non_semantic_info); + } + return Status::SuccessWithChange; +} + +Pass::Status InstDebugPrintfPass::Process() { + ext_inst_printf_id_ = + get_module()->GetExtInstImportId("NonSemantic.DebugPrintf"); + if (ext_inst_printf_id_ == 0) return Status::SuccessWithoutChange; + InitializeInstDebugPrintf(); + return ProcessImpl(); +} + +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/inst_debug_printf_pass.h b/3rdparty/spirv-tools/source/opt/inst_debug_printf_pass.h new file mode 100644 index 000000000..2968a203a --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/inst_debug_printf_pass.h @@ -0,0 +1,96 @@ +// Copyright (c) 2020 The Khronos Group Inc. +// Copyright (c) 2020 Valve Corporation +// Copyright (c) 2020 LunarG Inc. +// +// 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 LIBSPIRV_OPT_INST_DEBUG_PRINTF_PASS_H_ +#define LIBSPIRV_OPT_INST_DEBUG_PRINTF_PASS_H_ + +#include "instrument_pass.h" + +namespace spvtools { +namespace opt { + +// This class/pass is designed to support the debug printf GPU-assisted layer +// of https://github.com/KhronosGroup/Vulkan-ValidationLayers. Its internal and +// external design may change as the layer evolves. +class InstDebugPrintfPass : public InstrumentPass { + public: + // For test harness only + InstDebugPrintfPass() + : InstrumentPass(7, 23, kInstValidationIdDebugPrintf, 2) {} + // For all other interfaces + InstDebugPrintfPass(uint32_t desc_set, uint32_t shader_id) + : InstrumentPass(desc_set, shader_id, kInstValidationIdDebugPrintf, 2) {} + + ~InstDebugPrintfPass() override = default; + + // See optimizer.hpp for pass user documentation. + Status Process() override; + + const char* name() const override { return "inst-printf-pass"; } + + private: + // Generate instructions for OpDebugPrintf. + // + // If |ref_inst_itr| is an OpDebugPrintf, return in |new_blocks| the result + // of replacing it with buffer write instructions within its block at + // |ref_block_itr|. The instructions write a record to the printf + // output buffer stream including |function_idx, instruction_idx, stage_idx| + // and removes the OpDebugPrintf. The block at |ref_block_itr| can just be + // replaced with the block in |new_blocks|. Besides the buffer writes, this + // block will comprise all instructions preceding and following + // |ref_inst_itr|. + // + // This function is designed to be passed to + // InstrumentPass::InstProcessEntryPointCallTree(), which applies the + // function to each instruction in a module and replaces the instruction + // if warranted. + // + // This instrumentation function utilizes GenDebugStreamWrite() to write its + // error records. The validation-specific part of the error record will + // consist of a uint32 which is the id of the format string plus a sequence + // of uint32s representing the values of the remaining operands of the + // DebugPrintf. + void GenDebugPrintfCode(BasicBlock::iterator ref_inst_itr, + UptrVectorIterator ref_block_itr, + uint32_t stage_idx, + std::vector>* new_blocks); + + // Generate a sequence of uint32 instructions in |builder| (if necessary) + // representing the value of |val_inst|, which must be a buffer pointer, a + // uint64, or a scalar or vector of type uint32, float32 or float16. Append + // the ids of all values to the end of |val_ids|. + void GenOutputValues(Instruction* val_inst, std::vector* val_ids, + InstructionBuilder* builder); + + // Generate instructions to write a record containing the operands of + // |printf_inst| arguments to printf buffer, adding new code to the end of + // the last block in |new_blocks|. Kill OpDebugPrintf instruction. + void GenOutputCode(Instruction* printf_inst, uint32_t stage_idx, + std::vector>* new_blocks); + + // Initialize state for instrumenting bindless checking + void InitializeInstDebugPrintf(); + + // Apply GenDebugPrintfCode to every instruction in module. + Pass::Status ProcessImpl(); + + uint32_t ext_inst_printf_id_; +}; + +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_OPT_INST_DEBUG_PRINTF_PASS_H_ diff --git a/3rdparty/spirv-tools/source/opt/instrument_pass.cpp b/3rdparty/spirv-tools/source/opt/instrument_pass.cpp index b1a6edb9d..c8c6c2113 100644 --- a/3rdparty/spirv-tools/source/opt/instrument_pass.cpp +++ b/3rdparty/spirv-tools/source/opt/instrument_pass.cpp @@ -380,6 +380,8 @@ uint32_t InstrumentPass::GetOutputBufferBinding() { return kDebugOutputBindingStream; case kInstValidationIdBuffAddr: return kDebugOutputBindingStream; + case kInstValidationIdDebugPrintf: + return kDebugOutputPrintfStream; default: assert(false && "unexpected validation id"); } @@ -529,6 +531,16 @@ uint32_t InstrumentPass::GetInputBufferId() { return input_buffer_id_; } +uint32_t InstrumentPass::GetFloatId() { + if (float_id_ == 0) { + analysis::TypeManager* type_mgr = context()->get_type_mgr(); + analysis::Float float_ty(32); + analysis::Type* reg_float_ty = type_mgr->GetRegisteredType(&float_ty); + float_id_ = type_mgr->GetTypeInstruction(reg_float_ty); + } + return float_id_; +} + uint32_t InstrumentPass::GetVec4FloatId() { if (v4float_id_ == 0) { analysis::TypeManager* type_mgr = context()->get_type_mgr(); @@ -561,6 +573,16 @@ uint32_t InstrumentPass::GetUint64Id() { return uint64_id_; } +uint32_t InstrumentPass::GetUint8Id() { + if (uint8_id_ == 0) { + analysis::TypeManager* type_mgr = context()->get_type_mgr(); + analysis::Integer uint8_ty(8, false); + analysis::Type* reg_uint8_ty = type_mgr->GetRegisteredType(&uint8_ty); + uint8_id_ = type_mgr->GetTypeInstruction(reg_uint8_ty); + } + return uint8_id_; +} + uint32_t InstrumentPass::GetVecUintId(uint32_t len) { analysis::TypeManager* type_mgr = context()->get_type_mgr(); analysis::Integer uint_ty(32, false); @@ -606,21 +628,22 @@ uint32_t InstrumentPass::GetStreamWriteFunctionId(uint32_t stage_idx, // Total param count is common params plus validation-specific // params uint32_t param_cnt = kInstCommonParamCnt + val_spec_param_cnt; - if (output_func_id_ == 0) { + if (param2output_func_id_[param_cnt] == 0) { // Create function - output_func_id_ = TakeNextId(); + param2output_func_id_[param_cnt] = TakeNextId(); analysis::TypeManager* type_mgr = context()->get_type_mgr(); std::vector param_types; for (uint32_t c = 0; c < param_cnt; ++c) param_types.push_back(type_mgr->GetType(GetUintId())); analysis::Function func_ty(type_mgr->GetType(GetVoidId()), param_types); analysis::Type* reg_func_ty = type_mgr->GetRegisteredType(&func_ty); - std::unique_ptr func_inst(new Instruction( - get_module()->context(), SpvOpFunction, GetVoidId(), output_func_id_, - {{spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER, - {SpvFunctionControlMaskNone}}, - {spv_operand_type_t::SPV_OPERAND_TYPE_ID, - {type_mgr->GetTypeInstruction(reg_func_ty)}}})); + std::unique_ptr func_inst( + new Instruction(get_module()->context(), SpvOpFunction, GetVoidId(), + param2output_func_id_[param_cnt], + {{spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER, + {SpvFunctionControlMaskNone}}, + {spv_operand_type_t::SPV_OPERAND_TYPE_ID, + {type_mgr->GetTypeInstruction(reg_func_ty)}}})); get_def_use_mgr()->AnalyzeInstDefUse(&*func_inst); std::unique_ptr output_func = MakeUnique(std::move(func_inst)); @@ -709,10 +732,8 @@ uint32_t InstrumentPass::GetStreamWriteFunctionId(uint32_t stage_idx, get_def_use_mgr()->AnalyzeInstDefUse(&*func_end_inst); output_func->SetFunctionEnd(std::move(func_end_inst)); context()->AddFunction(std::move(output_func)); - output_func_param_cnt_ = param_cnt; } - assert(param_cnt == output_func_param_cnt_ && "bad arg count"); - return output_func_id_; + return param2output_func_id_[param_cnt]; } uint32_t InstrumentPass::GetDirectReadFunctionId(uint32_t param_cnt) { @@ -848,7 +869,7 @@ bool InstrumentPass::InstProcessCallTreeFromRoots(InstProcessFunction& pfn, std::unordered_set done; // Don't process input and output functions for (auto& ifn : param2input_func_id_) done.insert(ifn.second); - if (output_func_id_ != 0) done.insert(output_func_id_); + for (auto& ofn : param2output_func_id_) done.insert(ofn.second); // Process all functions from roots while (!roots->empty()) { const uint32_t fi = roots->front(); @@ -926,12 +947,12 @@ void InstrumentPass::InitializeInstrument() { output_buffer_id_ = 0; output_buffer_ptr_id_ = 0; input_buffer_ptr_id_ = 0; - output_func_id_ = 0; - output_func_param_cnt_ = 0; input_buffer_id_ = 0; + float_id_ = 0; v4float_id_ = 0; uint_id_ = 0; uint64_id_ = 0; + uint8_id_ = 0; v4uint_id_ = 0; v3uint_id_ = 0; bool_id_ = 0; @@ -944,6 +965,10 @@ void InstrumentPass::InitializeInstrument() { id2function_.clear(); id2block_.clear(); + // clear maps + param2input_func_id_.clear(); + param2output_func_id_.clear(); + // Initialize function and block maps. for (auto& fn : *get_module()) { id2function_[fn.result_id()] = &fn; diff --git a/3rdparty/spirv-tools/source/opt/instrument_pass.h b/3rdparty/spirv-tools/source/opt/instrument_pass.h index 02568fb7a..11afdce82 100644 --- a/3rdparty/spirv-tools/source/opt/instrument_pass.h +++ b/3rdparty/spirv-tools/source/opt/instrument_pass.h @@ -61,6 +61,7 @@ namespace opt { // its output buffers. static const uint32_t kInstValidationIdBindless = 0; static const uint32_t kInstValidationIdBuffAddr = 1; +static const uint32_t kInstValidationIdDebugPrintf = 2; class InstrumentPass : public Pass { using cbb_ptr = const BasicBlock*; @@ -227,9 +228,12 @@ class InstrumentPass : public Pass { // Return id for 32-bit unsigned type uint32_t GetUintId(); - // Return id for 32-bit unsigned type + // Return id for 64-bit unsigned type uint32_t GetUint64Id(); + // Return id for 8-bit unsigned type + uint32_t GetUint8Id(); + // Return id for 32-bit unsigned type uint32_t GetBoolId(); @@ -267,6 +271,9 @@ class InstrumentPass : public Pass { // Return id for debug input buffer uint32_t GetInputBufferId(); + // Return id for 32-bit float type + uint32_t GetFloatId(); + // Return id for v4float type uint32_t GetVec4FloatId(); @@ -383,17 +390,17 @@ class InstrumentPass : public Pass { uint32_t input_buffer_ptr_id_; // id for debug output function - uint32_t output_func_id_; + std::unordered_map param2output_func_id_; // ids for debug input functions std::unordered_map param2input_func_id_; - // param count for output function - uint32_t output_func_param_cnt_; - // id for input buffer variable uint32_t input_buffer_id_; + // id for 32-bit float type + uint32_t float_id_; + // id for v4float type uint32_t v4float_id_; @@ -406,9 +413,12 @@ class InstrumentPass : public Pass { // id for 32-bit unsigned type uint32_t uint_id_; - // id for 32-bit unsigned type + // id for 64-bit unsigned type uint32_t uint64_id_; + // id for 8-bit unsigned type + uint32_t uint8_id_; + // id for bool type uint32_t bool_id_; diff --git a/3rdparty/spirv-tools/source/opt/optimizer.cpp b/3rdparty/spirv-tools/source/opt/optimizer.cpp index 241aa75be..6e271f538 100644 --- a/3rdparty/spirv-tools/source/opt/optimizer.cpp +++ b/3rdparty/spirv-tools/source/opt/optimizer.cpp @@ -425,6 +425,8 @@ bool Optimizer::RegisterPassFromFlag(const std::string& flag) { RegisterPass(CreateConvertRelaxedToHalfPass()); } else if (pass_name == "relax-float-ops") { RegisterPass(CreateRelaxFloatOpsPass()); + } else if (pass_name == "inst-debug-printf") { + RegisterPass(CreateInstDebugPrintfPass(7, 23)); } else if (pass_name == "simplify-instructions") { RegisterPass(CreateSimplificationPass()); } else if (pass_name == "ssa-rewrite") { @@ -886,6 +888,12 @@ Optimizer::PassToken CreateInstBindlessCheckPass(uint32_t desc_set, input_init_enable, version)); } +Optimizer::PassToken CreateInstDebugPrintfPass(uint32_t desc_set, + uint32_t shader_id) { + return MakeUnique( + MakeUnique(desc_set, shader_id)); +} + Optimizer::PassToken CreateInstBuffAddrCheckPass(uint32_t desc_set, uint32_t shader_id, uint32_t version) { diff --git a/3rdparty/spirv-tools/source/opt/passes.h b/3rdparty/spirv-tools/source/opt/passes.h index 1a3675c77..5b4ab898e 100644 --- a/3rdparty/spirv-tools/source/opt/passes.h +++ b/3rdparty/spirv-tools/source/opt/passes.h @@ -46,6 +46,7 @@ #include "source/opt/inline_opaque_pass.h" #include "source/opt/inst_bindless_check_pass.h" #include "source/opt/inst_buff_addr_check_pass.h" +#include "source/opt/inst_debug_printf_pass.h" #include "source/opt/legalize_vector_shuffle_pass.h" #include "source/opt/licm_pass.h" #include "source/opt/local_access_chain_convert_pass.h" diff --git a/3rdparty/spirv-tools/source/val/validate.cpp b/3rdparty/spirv-tools/source/val/validate.cpp index 7f4b0dce4..168968da4 100644 --- a/3rdparty/spirv-tools/source/val/validate.cpp +++ b/3rdparty/spirv-tools/source/val/validate.cpp @@ -142,7 +142,7 @@ spv_result_t ValidateEntryPointNameUnique(ValidationState_t& _, for (const auto other_id : _.entry_points()) { if (other_id == id) continue; const auto other_id_names = CalculateNamesForEntryPoint(_, other_id); - for (const auto other_id_name : other_id_names) { + for (const auto& other_id_name : other_id_names) { if (names.find(other_id_name) != names.end()) { return _.diag(SPV_ERROR_INVALID_BINARY, _.FindDef(id)) << "Entry point name \"" << other_id_name @@ -431,7 +431,7 @@ spv_result_t ValidateBinaryUsingContextAndValidationState( if (auto error = ValidateBuiltIns(*vstate)) return error; // These checks must be performed after individual opcode checks because // those checks register the limitation checked here. - for (const auto inst : vstate->ordered_instructions()) { + for (const auto& inst : vstate->ordered_instructions()) { if (auto error = ValidateExecutionLimitations(*vstate, &inst)) return error; if (auto error = ValidateSmallTypeUses(*vstate, &inst)) return error; } diff --git a/3rdparty/spirv-tools/source/val/validate_extensions.cpp b/3rdparty/spirv-tools/source/val/validate_extensions.cpp index 070cc4c6a..1e311c19d 100644 --- a/3rdparty/spirv-tools/source/val/validate_extensions.cpp +++ b/3rdparty/spirv-tools/source/val/validate_extensions.cpp @@ -14,12 +14,11 @@ // Validates correctness of extension SPIR-V instructions. -#include "source/val/validate.h" - #include #include #include +#include "OpenCLDebugInfo100.h" #include "source/diagnostic.h" #include "source/enum_string_mapping.h" #include "source/extensions.h" @@ -28,6 +27,7 @@ #include "source/opcode.h" #include "source/spirv_target_env.h" #include "source/val/instruction.h" +#include "source/val/validate.h" #include "source/val/validation_state.h" namespace spvtools { @@ -42,6 +42,144 @@ uint32_t GetSizeTBitWidth(const ValidationState_t& _) { return 0; } +// Check that the operand of a debug info instruction |inst| at |word_index| +// is a result id of an instruction with |expected_opcode|. +spv_result_t ValidateOperandForDebugInfo( + ValidationState_t& _, const std::string& operand_name, + SpvOp expected_opcode, const Instruction* inst, uint32_t word_index, + const std::function& ext_inst_name) { + auto* operand = _.FindDef(inst->word(word_index)); + if (operand->opcode() != expected_opcode) { + spv_opcode_desc desc = nullptr; + if (_.grammar().lookupOpcode(expected_opcode, &desc) != SPV_SUCCESS || + !desc) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << ext_inst_name() << ": " + << "expected operand " << operand_name << " is invalid"; + } + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << ext_inst_name() << ": " + << "expected operand " << operand_name << " must be a result id of " + << "Op" << desc->name; + } + return SPV_SUCCESS; +} + +#define CHECK_OPERAND(NAME, opcode, index) \ + do { \ + auto result = ValidateOperandForDebugInfo(_, NAME, opcode, inst, index, \ + ext_inst_name); \ + if (result != SPV_SUCCESS) return result; \ + } while (0) + +// True if the operand of a debug info instruction |inst| at |word_index| +// satisifies |expectation| that is given as a function. Otherwise, +// returns false. +bool DoesDebugInfoOperandMatchExpectation( + const ValidationState_t& _, + const std::function& expectation, + const Instruction* inst, uint32_t word_index) { + auto* debug_inst = _.FindDef(inst->word(word_index)); + if (debug_inst->opcode() != SpvOpExtInst || + debug_inst->ext_inst_type() != SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100 || + !expectation(OpenCLDebugInfo100Instructions(debug_inst->word(4)))) { + return false; + } + return true; +} + +// Check that the operand of a debug info instruction |inst| at |word_index| +// is a result id of an debug info instruction whose debug instruction type +// is |expected_debug_inst|. +spv_result_t ValidateDebugInfoOperand( + ValidationState_t& _, const std::string& debug_inst_name, + OpenCLDebugInfo100Instructions expected_debug_inst, const Instruction* inst, + uint32_t word_index, const std::function& ext_inst_name) { + std::function expectation = + [expected_debug_inst](OpenCLDebugInfo100Instructions dbg_inst) { + return dbg_inst == expected_debug_inst; + }; + if (DoesDebugInfoOperandMatchExpectation(_, expectation, inst, word_index)) + return SPV_SUCCESS; + + spv_ext_inst_desc desc = nullptr; + _.grammar().lookupExtInst(SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100, + expected_debug_inst, &desc); + if (_.grammar().lookupExtInst(SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100, + expected_debug_inst, &desc) != SPV_SUCCESS || + !desc) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << ext_inst_name() << ": " + << "expected operand " << debug_inst_name << " is invalid"; + } + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << ext_inst_name() << ": " + << "expected operand " << debug_inst_name << " must be a result id of " + << desc->name; +} + +#define CHECK_DEBUG_OPERAND(NAME, debug_opcode, index) \ + do { \ + auto result = ValidateDebugInfoOperand(_, NAME, debug_opcode, inst, index, \ + ext_inst_name); \ + if (result != SPV_SUCCESS) return result; \ + } while (0) + +// Check that the operand of a debug info instruction |inst| at |word_index| +// is a result id of an debug info instruction with DebugTypeBasic. +spv_result_t ValidateOperandBaseType( + ValidationState_t& _, const Instruction* inst, uint32_t word_index, + const std::function& ext_inst_name) { + return ValidateDebugInfoOperand(_, "Base Type", + OpenCLDebugInfo100DebugTypeBasic, inst, + word_index, ext_inst_name); +} + +// Check that the operand of a debug info instruction |inst| at |word_index| +// is a result id of a debug lexical scope instruction which is one of +// DebugCompilationUnit, DebugFunction, DebugLexicalBlock, or +// DebugTypeComposite. +spv_result_t ValidateOperandLexicalScope( + ValidationState_t& _, const std::string& debug_inst_name, + const Instruction* inst, uint32_t word_index, + const std::function& ext_inst_name) { + std::function expectation = + [](OpenCLDebugInfo100Instructions dbg_inst) { + return dbg_inst == OpenCLDebugInfo100DebugCompilationUnit || + dbg_inst == OpenCLDebugInfo100DebugFunction || + dbg_inst == OpenCLDebugInfo100DebugLexicalBlock || + dbg_inst == OpenCLDebugInfo100DebugTypeComposite; + }; + if (DoesDebugInfoOperandMatchExpectation(_, expectation, inst, word_index)) + return SPV_SUCCESS; + + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << ext_inst_name() << ": " + << "expected operand " << debug_inst_name + << " must be a result id of a lexical scope"; +} + +// Check that the operand of a debug info instruction |inst| at |word_index| +// is a result id of a debug type instruction (See DebugTypeXXX in +// "4.3. Type instructions" section of OpenCL.DebugInfo.100 spec. +spv_result_t ValidateOperandDebugType( + ValidationState_t& _, const std::string& debug_inst_name, + const Instruction* inst, uint32_t word_index, + const std::function& ext_inst_name) { + std::function expectation = + [](OpenCLDebugInfo100Instructions dbg_inst) { + return OpenCLDebugInfo100DebugTypeBasic <= dbg_inst && + dbg_inst <= OpenCLDebugInfo100DebugTypePtrToMember; + }; + if (DoesDebugInfoOperandMatchExpectation(_, expectation, inst, word_index)) + return SPV_SUCCESS; + + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << ext_inst_name() << ": " + << "expected operand " << debug_inst_name + << " is not a valid debug type"; +} + } // anonymous namespace spv_result_t ValidateExtension(ValidationState_t& _, const Instruction* inst) { @@ -2028,6 +2166,317 @@ spv_result_t ValidateExtInst(ValidationState_t& _, const Instruction* inst) { break; } } + } else if (ext_inst_type == SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100) { + if (!_.IsVoidType(result_type)) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << ext_inst_name() << ": " + << "expected result type must be a result id of " + << "OpTypeVoid"; + } + + auto num_words = inst->words().size(); + + const OpenCLDebugInfo100Instructions ext_inst_key = + OpenCLDebugInfo100Instructions(ext_inst_index); + switch (ext_inst_key) { + case OpenCLDebugInfo100DebugInfoNone: + case OpenCLDebugInfo100DebugNoScope: + case OpenCLDebugInfo100DebugOperation: + // The binary parser validates the opcode for DebugInfoNone, + // DebugNoScope, DebugOperation, and the literal values don't need + // further checks. + break; + case OpenCLDebugInfo100DebugCompilationUnit: { + CHECK_DEBUG_OPERAND("Source", OpenCLDebugInfo100DebugSource, 7); + break; + } + case OpenCLDebugInfo100DebugSource: { + CHECK_OPERAND("File", SpvOpString, 5); + if (num_words == 7) CHECK_OPERAND("Text", SpvOpString, 6); + break; + } + case OpenCLDebugInfo100DebugTypeBasic: { + CHECK_OPERAND("Name", SpvOpString, 5); + CHECK_OPERAND("Size", SpvOpConstant, 6); + // "Encoding" param is already validated by the binary parsing stage. + break; + } + case OpenCLDebugInfo100DebugTypePointer: + case OpenCLDebugInfo100DebugTypeQualifier: { + auto validate_base_type = + ValidateOperandBaseType(_, inst, 5, ext_inst_name); + if (validate_base_type != SPV_SUCCESS) return validate_base_type; + break; + } + case OpenCLDebugInfo100DebugTypeVector: { + auto validate_base_type = + ValidateOperandBaseType(_, inst, 5, ext_inst_name); + if (validate_base_type != SPV_SUCCESS) return validate_base_type; + + uint32_t component_count = inst->word(6); + if (!component_count || component_count > 4) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << ext_inst_name() << ": Component Count must be positive " + << "integer less than or equal to 4"; + } + break; + } + case OpenCLDebugInfo100DebugTypeArray: { + auto validate_base_type = + ValidateOperandDebugType(_, "Base Type", inst, 5, ext_inst_name); + if (validate_base_type != SPV_SUCCESS) return validate_base_type; + for (uint32_t i = 6; i < num_words; ++i) { + CHECK_OPERAND("Component Count", SpvOpConstant, i); + auto* component_count = _.FindDef(inst->word(i)); + if (!_.IsIntScalarType(component_count->type_id()) || + !component_count->word(3)) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << ext_inst_name() << ": Component Count must be positive " + << "integer"; + } + } + break; + } + case OpenCLDebugInfo100DebugTypedef: { + CHECK_OPERAND("Name", SpvOpString, 5); + auto validate_base_type = + ValidateOperandBaseType(_, inst, 6, ext_inst_name); + if (validate_base_type != SPV_SUCCESS) return validate_base_type; + CHECK_DEBUG_OPERAND("Source", OpenCLDebugInfo100DebugSource, 7); + auto validate_parent = + ValidateOperandLexicalScope(_, "Parent", inst, 10, ext_inst_name); + if (validate_parent != SPV_SUCCESS) return validate_parent; + break; + } + case OpenCLDebugInfo100DebugTypeFunction: { + auto* return_type = _.FindDef(inst->word(6)); + if (return_type->opcode() != SpvOpTypeVoid) { + auto validate_return = ValidateOperandDebugType( + _, "Return Type", inst, 6, ext_inst_name); + if (validate_return != SPV_SUCCESS) return validate_return; + } + for (uint32_t word_index = 7; word_index < num_words; ++word_index) { + auto validate_param = ValidateOperandDebugType( + _, "Parameter Types", inst, word_index, ext_inst_name); + if (validate_param != SPV_SUCCESS) return validate_param; + } + break; + } + case OpenCLDebugInfo100DebugTypeEnum: { + CHECK_OPERAND("Name", SpvOpString, 5); + if (!DoesDebugInfoOperandMatchExpectation( + _, + [](OpenCLDebugInfo100Instructions dbg_inst) { + return dbg_inst == OpenCLDebugInfo100DebugInfoNone; + }, + inst, 6)) { + auto validate_underlying_type = ValidateOperandDebugType( + _, "Underlying Types", inst, 6, ext_inst_name); + if (validate_underlying_type != SPV_SUCCESS) + return validate_underlying_type; + } + CHECK_DEBUG_OPERAND("Source", OpenCLDebugInfo100DebugSource, 7); + auto validate_parent = + ValidateOperandLexicalScope(_, "Parent", inst, 10, ext_inst_name); + if (validate_parent != SPV_SUCCESS) return validate_parent; + CHECK_OPERAND("Size", SpvOpConstant, 11); + auto* size = _.FindDef(inst->word(11)); + if (!_.IsIntScalarType(size->type_id()) || !size->word(3)) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << ext_inst_name() << ": expected operand Size is a " + << "positive integer"; + } + for (uint32_t word_index = 13; word_index + 1 < num_words; + word_index += 2) { + CHECK_OPERAND("Value", SpvOpConstant, word_index); + CHECK_OPERAND("Name", SpvOpString, word_index + 1); + } + break; + } + case OpenCLDebugInfo100DebugTypeComposite: { + CHECK_OPERAND("Name", SpvOpString, 5); + CHECK_DEBUG_OPERAND("Source", OpenCLDebugInfo100DebugSource, 7); + auto validate_parent = + ValidateOperandLexicalScope(_, "Parent", inst, 10, ext_inst_name); + if (validate_parent != SPV_SUCCESS) return validate_parent; + CHECK_OPERAND("Linkage Name", SpvOpString, 11); + CHECK_OPERAND("Size", SpvOpConstant, 12); + for (uint32_t word_index = 14; word_index < num_words; ++word_index) { + if (!DoesDebugInfoOperandMatchExpectation( + _, + [](OpenCLDebugInfo100Instructions dbg_inst) { + return dbg_inst == OpenCLDebugInfo100DebugTypeMember || + dbg_inst == OpenCLDebugInfo100DebugFunction || + dbg_inst == OpenCLDebugInfo100DebugTypeInheritance; + }, + inst, word_index)) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << ext_inst_name() << ": " + << "expected operand Members " + << "must be DebugTypeMember, DebugFunction, or " + "DebugTypeInheritance"; + } + } + break; + } + case OpenCLDebugInfo100DebugTypeMember: { + CHECK_OPERAND("Name", SpvOpString, 5); + auto validate_type = + ValidateOperandDebugType(_, "Type", inst, 6, ext_inst_name); + if (validate_type != SPV_SUCCESS) return validate_type; + CHECK_DEBUG_OPERAND("Source", OpenCLDebugInfo100DebugSource, 7); + CHECK_DEBUG_OPERAND("Parent", OpenCLDebugInfo100DebugTypeComposite, 10); + CHECK_OPERAND("Offset", SpvOpConstant, 11); + CHECK_OPERAND("Size", SpvOpConstant, 12); + if (num_words == 15) CHECK_OPERAND("Value", SpvOpConstant, 14); + break; + } + case OpenCLDebugInfo100DebugTypeInheritance: { + CHECK_DEBUG_OPERAND("Child", OpenCLDebugInfo100DebugTypeComposite, 5); + auto* debug_inst = _.FindDef(inst->word(5)); + auto composite_type = + OpenCLDebugInfo100DebugCompositeType(debug_inst->word(6)); + if (composite_type != OpenCLDebugInfo100Class && + composite_type != OpenCLDebugInfo100Structure) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << ext_inst_name() << ": " + << "expected operand Child must be class or struct debug type"; + } + CHECK_DEBUG_OPERAND("Parent", OpenCLDebugInfo100DebugTypeComposite, 6); + debug_inst = _.FindDef(inst->word(6)); + composite_type = + OpenCLDebugInfo100DebugCompositeType(debug_inst->word(6)); + if (composite_type != OpenCLDebugInfo100Class && + composite_type != OpenCLDebugInfo100Structure) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << ext_inst_name() << ": " + << "expected operand Parent must be class or struct debug " + "type"; + } + CHECK_OPERAND("Offset", SpvOpConstant, 7); + CHECK_OPERAND("Size", SpvOpConstant, 8); + break; + } + case OpenCLDebugInfo100DebugFunction: { + CHECK_OPERAND("Name", SpvOpString, 5); + auto validate_type = + ValidateOperandDebugType(_, "Type", inst, 6, ext_inst_name); + if (validate_type != SPV_SUCCESS) return validate_type; + CHECK_DEBUG_OPERAND("Source", OpenCLDebugInfo100DebugSource, 7); + auto validate_parent = + ValidateOperandLexicalScope(_, "Parent", inst, 10, ext_inst_name); + if (validate_parent != SPV_SUCCESS) return validate_parent; + CHECK_OPERAND("Linkage Name", SpvOpString, 11); + // TODO: The current OpenCL.100.DebugInfo spec says "Function + // is an OpFunction which is described by this instruction.". + // However, the function definition can be opted-out e.g., + // inlining. We assume that Function operand can be a + // DebugInfoNone, but we must discuss it and update the spec. + if (!DoesDebugInfoOperandMatchExpectation( + _, + [](OpenCLDebugInfo100Instructions dbg_inst) { + return dbg_inst == OpenCLDebugInfo100DebugInfoNone; + }, + inst, 14)) { + CHECK_OPERAND("Function", SpvOpFunction, 14); + } + if (num_words == 16) { + CHECK_DEBUG_OPERAND("Declaration", + OpenCLDebugInfo100DebugFunctionDeclaration, 15); + } + break; + } + case OpenCLDebugInfo100DebugFunctionDeclaration: { + CHECK_OPERAND("Name", SpvOpString, 5); + auto validate_type = + ValidateOperandDebugType(_, "Type", inst, 6, ext_inst_name); + if (validate_type != SPV_SUCCESS) return validate_type; + CHECK_DEBUG_OPERAND("Source", OpenCLDebugInfo100DebugSource, 7); + auto validate_parent = + ValidateOperandLexicalScope(_, "Parent", inst, 10, ext_inst_name); + if (validate_parent != SPV_SUCCESS) return validate_parent; + CHECK_OPERAND("Linkage Name", SpvOpString, 11); + break; + } + case OpenCLDebugInfo100DebugLexicalBlock: { + CHECK_DEBUG_OPERAND("Source", OpenCLDebugInfo100DebugSource, 5); + auto validate_parent = + ValidateOperandLexicalScope(_, "Parent", inst, 8, ext_inst_name); + if (validate_parent != SPV_SUCCESS) return validate_parent; + if (num_words == 10) CHECK_OPERAND("Name", SpvOpString, 9); + break; + } + case OpenCLDebugInfo100DebugScope: { + // TODO(https://gitlab.khronos.org/spirv/SPIR-V/issues/533): We are + // still in spec discussion about what must be "Scope" operand of + // DebugScope. Update this code if the conclusion is different. + auto validate_scope = + ValidateOperandLexicalScope(_, "Scope", inst, 5, ext_inst_name); + if (validate_scope != SPV_SUCCESS) return validate_scope; + if (num_words == 7) { + CHECK_DEBUG_OPERAND("Inlined At", OpenCLDebugInfo100DebugInlinedAt, + 6); + } + break; + } + case OpenCLDebugInfo100DebugLocalVariable: { + CHECK_OPERAND("Name", SpvOpString, 5); + auto validate_type = + ValidateOperandDebugType(_, "Type", inst, 6, ext_inst_name); + if (validate_type != SPV_SUCCESS) return validate_type; + CHECK_DEBUG_OPERAND("Source", OpenCLDebugInfo100DebugSource, 7); + auto validate_parent = + ValidateOperandLexicalScope(_, "Parent", inst, 10, ext_inst_name); + if (validate_parent != SPV_SUCCESS) return validate_parent; + break; + } + case OpenCLDebugInfo100DebugDeclare: { + CHECK_DEBUG_OPERAND("Local Variable", + OpenCLDebugInfo100DebugLocalVariable, 5); + + // TODO: We must discuss DebugDeclare.Variable of OpenCL.100.DebugInfo. + // Currently, it says "Variable must be an id of OpVariable instruction + // which defines the local variable.", but we want to allow + // OpFunctionParameter as well. + auto* operand = _.FindDef(inst->word(6)); + if (operand->opcode() != SpvOpVariable && + operand->opcode() != SpvOpFunctionParameter) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << ext_inst_name() << ": " + << "expected operand Variable must be a result id of " + "OpVariable or OpFunctionParameter"; + } + + CHECK_DEBUG_OPERAND("Expression", OpenCLDebugInfo100DebugExpression, 7); + break; + } + case OpenCLDebugInfo100DebugExpression: { + for (uint32_t word_index = 5; word_index < num_words; ++word_index) { + CHECK_DEBUG_OPERAND("Operation", OpenCLDebugInfo100DebugOperation, + word_index); + } + break; + } + + // TODO: Add validation rules for remaining cases as well. + case OpenCLDebugInfo100DebugTypePtrToMember: + case OpenCLDebugInfo100DebugTypeTemplate: + case OpenCLDebugInfo100DebugTypeTemplateParameter: + case OpenCLDebugInfo100DebugTypeTemplateTemplateParameter: + case OpenCLDebugInfo100DebugTypeTemplateParameterPack: + case OpenCLDebugInfo100DebugGlobalVariable: + case OpenCLDebugInfo100DebugLexicalBlockDiscriminator: + case OpenCLDebugInfo100DebugInlinedAt: + case OpenCLDebugInfo100DebugInlinedVariable: + case OpenCLDebugInfo100DebugValue: + case OpenCLDebugInfo100DebugMacroDef: + case OpenCLDebugInfo100DebugMacroUndef: + case OpenCLDebugInfo100DebugImportedEntity: + break; + case OpenCLDebugInfo100InstructionsMax: + assert(0); + break; + } } return SPV_SUCCESS; diff --git a/3rdparty/spirv-tools/source/val/validate_id.cpp b/3rdparty/spirv-tools/source/val/validate_id.cpp index c171d310d..e1a775a85 100644 --- a/3rdparty/spirv-tools/source/val/validate_id.cpp +++ b/3rdparty/spirv-tools/source/val/validate_id.cpp @@ -171,8 +171,8 @@ spv_result_t IdPass(ValidationState_t& _, Instruction* inst) { const auto opcode = inst->opcode(); if (spvOpcodeGeneratesType(def->opcode()) && !spvOpcodeGeneratesType(opcode) && !spvOpcodeIsDebug(opcode) && - !inst->IsNonSemantic() && !spvOpcodeIsDecoration(opcode) && - opcode != SpvOpFunction && + !inst->IsDebugInfo() && !inst->IsNonSemantic() && + !spvOpcodeIsDecoration(opcode) && opcode != SpvOpFunction && opcode != SpvOpCooperativeMatrixLengthNV && !(opcode == SpvOpSpecConstantOp && inst->word(3) == SpvOpCooperativeMatrixLengthNV)) { @@ -180,8 +180,8 @@ spv_result_t IdPass(ValidationState_t& _, Instruction* inst) { << "Operand " << _.getIdName(operand_word) << " cannot be a type"; } else if (def->type_id() == 0 && !spvOpcodeGeneratesType(opcode) && - !spvOpcodeIsDebug(opcode) && !inst->IsNonSemantic() && - !spvOpcodeIsDecoration(opcode) && + !spvOpcodeIsDebug(opcode) && !inst->IsDebugInfo() && + !inst->IsNonSemantic() && !spvOpcodeIsDecoration(opcode) && !spvOpcodeIsBranch(opcode) && opcode != SpvOpPhi && opcode != SpvOpExtInst && opcode != SpvOpExtInstImport && opcode != SpvOpSelectionMerge && diff --git a/3rdparty/spirv-tools/source/val/validate_image.cpp b/3rdparty/spirv-tools/source/val/validate_image.cpp index bc2753cf9..5b77058c8 100644 --- a/3rdparty/spirv-tools/source/val/validate_image.cpp +++ b/3rdparty/spirv-tools/source/val/validate_image.cpp @@ -149,6 +149,17 @@ bool IsExplicitLod(SpvOp opcode) { return false; } +bool IsValidLodOperand(const ValidationState_t& _, SpvOp opcode) { + switch (opcode) { + case SpvOpImageRead: + case SpvOpImageWrite: + case SpvOpImageSparseRead: + return _.HasCapability(SpvCapabilityImageReadWriteLodAMD); + default: + return IsExplicitLod(opcode); + } +} + // Returns true if the opcode is a Image instruction which applies // homogenous projection to the coordinates. bool IsProj(SpvOp opcode) { @@ -248,6 +259,7 @@ spv_result_t ValidateImageOperands(ValidationState_t& _, const bool is_implicit_lod = IsImplicitLod(opcode); const bool is_explicit_lod = IsExplicitLod(opcode); + const bool is_valid_lod_operand = IsValidLodOperand(_, opcode); // The checks should be done in the order of definition of OperandImage. @@ -277,7 +289,7 @@ spv_result_t ValidateImageOperands(ValidationState_t& _, } if (mask & SpvImageOperandsLodMask) { - if (!is_explicit_lod && opcode != SpvOpImageFetch && + if (!is_valid_lod_operand && opcode != SpvOpImageFetch && opcode != SpvOpImageSparseFetch) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Image Operand Lod can only be used with ExplicitLod opcodes " @@ -835,6 +847,7 @@ bool IsAllowedSampledImageOperand(SpvOp opcode) { case SpvOpImageSparseSampleDrefExplicitLod: case SpvOpImageSparseGather: case SpvOpImageSparseDrefGather: + case SpvOpCopyObject: return true; default: return false; diff --git a/3rdparty/spirv-tools/source/val/validate_scopes.cpp b/3rdparty/spirv-tools/source/val/validate_scopes.cpp index 320d82821..473d6e840 100644 --- a/3rdparty/spirv-tools/source/val/validate_scopes.cpp +++ b/3rdparty/spirv-tools/source/val/validate_scopes.cpp @@ -143,6 +143,20 @@ spv_result_t ValidateExecutionScope(ValidationState_t& _, << spvOpcodeString(opcode) << ": in WebGPU environment Execution Scope is limited to " << "Workgroup"; + } else { + _.function(inst->function()->id()) + ->RegisterExecutionModelLimitation( + [](SpvExecutionModel model, std::string* message) { + if (model != SpvExecutionModelGLCompute) { + if (message) { + *message = + ": in WebGPU environment, Workgroup Execution Scope is " + "limited to GLCompute execution model"; + } + return false; + } + return true; + }); } } @@ -261,6 +275,22 @@ spv_result_t ValidateMemoryScope(ValidationState_t& _, const Instruction* inst, } break; } + + if (value == SpvScopeWorkgroup) { + _.function(inst->function()->id()) + ->RegisterExecutionModelLimitation( + [](SpvExecutionModel model, std::string* message) { + if (model != SpvExecutionModelGLCompute) { + if (message) { + *message = + ": in WebGPU environment, Workgroup Memory Scope is " + "limited to GLCompute execution model"; + } + return false; + } + return true; + }); + } } // TODO(atgoo@github.com) Add checks for OpenCL and OpenGL environments. diff --git a/3rdparty/spirv-tools/source/val/validation_state.cpp b/3rdparty/spirv-tools/source/val/validation_state.cpp index 51aebbe51..0739148e4 100644 --- a/3rdparty/spirv-tools/source/val/validation_state.cpp +++ b/3rdparty/spirv-tools/source/val/validation_state.cpp @@ -1052,7 +1052,7 @@ void ValidationState_t::ComputeFunctionToEntryPointMapping() { } void ValidationState_t::ComputeRecursiveEntryPoints() { - for (const Function func : functions()) { + for (const Function& func : functions()) { std::stack call_stack; std::set visited; diff --git a/3rdparty/spirv-tools/utils/check_copyright.py b/3rdparty/spirv-tools/utils/check_copyright.py index 2d288a122..b2283009d 100755 --- a/3rdparty/spirv-tools/utils/check_copyright.py +++ b/3rdparty/spirv-tools/utils/check_copyright.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# coding=utf-8 # Copyright (c) 2016 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -31,7 +32,9 @@ AUTHORS = ['The Khronos Group Inc.', 'Google Inc.', 'Google LLC', 'Pierre Moreau', - 'Samsung Inc'] + 'Samsung Inc', + 'AndrĂ© Perez Maselco', + 'Vasyl Teliman'] CURRENT_YEAR='2020' YEARS = '(2014-2016|2015-2016|2016|2016-2017|2017|2017-2019|2018|2019|2020)' @@ -167,7 +170,7 @@ def alert_if_no_copyright(glob, comment_prefix): has_apache2 = False line_num = 0 apache_expected_end = 0 - with open(file) as contents: + with open(file, encoding='utf-8') as contents: for line in contents: line_num += 1 if COPYRIGHT_RE.search(line): diff --git a/3rdparty/spirv-tools/utils/generate_language_headers.py b/3rdparty/spirv-tools/utils/generate_language_headers.py index 029616336..83fa99e1f 100755 --- a/3rdparty/spirv-tools/utils/generate_language_headers.py +++ b/3rdparty/spirv-tools/utils/generate_language_headers.py @@ -159,27 +159,25 @@ def main(): import argparse parser = argparse.ArgumentParser(description='Generate language headers from a JSON grammar') - parser.add_argument('--extinst-name', - type=str, required=True, - help='The name to use in tokens') parser.add_argument('--extinst-grammar', metavar='', type=str, required=True, help='input JSON grammar file for extended instruction set') - parser.add_argument('--extinst-output-base', metavar='', + parser.add_argument('--extinst-output-path', metavar='', type=str, required=True, - help='Basename of the language-specific output file.') + help='Path of the language-specific output file.') args = parser.parse_args() with open(args.extinst_grammar) as json_file: grammar_json = json.loads(json_file.read()) - grammar = ExtInstGrammar(name = args.extinst_name, + grammar_name = os.path.splitext(os.path.basename(args.extinst_output_path))[0] + grammar = ExtInstGrammar(name = grammar_name, copyright = grammar_json['copyright'], instructions = grammar_json['instructions'], operand_kinds = grammar_json['operand_kinds'], version = grammar_json['version'], revision = grammar_json['revision']) - make_path_to_file(args.extinst_output_base) - with open(args.extinst_output_base + '.h', 'w') as f: + make_path_to_file(args.extinst_output_path) + with open(args.extinst_output_path, 'w') as f: f.write(CGenerator().generate(grammar)) diff --git a/3rdparty/spirv-tools/utils/generate_registry_tables.py b/3rdparty/spirv-tools/utils/generate_registry_tables.py index e662ba99b..28152ef3e 100755 --- a/3rdparty/spirv-tools/utils/generate_registry_tables.py +++ b/3rdparty/spirv-tools/utils/generate_registry_tables.py @@ -14,11 +14,30 @@ # limitations under the License. """Generates the vendor tool table from the SPIR-V XML registry.""" -import distutils.dir_util +import errno import os.path import xml.etree.ElementTree +def mkdir_p(directory): + """Make the directory, and all its ancestors as required. Any of the + directories are allowed to already exist. + This is compatible with Python down to 3.0. + """ + + if directory == "": + # We're being asked to make the current directory. + return + + try: + os.makedirs(directory) + except OSError as e: + if e.errno == errno.EEXIST and os.path.isdir(directory): + pass + else: + raise + + def generate_vendor_table(registry): """Returns a list of C style initializers for the registered vendors and their tools. @@ -62,7 +81,7 @@ def main(): with open(args.xml) as xml_in: registry = xml.etree.ElementTree.fromstring(xml_in.read()) - distutils.dir_util.mkpath(os.path.dirname(args.generator_output)) + mkdir_p(os.path.dirname(args.generator_output)) with open(args.generator_output, 'w') as f: f.write(generate_vendor_table(registry))