diff --git a/3rdparty/spirv-tools/include/generated/build-version.inc b/3rdparty/spirv-tools/include/generated/build-version.inc index 66955558d..4fd1cf8f7 100644 --- a/3rdparty/spirv-tools/include/generated/build-version.inc +++ b/3rdparty/spirv-tools/include/generated/build-version.inc @@ -1 +1 @@ -"v2020.5", "SPIRV-Tools v2020.5 276598ad50d33f1d1a56311520b17390a6bed635" +"v2020.5", "SPIRV-Tools v2020.5 1a2bfbb06fe9545a7aa26e85e4b92b876ca316fd" diff --git a/3rdparty/spirv-tools/source/CMakeLists.txt b/3rdparty/spirv-tools/source/CMakeLists.txt index 79a81a1c0..fa900e03e 100644 --- a/3rdparty/spirv-tools/source/CMakeLists.txt +++ b/3rdparty/spirv-tools/source/CMakeLists.txt @@ -414,7 +414,7 @@ if(ENABLE_SPIRV_TOOLS_INSTALL) install(FILES ${CMAKE_BINARY_DIR}/${SPIRV_TOOLS}Config.cmake DESTINATION ${PACKAGE_DIR}) endif(ENABLE_SPIRV_TOOLS_INSTALL) -if(MSVC) +if(MSVC AND (NOT ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang"))) # Enable parallel builds across four cores for this lib add_definitions(/MP4) endif() diff --git a/3rdparty/spirv-tools/source/fuzz/CMakeLists.txt b/3rdparty/spirv-tools/source/fuzz/CMakeLists.txt index 150ee314d..0a4df4ef8 100644 --- a/3rdparty/spirv-tools/source/fuzz/CMakeLists.txt +++ b/3rdparty/spirv-tools/source/fuzz/CMakeLists.txt @@ -50,6 +50,7 @@ if(SPIRV_BUILD_FUZZER) fuzzer_pass_add_synonyms.h fuzzer_pass_add_loads.h fuzzer_pass_add_local_variables.h + fuzzer_pass_add_loop_preheaders.h fuzzer_pass_add_no_contraction_decorations.h fuzzer_pass_add_parameters.h fuzzer_pass_add_relaxed_decorations.h @@ -67,13 +68,17 @@ if(SPIRV_BUILD_FUZZER) fuzzer_pass_invert_comparison_operators.h fuzzer_pass_interchange_signedness_of_integer_operands.h fuzzer_pass_interchange_zero_like_constants.h + fuzzer_pass_make_vector_operations_dynamic.h fuzzer_pass_merge_blocks.h fuzzer_pass_obfuscate_constants.h fuzzer_pass_outline_functions.h fuzzer_pass_permute_blocks.h fuzzer_pass_permute_function_parameters.h + fuzzer_pass_permute_instructions.h fuzzer_pass_permute_phi_operands.h + fuzzer_pass_propagate_instructions_up.h fuzzer_pass_push_ids_through_variables.h + fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.h fuzzer_pass_replace_copy_memories_with_loads_stores.h fuzzer_pass_replace_copy_objects_with_stores_loads.h fuzzer_pass_replace_linear_algebra_instructions.h @@ -132,13 +137,17 @@ if(SPIRV_BUILD_FUZZER) transformation_function_call.h transformation_invert_comparison_operator.h transformation_load.h + transformation_make_vector_operation_dynamic.h transformation_merge_blocks.h transformation_move_block_down.h + transformation_move_instruction_down.h transformation_outline_function.h transformation_permute_function_parameters.h transformation_permute_phi_operands.h + transformation_propagate_instruction_up.h transformation_push_id_through_variable.h transformation_record_synonymous_constants.h + transformation_replace_add_sub_mul_with_carrying_extended.h transformation_replace_boolean_constant_with_constant_binary.h transformation_replace_constant_with_uniform.h transformation_replace_copy_memory_with_load_store.h @@ -181,6 +190,7 @@ if(SPIRV_BUILD_FUZZER) fuzzer_pass_add_synonyms.cpp fuzzer_pass_add_loads.cpp fuzzer_pass_add_local_variables.cpp + fuzzer_pass_add_loop_preheaders.cpp fuzzer_pass_add_no_contraction_decorations.cpp fuzzer_pass_add_parameters.cpp fuzzer_pass_add_relaxed_decorations.cpp @@ -198,13 +208,17 @@ if(SPIRV_BUILD_FUZZER) fuzzer_pass_invert_comparison_operators.cpp fuzzer_pass_interchange_signedness_of_integer_operands.cpp fuzzer_pass_interchange_zero_like_constants.cpp + fuzzer_pass_make_vector_operations_dynamic.cpp fuzzer_pass_merge_blocks.cpp fuzzer_pass_obfuscate_constants.cpp fuzzer_pass_outline_functions.cpp fuzzer_pass_permute_blocks.cpp fuzzer_pass_permute_function_parameters.cpp + fuzzer_pass_permute_instructions.cpp fuzzer_pass_permute_phi_operands.cpp + fuzzer_pass_propagate_instructions_up.cpp fuzzer_pass_push_ids_through_variables.cpp + fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.cpp fuzzer_pass_replace_copy_memories_with_loads_stores.cpp fuzzer_pass_replace_copy_objects_with_stores_loads.cpp fuzzer_pass_replace_linear_algebra_instructions.cpp @@ -262,13 +276,17 @@ if(SPIRV_BUILD_FUZZER) transformation_function_call.cpp transformation_invert_comparison_operator.cpp transformation_load.cpp + transformation_make_vector_operation_dynamic.cpp transformation_merge_blocks.cpp transformation_move_block_down.cpp + transformation_move_instruction_down.cpp transformation_outline_function.cpp transformation_permute_function_parameters.cpp transformation_permute_phi_operands.cpp + transformation_propagate_instruction_up.cpp transformation_push_id_through_variable.cpp transformation_record_synonymous_constants.cpp + transformation_replace_add_sub_mul_with_carrying_extended.cpp transformation_replace_boolean_constant_with_constant_binary.cpp transformation_replace_constant_with_uniform.cpp transformation_replace_copy_memory_with_load_store.cpp @@ -292,7 +310,7 @@ if(SPIRV_BUILD_FUZZER) ${CMAKE_CURRENT_BINARY_DIR}/protobufs/spvtoolsfuzz.pb.cc ) - if(MSVC) + if(MSVC AND (NOT ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang"))) # Enable parallel builds across four cores for this lib add_definitions(/MP4) endif() diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer.cpp index ae4332bde..77ef8e2a9 100644 --- a/3rdparty/spirv-tools/source/fuzz/fuzzer.cpp +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer.cpp @@ -32,6 +32,7 @@ #include "source/fuzz/fuzzer_pass_add_image_sample_unused_components.h" #include "source/fuzz/fuzzer_pass_add_loads.h" #include "source/fuzz/fuzzer_pass_add_local_variables.h" +#include "source/fuzz/fuzzer_pass_add_loop_preheaders.h" #include "source/fuzz/fuzzer_pass_add_no_contraction_decorations.h" #include "source/fuzz/fuzzer_pass_add_parameters.h" #include "source/fuzz/fuzzer_pass_add_stores.h" @@ -49,12 +50,15 @@ #include "source/fuzz/fuzzer_pass_interchange_signedness_of_integer_operands.h" #include "source/fuzz/fuzzer_pass_interchange_zero_like_constants.h" #include "source/fuzz/fuzzer_pass_invert_comparison_operators.h" +#include "source/fuzz/fuzzer_pass_make_vector_operations_dynamic.h" #include "source/fuzz/fuzzer_pass_merge_blocks.h" #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_permute_instructions.h" #include "source/fuzz/fuzzer_pass_permute_phi_operands.h" +#include "source/fuzz/fuzzer_pass_propagate_instructions_up.h" #include "source/fuzz/fuzzer_pass_push_ids_through_variables.h" #include "source/fuzz/fuzzer_pass_replace_linear_algebra_instructions.h" #include "source/fuzz/fuzzer_pass_replace_parameter_with_global.h" @@ -239,6 +243,9 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run( MaybeAddPass( &passes, ir_context.get(), &transformation_context, &fuzzer_context, transformation_sequence_out); + MaybeAddPass( + &passes, ir_context.get(), &transformation_context, &fuzzer_context, + transformation_sequence_out); MaybeAddPass( &passes, ir_context.get(), &transformation_context, &fuzzer_context, transformation_sequence_out); @@ -266,6 +273,9 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run( MaybeAddPass( &passes, ir_context.get(), &transformation_context, &fuzzer_context, transformation_sequence_out); + MaybeAddPass( + &passes, ir_context.get(), &transformation_context, &fuzzer_context, + transformation_sequence_out); MaybeAddPass( &passes, ir_context.get(), &transformation_context, &fuzzer_context, transformation_sequence_out); @@ -281,6 +291,12 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run( MaybeAddPass( &passes, ir_context.get(), &transformation_context, &fuzzer_context, transformation_sequence_out); + MaybeAddPass( + &passes, ir_context.get(), &transformation_context, &fuzzer_context, + transformation_sequence_out); + MaybeAddPass( + &passes, ir_context.get(), &transformation_context, &fuzzer_context, + transformation_sequence_out); MaybeAddPass( &passes, ir_context.get(), &transformation_context, &fuzzer_context, transformation_sequence_out); diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_context.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer_context.cpp index dacdc58fd..ced25a5fb 100644 --- a/3rdparty/spirv-tools/source/fuzz/fuzzer_context.cpp +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_context.cpp @@ -38,6 +38,7 @@ const std::pair kChanceOfAddingImageSampleUnusedComponents = {20, 90}; const std::pair kChanceOfAddingLoad = {5, 50}; const std::pair kChanceOfAddingLocalVariable = {20, 90}; +const std::pair kChanceOfAddingLoopPreheader = {20, 90}; const std::pair kChanceOfAddingMatrixType = {20, 70}; const std::pair kChanceOfAddingNoContractionDecoration = { 5, 70}; @@ -72,13 +73,20 @@ const std::pair const std::pair kChanceOfInvertingComparisonOperators = { 20, 50}; const std::pair kChanceOfMakingDonorLivesafe = {40, 60}; +const std::pair kChanceOfMakingVectorOperationDynamic = { + 20, 90}; 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 kChanceOfPermutingInstructions = {20, 70}; const std::pair kChanceOfPermutingParameters = {30, 90}; const std::pair kChanceOfPermutingPhiOperands = {30, 90}; +const std::pair kChanceOfPropagatingInstructionsUp = {20, + 70}; const std::pair kChanceOfPushingIdThroughVariable = {5, 50}; +const std::pair + kChanceOfReplacingAddSubMulWithCarryingExtended = {20, 90}; const std::pair kChanceOfReplacingCopyMemoryWithLoadStore = {20, 90}; const std::pair kChanceOfReplacingCopyObjectWithStoreLoad = @@ -158,6 +166,8 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator, chance_of_adding_global_variable_ = ChooseBetweenMinAndMax(kChanceOfAddingGlobalVariable); chance_of_adding_load_ = ChooseBetweenMinAndMax(kChanceOfAddingLoad); + chance_of_adding_loop_preheader_ = + ChooseBetweenMinAndMax(kChanceOfAddingLoopPreheader); chance_of_adding_image_sample_unused_components_ = ChooseBetweenMinAndMax(kChanceOfAddingImageSampleUnusedComponents); chance_of_adding_local_variable_ = @@ -207,6 +217,8 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator, ChooseBetweenMinAndMax(kChanceOfInvertingComparisonOperators); chance_of_making_donor_livesafe_ = ChooseBetweenMinAndMax(kChanceOfMakingDonorLivesafe); + chance_of_making_vector_operation_dynamic_ = + ChooseBetweenMinAndMax(kChanceOfMakingVectorOperationDynamic); chance_of_merging_blocks_ = ChooseBetweenMinAndMax(kChanceOfMergingBlocks); chance_of_moving_block_down_ = ChooseBetweenMinAndMax(kChanceOfMovingBlockDown); @@ -214,12 +226,18 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator, ChooseBetweenMinAndMax(kChanceOfObfuscatingConstant); chance_of_outlining_function_ = ChooseBetweenMinAndMax(kChanceOfOutliningFunction); + chance_of_permuting_instructions_ = + ChooseBetweenMinAndMax(kChanceOfPermutingInstructions); chance_of_permuting_parameters_ = ChooseBetweenMinAndMax(kChanceOfPermutingParameters); chance_of_permuting_phi_operands_ = ChooseBetweenMinAndMax(kChanceOfPermutingPhiOperands); + chance_of_propagating_instructions_up_ = + ChooseBetweenMinAndMax(kChanceOfPropagatingInstructionsUp); chance_of_pushing_id_through_variable_ = ChooseBetweenMinAndMax(kChanceOfPushingIdThroughVariable); + chance_of_replacing_add_sub_mul_with_carrying_extended_ = + ChooseBetweenMinAndMax(kChanceOfReplacingAddSubMulWithCarryingExtended); chance_of_replacing_copy_memory_with_load_store_ = ChooseBetweenMinAndMax(kChanceOfReplacingCopyMemoryWithLoadStore); chance_of_replacing_copyobject_with_store_load_ = diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_context.h b/3rdparty/spirv-tools/source/fuzz/fuzzer_context.h index 24aad0ce1..947a834aa 100644 --- a/3rdparty/spirv-tools/source/fuzz/fuzzer_context.h +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_context.h @@ -136,6 +136,9 @@ class FuzzerContext { uint32_t GetChanceOfAddingLocalVariable() { return chance_of_adding_local_variable_; } + uint32_t GetChanceOfAddingLoopPreheader() { + return chance_of_adding_loop_preheader_; + } uint32_t GetChanceOfAddingMatrixType() { return chance_of_adding_matrix_type_; } @@ -198,6 +201,9 @@ class FuzzerContext { uint32_t ChanceOfMakingDonorLivesafe() { return chance_of_making_donor_livesafe_; } + uint32_t GetChanceOfMakingVectorOperationDynamic() { + return chance_of_making_vector_operation_dynamic_; + } uint32_t GetChanceOfMergingBlocks() { return chance_of_merging_blocks_; } uint32_t GetChanceOfMovingBlockDown() { return chance_of_moving_block_down_; } uint32_t GetChanceOfObfuscatingConstant() { @@ -206,15 +212,24 @@ class FuzzerContext { uint32_t GetChanceOfOutliningFunction() { return chance_of_outlining_function_; } + uint32_t GetChanceOfPermutingInstructions() { + return chance_of_permuting_instructions_; + } uint32_t GetChanceOfPermutingParameters() { return chance_of_permuting_parameters_; } uint32_t GetChanceOfPermutingPhiOperands() { return chance_of_permuting_phi_operands_; } + uint32_t GetChanceOfPropagatingInstructionsUp() { + return chance_of_propagating_instructions_up_; + } uint32_t GetChanceOfPushingIdThroughVariable() { return chance_of_pushing_id_through_variable_; } + uint32_t GetChanceOfReplacingAddSubMulWithCarryingExtended() { + return chance_of_replacing_add_sub_mul_with_carrying_extended_; + } uint32_t GetChanceOfReplacingCopyMemoryWithLoadStore() { return chance_of_replacing_copy_memory_with_load_store_; } @@ -336,6 +351,7 @@ class FuzzerContext { uint32_t chance_of_adding_image_sample_unused_components_; uint32_t chance_of_adding_load_; uint32_t chance_of_adding_local_variable_; + uint32_t chance_of_adding_loop_preheader_; uint32_t chance_of_adding_matrix_type_; uint32_t chance_of_adding_no_contraction_decoration_; uint32_t chance_of_adding_parameters; @@ -360,13 +376,17 @@ class FuzzerContext { uint32_t chance_of_interchanging_zero_like_constants_; uint32_t chance_of_inverting_comparison_operators_; uint32_t chance_of_making_donor_livesafe_; + uint32_t chance_of_making_vector_operation_dynamic_; 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_instructions_; uint32_t chance_of_permuting_parameters_; uint32_t chance_of_permuting_phi_operands_; + uint32_t chance_of_propagating_instructions_up_; uint32_t chance_of_pushing_id_through_variable_; + uint32_t chance_of_replacing_add_sub_mul_with_carrying_extended_; uint32_t chance_of_replacing_copy_memory_with_load_store_; uint32_t chance_of_replacing_copyobject_with_store_load_; uint32_t chance_of_replacing_id_with_synonym_; diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass.cpp index 8d1b28cca..262dbe376 100644 --- a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass.cpp +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass.cpp @@ -24,6 +24,7 @@ #include "source/fuzz/transformation_add_constant_null.h" #include "source/fuzz/transformation_add_constant_scalar.h" #include "source/fuzz/transformation_add_global_undef.h" +#include "source/fuzz/transformation_add_loop_preheader.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" @@ -533,5 +534,66 @@ void FuzzerPass::MaybeAddUseToReplace( std::make_pair(id_use_descriptor, replacement_id)); } +opt::BasicBlock* FuzzerPass::GetOrCreateSimpleLoopPreheader( + uint32_t header_id) { + auto header_block = fuzzerutil::MaybeFindBlock(GetIRContext(), header_id); + + assert(header_block && header_block->IsLoopHeader() && + "|header_id| should be the label id of a loop header"); + + auto predecessors = GetIRContext()->cfg()->preds(header_id); + + assert(predecessors.size() >= 2 && + "The block |header_id| should be reachable."); + + auto function = header_block->GetParent(); + + if (predecessors.size() == 2) { + // The header has a single out-of-loop predecessor, which could be a + // preheader. + + opt::BasicBlock* maybe_preheader; + + if (GetIRContext()->GetDominatorAnalysis(function)->Dominates( + header_id, predecessors[0])) { + // The first predecessor is the back-edge block, because the header + // dominates it, so the second one is out of the loop. + maybe_preheader = &*function->FindBlock(predecessors[1]); + } else { + // The first predecessor is out of the loop. + maybe_preheader = &*function->FindBlock(predecessors[0]); + } + + // |maybe_preheader| is a preheader if it branches unconditionally to + // the header. We also require it not to be a loop header. + if (maybe_preheader->terminator()->opcode() == SpvOpBranch && + !maybe_preheader->IsLoopHeader()) { + return maybe_preheader; + } + } + + // We need to add a preheader. + + // Get a fresh id for the preheader. + uint32_t preheader_id = GetFuzzerContext()->GetFreshId(); + + // Get a fresh id for each OpPhi instruction, if there is more than one + // out-of-loop predecessor. + std::vector phi_ids; + if (predecessors.size() > 2) { + header_block->ForEachPhiInst( + [this, &phi_ids](opt::Instruction* /* unused */) { + phi_ids.push_back(GetFuzzerContext()->GetFreshId()); + }); + } + + // Add the preheader. + ApplyTransformation( + TransformationAddLoopPreheader(header_id, preheader_id, phi_ids)); + + // Make the newly-created preheader the new entry block. + return &*function->FindBlock(preheader_id); +} + } // 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 2777f24c7..c8d4a4494 100644 --- a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass.h +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass.h @@ -283,6 +283,16 @@ class FuzzerPass { std::vector>* uses_to_replace); + // Returns the preheader of the loop with header |header_id|, which satisfies + // all of the following conditions: + // - It is the only out-of-loop predecessor of the header + // - It unconditionally branches to the header + // - It is not a loop header itself + // If such preheader does not exist, a new one is added and returned. + // Requires |header_id| to be the label id of a loop header block that is + // reachable in the CFG (and thus has at least 2 predecessors). + opt::BasicBlock* GetOrCreateSimpleLoopPreheader(uint32_t header_id); + private: opt::IRContext* ir_context_; TransformationContext* transformation_context_; 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 index 9170ada22..62fcfea7f 100644 --- a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_equation_instructions.cpp +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_equation_instructions.cpp @@ -21,6 +21,26 @@ namespace spvtools { namespace fuzz { +namespace { + +bool IsBitWidthSupported(opt::IRContext* ir_context, uint32_t bit_width) { + switch (bit_width) { + case 32: + return true; + case 64: + return ir_context->get_feature_mgr()->HasCapability( + SpvCapabilityFloat64) && + ir_context->get_feature_mgr()->HasCapability(SpvCapabilityInt64); + case 16: + return ir_context->get_feature_mgr()->HasCapability( + SpvCapabilityFloat16) && + ir_context->get_feature_mgr()->HasCapability(SpvCapabilityInt16); + default: + return false; + } +} + +} // namespace FuzzerPassAddEquationInstructions::FuzzerPassAddEquationInstructions( opt::IRContext* ir_context, TransformationContext* transformation_context, @@ -76,8 +96,22 @@ void FuzzerPassAddEquationInstructions::Apply() { switch (opcode) { case SpvOpConvertSToF: case SpvOpConvertUToF: { - auto candidate_instructions = - GetIntegerInstructions(available_instructions); + std::vector candidate_instructions; + for (const auto* inst : + GetIntegerInstructions(available_instructions)) { + const auto* type = + GetIRContext()->get_type_mgr()->GetType(inst->type_id()); + assert(type && "|inst| has invalid type"); + + if (const auto* vector_type = type->AsVector()) { + type = vector_type->element_type(); + } + + if (IsBitWidthSupported(GetIRContext(), + type->AsInteger()->width())) { + candidate_instructions.push_back(inst); + } + } if (candidate_instructions.empty()) { break; @@ -112,20 +146,8 @@ void FuzzerPassAddEquationInstructions::Apply() { return; } case SpvOpBitcast: { - std::vector candidate_instructions; - for (const auto* inst : available_instructions) { - const auto* type = - GetIRContext()->get_type_mgr()->GetType(inst->type_id()); - assert(type && "Instruction has invalid type"); - if ((type->AsVector() && - (type->AsVector()->element_type()->AsInteger() || - type->AsVector()->element_type()->AsFloat())) || - type->AsInteger() || type->AsFloat()) { - // We support OpBitcast for only scalars or vectors of - // numerical type. - candidate_instructions.push_back(inst); - } - } + const auto candidate_instructions = + GetNumericalInstructions(available_instructions); if (!candidate_instructions.empty()) { const auto* operand_inst = @@ -356,5 +378,36 @@ FuzzerPassAddEquationInstructions::RestrictToElementBitWidth( return result; } +std::vector +FuzzerPassAddEquationInstructions::GetNumericalInstructions( + const std::vector& instructions) const { + std::vector result; + + for (auto* inst : instructions) { + const auto* type = GetIRContext()->get_type_mgr()->GetType(inst->type_id()); + assert(type && "Instruction has invalid type"); + + if (const auto* vector_type = type->AsVector()) { + type = vector_type->element_type(); + } + + if (!type->AsInteger() && !type->AsFloat()) { + // Only numerical scalars or vectors of numerical components are + // supported. + continue; + } + + if (!IsBitWidthSupported(GetIRContext(), type->AsInteger() + ? type->AsInteger()->width() + : type->AsFloat()->width())) { + continue; + } + + 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 index 8328b6b8b..9ce581eb3 100644 --- a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_equation_instructions.h +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_equation_instructions.h @@ -51,6 +51,14 @@ class FuzzerPassAddEquationInstructions : public FuzzerPass { std::vector GetBooleanInstructions( const std::vector& instructions) const; + // Yields those instructions in |instructions| that have a scalar numerical or + // a vector of numerical components type. Only 16, 32 and 64-bit numericals + // are supported if both OpTypeInt and OpTypeFloat instructions can be created + // with the specified width (e.g. for 16-bit types both Float16 and Int16 + // capabilities must be present). + std::vector GetNumericalInstructions( + 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. diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_loop_preheaders.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_loop_preheaders.cpp new file mode 100644 index 000000000..bdc315136 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_loop_preheaders.cpp @@ -0,0 +1,66 @@ +// 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_loop_preheaders.h" + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/transformation_add_loop_preheader.h" + +namespace spvtools { +namespace fuzz { + +FuzzerPassAddLoopPreheaders::FuzzerPassAddLoopPreheaders( + opt::IRContext* ir_context, TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations) + : FuzzerPass(ir_context, transformation_context, fuzzer_context, + transformations) {} + +FuzzerPassAddLoopPreheaders::~FuzzerPassAddLoopPreheaders() = default; + +void FuzzerPassAddLoopPreheaders::Apply() { + for (auto& function : *GetIRContext()->module()) { + // Keep track of all the loop headers we want to add a preheader to. + std::vector loop_header_ids_to_consider; + for (auto& block : function) { + // We only care about loop headers. + if (!block.IsLoopHeader()) { + continue; + } + + // Randomly decide whether to consider this header. + if (!GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext()->GetChanceOfAddingLoopPreheader())) { + continue; + } + + // We exclude loop headers with just one predecessor (the back-edge block) + // because they are unreachable. + if (GetIRContext()->cfg()->preds(block.id()).size() < 2) { + continue; + } + + loop_header_ids_to_consider.push_back(block.id()); + } + + for (uint32_t header_id : loop_header_ids_to_consider) { + // If not already present, add a preheader which is not also a loop + // header. + GetOrCreateSimpleLoopPreheader(header_id); + } + } +} + +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_loop_preheaders.h b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_loop_preheaders.h new file mode 100644 index 000000000..a8350567a --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_loop_preheaders.h @@ -0,0 +1,43 @@ +// 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_LOOP_PREHEADERS_H +#define SOURCE_FUZZ_FUZZER_PASS_ADD_LOOP_PREHEADERS_H + +#include "source/fuzz/fuzzer_pass.h" + +namespace spvtools { +namespace fuzz { + +// A fuzzer pass that randomly adds simple loop preheaders to loops that do not +// have one. A simple loop preheader is a block that: +// - is the only out-of-loop predecessor of the header +// - branches unconditionally to the header +// - is not a loop header itself +class FuzzerPassAddLoopPreheaders : public FuzzerPass { + public: + FuzzerPassAddLoopPreheaders( + opt::IRContext* ir_context, TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations); + + ~FuzzerPassAddLoopPreheaders(); + + void Apply() override; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_FUZZER_PASS_ADD_LOOP_PREHEADERS_H 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 81326ac7f..4cc4044fe 100644 --- a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_copy_objects.cpp +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_copy_objects.cpp @@ -54,13 +54,13 @@ void FuzzerPassCopyObjects::Apply() { return; } - std::vector relevant_instructions = - FindAvailableInstructions( - function, block, inst_it, - [this](opt::IRContext* ir_context, opt::Instruction* inst) { - return fuzzerutil::CanMakeSynonymOf( - ir_context, *GetTransformationContext(), inst); - }); + const auto relevant_instructions = FindAvailableInstructions( + function, block, inst_it, + [this](opt::IRContext* ir_context, opt::Instruction* inst) { + return TransformationAddSynonym::IsInstructionValid( + ir_context, *GetTransformationContext(), inst, + protobufs::TransformationAddSynonym::COPY_OBJECT); + }); // At this point, |relevant_instructions| contains all the instructions // we might think of copying. 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 aa0e24301..a8b7ddc2c 100644 --- a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_donate_modules.cpp +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_donate_modules.cpp @@ -84,6 +84,16 @@ void FuzzerPassDonateModules::Apply() { void FuzzerPassDonateModules::DonateSingleModule( opt::IRContext* donor_ir_context, bool make_livesafe) { + // Check that the donated module has capabilities, supported by the recipient + // module. + for (const auto& capability_inst : donor_ir_context->capabilities()) { + auto capability = + static_cast(capability_inst.GetSingleWordInOperand(0)); + if (!GetIRContext()->get_feature_mgr()->HasCapability(capability)) { + return; + } + } + // The ids used by the donor module may very well clash with ids defined in // the recipient module. Furthermore, some instructions defined in the donor // module will be equivalent to instructions defined in the recipient module, @@ -646,12 +656,13 @@ void FuzzerPassDonateModules::HandleFunctions( } }); - if (make_livesafe) { - // Make the function livesafe and then add it. - AddLivesafeFunction(*function_to_donate, donor_ir_context, - *original_id_to_donated_id, donated_instructions); - } else { - // Add the function in a non-livesafe manner. + // If |make_livesafe| is true, try to add the function in a livesafe manner. + // Otherwise (if |make_lifesafe| is false or an attempt to make the function + // livesafe has failed), add the function in a non-livesafe manner. + if (!make_livesafe || + !MaybeAddLivesafeFunction(*function_to_donate, donor_ir_context, + *original_id_to_donated_id, + donated_instructions)) { ApplyTransformation(TransformationAddFunction(donated_instructions)); } } @@ -773,6 +784,7 @@ bool FuzzerPassDonateModules::IsBasicType( const opt::Instruction& instruction) const { switch (instruction.opcode()) { case SpvOpTypeArray: + case SpvOpTypeBool: case SpvOpTypeFloat: case SpvOpTypeInt: case SpvOpTypeMatrix: @@ -1007,7 +1019,96 @@ void FuzzerPassDonateModules::PrepareInstructionForDonation( input_operands)); } -void FuzzerPassDonateModules::AddLivesafeFunction( +bool FuzzerPassDonateModules::CreateLoopLimiterInfo( + opt::IRContext* donor_ir_context, const opt::BasicBlock& loop_header, + const std::map& original_id_to_donated_id, + protobufs::LoopLimiterInfo* out) { + assert(loop_header.IsLoopHeader() && "|loop_header| is not a loop header"); + + // Grab the loop header's id, mapped to its donated value. + out->set_loop_header_id(original_id_to_donated_id.at(loop_header.id())); + + // Get fresh ids that will be used to load the loop limiter, increment + // it, compare it with the loop limit, and an id for a new block that + // will contain the loop's original terminator. + out->set_load_id(GetFuzzerContext()->GetFreshId()); + out->set_increment_id(GetFuzzerContext()->GetFreshId()); + out->set_compare_id(GetFuzzerContext()->GetFreshId()); + out->set_logical_op_id(GetFuzzerContext()->GetFreshId()); + + // We are creating a branch from the back-edge block to the merge block. Thus, + // if merge block has any OpPhi instructions, we might need to adjust + // them. + + // Note that the loop might have an unreachable back-edge block. This means + // that the loop can't iterate, so we don't need to adjust anything. + const auto back_edge_block_id = TransformationAddFunction::GetBackEdgeBlockId( + donor_ir_context, loop_header.id()); + if (!back_edge_block_id) { + return true; + } + + auto* back_edge_block = donor_ir_context->cfg()->block(back_edge_block_id); + assert(back_edge_block && "|back_edge_block_id| is invalid"); + + const auto* merge_block = + donor_ir_context->cfg()->block(loop_header.MergeBlockId()); + assert(merge_block && "Loop header has invalid merge block id"); + + // We don't need to adjust anything if there is already a branch from + // the back-edge block to the merge block. + if (back_edge_block->IsSuccessor(merge_block)) { + return true; + } + + // Adjust OpPhi instructions in the |merge_block|. + for (const auto& inst : *merge_block) { + if (inst.opcode() != SpvOpPhi) { + break; + } + + // There is no simple way to ensure that a chosen operand for the OpPhi + // instruction will never cause any problems (e.g. if we choose an + // integer id, it might have a zero value when we branch from the back + // edge block. This might cause a division by 0 later in the function.). + // Thus, we ignore possible problems and proceed as follows: + // - if any of the existing OpPhi operands dominates the back-edge + // block - use it + // - if OpPhi has a basic type (see IsBasicType method) - create + // a zero constant + // - otherwise, we can't add a livesafe function. + uint32_t suitable_operand_id = 0; + for (uint32_t i = 0; i < inst.NumInOperands(); i += 2) { + auto dependency_inst_id = inst.GetSingleWordInOperand(i); + + if (fuzzerutil::IdIsAvailableBeforeInstruction( + donor_ir_context, back_edge_block->terminator(), + dependency_inst_id)) { + suitable_operand_id = original_id_to_donated_id.at(dependency_inst_id); + break; + } + } + + if (suitable_operand_id == 0 && + IsBasicType( + *donor_ir_context->get_def_use_mgr()->GetDef(inst.type_id()))) { + // We mark this constant as irrelevant so that we can replace it + // with more interesting value later. + suitable_operand_id = FindOrCreateZeroConstant( + original_id_to_donated_id.at(inst.type_id()), true); + } + + if (suitable_operand_id == 0) { + return false; + } + + out->add_phi_id(suitable_operand_id); + } + + return true; +} + +bool FuzzerPassDonateModules::MaybeAddLivesafeFunction( const opt::Function& function_to_donate, opt::IRContext* donor_ir_context, const std::map& original_id_to_donated_id, const std::vector& donated_instructions) { @@ -1035,16 +1136,13 @@ void FuzzerPassDonateModules::AddLivesafeFunction( for (auto& block : function_to_donate) { if (block.IsLoopHeader()) { protobufs::LoopLimiterInfo loop_limiter; - // Grab the loop header's id, mapped to its donated value. - loop_limiter.set_loop_header_id(original_id_to_donated_id.at(block.id())); - // Get fresh ids that will be used to load the loop limiter, increment - // it, compare it with the loop limit, and an id for a new block that - // will contain the loop's original terminator. - loop_limiter.set_load_id(GetFuzzerContext()->GetFreshId()); - loop_limiter.set_increment_id(GetFuzzerContext()->GetFreshId()); - loop_limiter.set_compare_id(GetFuzzerContext()->GetFreshId()); - loop_limiter.set_logical_op_id(GetFuzzerContext()->GetFreshId()); - loop_limiters.emplace_back(loop_limiter); + + if (!CreateLoopLimiterInfo(donor_ir_context, block, + original_id_to_donated_id, &loop_limiter)) { + return false; + } + + loop_limiters.emplace_back(std::move(loop_limiter)); } } @@ -1157,6 +1255,7 @@ void FuzzerPassDonateModules::AddLivesafeFunction( ApplyTransformation(TransformationAddFunction( donated_instructions, loop_limiter_variable_id, loop_limit, loop_limiters, kill_unreachable_return_value_id, access_chain_clamping_info)); + return true; } } // namespace fuzz diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_donate_modules.h b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_donate_modules.h index c59ad71d1..89858e440 100644 --- a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_donate_modules.h +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_donate_modules.h @@ -128,14 +128,24 @@ class FuzzerPassDonateModules : public FuzzerPass { std::map* original_id_to_donated_id, std::vector* donated_instructions); + // Tries to create a protobufs::LoopLimiterInfo given a loop header basic + // block. Returns true if successful and outputs loop limiter into the |out| + // variable. Otherwise, returns false. |out| contains an undefined value when + // this function returns false. + bool CreateLoopLimiterInfo( + opt::IRContext* donor_ir_context, const opt::BasicBlock& loop_header, + const std::map& original_id_to_donated_id, + protobufs::LoopLimiterInfo* out); + // Requires that |donated_instructions| represents a prepared version of the // instructions of |function_to_donate| (which comes from |donor_ir_context|) // ready for donation, and |original_id_to_donated_id| maps ids from // |donor_ir_context| to their corresponding ids in the recipient module. // - // Adds a livesafe version of the function, based on |donated_instructions|, - // to the recipient module. - void AddLivesafeFunction( + // Attempts to add a livesafe version of the function, based on + // |donated_instructions|, to the recipient module. Returns true if the + // donation was successful, false otherwise. + bool MaybeAddLivesafeFunction( const opt::Function& function_to_donate, opt::IRContext* donor_ir_context, const std::map& original_id_to_donated_id, const std::vector& donated_instructions); diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_interchange_signedness_of_integer_operands.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_interchange_signedness_of_integer_operands.cpp index 6c3aa7bfb..0e40b4963 100644 --- a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_interchange_signedness_of_integer_operands.cpp +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_interchange_signedness_of_integer_operands.cpp @@ -91,6 +91,13 @@ void FuzzerPassInterchangeSignednessOfIntegerOperands::Apply() { uint32_t FuzzerPassInterchangeSignednessOfIntegerOperands:: FindOrCreateToggledIntegerConstant(uint32_t id) { + // |id| must not be a specialization constant because we do not know the value + // of specialization constants. + if (opt::IsSpecConstantInst( + GetIRContext()->get_def_use_mgr()->GetDef(id)->opcode())) { + return 0; + } + auto constant = GetIRContext()->get_constant_mgr()->FindDeclaredConstant(id); // This pass only toggles integer constants. diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_interchange_zero_like_constants.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_interchange_zero_like_constants.cpp index 8bd670f3a..20575e115 100644 --- a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_interchange_zero_like_constants.cpp +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_interchange_zero_like_constants.cpp @@ -34,6 +34,12 @@ FuzzerPassInterchangeZeroLikeConstants:: uint32_t FuzzerPassInterchangeZeroLikeConstants::FindOrCreateToggledConstant( opt::Instruction* declaration) { + // |declaration| must not be a specialization constant because we do not know + // the value of specialization constants. + if (opt::IsSpecConstantInst(declaration->opcode())) { + return 0; + } + auto constant = GetIRContext()->get_constant_mgr()->FindDeclaredConstant( declaration->result_id()); @@ -107,4 +113,4 @@ void FuzzerPassInterchangeZeroLikeConstants::Apply() { } } } // namespace fuzz -} // namespace spvtools \ No newline at end of file +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_make_vector_operations_dynamic.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_make_vector_operations_dynamic.cpp new file mode 100644 index 000000000..f4f2a8026 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_make_vector_operations_dynamic.cpp @@ -0,0 +1,71 @@ +// 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_make_vector_operations_dynamic.h" + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/instruction_descriptor.h" +#include "source/fuzz/transformation_make_vector_operation_dynamic.h" + +namespace spvtools { +namespace fuzz { + +FuzzerPassMakeVectorOperationsDynamic::FuzzerPassMakeVectorOperationsDynamic( + opt::IRContext* ir_context, TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations) + : FuzzerPass(ir_context, transformation_context, fuzzer_context, + transformations) {} + +FuzzerPassMakeVectorOperationsDynamic:: + ~FuzzerPassMakeVectorOperationsDynamic() = default; + +void FuzzerPassMakeVectorOperationsDynamic::Apply() { + for (auto& function : *GetIRContext()->module()) { + for (auto& block : function) { + for (auto& instruction : block) { + // Randomly decide whether to try applying the transformation. + if (!GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext() + ->GetChanceOfMakingVectorOperationDynamic())) { + continue; + } + + // |instruction| must be a vector operation. + if (!TransformationMakeVectorOperationDynamic::IsVectorOperation( + GetIRContext(), &instruction)) { + continue; + } + + // Make sure |instruction| has only one indexing operand. + assert(instruction.NumInOperands() == + (instruction.opcode() == SpvOpCompositeExtract ? 2 : 3) && + "FuzzerPassMakeVectorOperationsDynamic: the composite " + "instruction must have " + "only one indexing operand."); + + // Applies the make vector operation dynamic transformation. + ApplyTransformation(TransformationMakeVectorOperationDynamic( + instruction.result_id(), + FindOrCreateIntegerConstant( + {instruction.GetSingleWordInOperand( + instruction.opcode() == SpvOpCompositeExtract ? 1 : 2)}, + 32, GetFuzzerContext()->ChooseEven(), false))); + } + } + } +} + +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_make_vector_operations_dynamic.h b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_make_vector_operations_dynamic.h new file mode 100644 index 000000000..dd51cde73 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_make_vector_operations_dynamic.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_MAKE_VECTOR_OPERATIONS_DYNAMIC_H_ +#define SOURCE_FUZZ_FUZZER_PASS_MAKE_VECTOR_OPERATIONS_DYNAMIC_H_ + +#include "source/fuzz/fuzzer_pass.h" + +namespace spvtools { +namespace fuzz { + +// Looks for OpCompositeExtract/Insert instructions on vectors, and replaces +// them with OpVectorExtract/InsertDynamic. +class FuzzerPassMakeVectorOperationsDynamic : public FuzzerPass { + public: + FuzzerPassMakeVectorOperationsDynamic( + opt::IRContext* ir_context, TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations); + + ~FuzzerPassMakeVectorOperationsDynamic() override; + + void Apply() override; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_FUZZER_PASS_MAKE_VECTOR_OPERATIONS_DYNAMIC_H_ diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_outline_functions.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_outline_functions.cpp index c2c254e85..3bd0a9f57 100644 --- a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_outline_functions.cpp +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_outline_functions.cpp @@ -47,32 +47,13 @@ void FuzzerPassOutlineFunctions::Apply() { for (auto& block : *function) { blocks.push_back(&block); } - auto entry_block = blocks[GetFuzzerContext()->RandomIndex(blocks)]; + auto entry_block = MaybeGetEntryBlockSuitableForOutlining( + blocks[GetFuzzerContext()->RandomIndex(blocks)]); - // If the entry block starts with OpPhi, try to split it. - if (entry_block->begin()->opcode() == SpvOpPhi) { - // Find the first non-OpPhi instruction. - opt::Instruction* non_phi_inst = nullptr; - for (auto& instruction : *entry_block) { - if (instruction.opcode() != SpvOpPhi) { - non_phi_inst = &instruction; - break; - } - } - - assert(non_phi_inst && "|non_phi_inst| must've been initialized"); - - // If the split was not applicable, the transformation will not work. - uint32_t new_block_id = GetFuzzerContext()->GetFreshId(); - if (!MaybeApplyTransformation(TransformationSplitBlock( - MakeInstructionDescriptor(non_phi_inst->result_id(), - non_phi_inst->opcode(), 0), - new_block_id))) { - return; - } - - // The new entry block is the newly-created block. - entry_block = &*function->FindBlock(new_block_id); + if (!entry_block) { + // The chosen block is not suitable to be the entry block of a region that + // will be outlined. + continue; } auto dominator_analysis = GetIRContext()->GetDominatorAnalysis(function); @@ -83,16 +64,26 @@ void FuzzerPassOutlineFunctions::Apply() { postdominates_entry_block != nullptr; postdominates_entry_block = postdominator_analysis->ImmediateDominator( postdominates_entry_block)) { + // Consider the block if it is dominated by the entry block, ignore it if + // it is a continue target. if (dominator_analysis->Dominates(entry_block, - postdominates_entry_block)) { + postdominates_entry_block) && + !GetIRContext()->GetStructuredCFGAnalysis()->IsContinueBlock( + postdominates_entry_block->id())) { candidate_exit_blocks.push_back(postdominates_entry_block); } } if (candidate_exit_blocks.empty()) { continue; } - auto exit_block = candidate_exit_blocks[GetFuzzerContext()->RandomIndex( - candidate_exit_blocks)]; + auto exit_block = MaybeGetExitBlockSuitableForOutlining( + candidate_exit_blocks[GetFuzzerContext()->RandomIndex( + candidate_exit_blocks)]); + + if (!exit_block) { + // The block chosen is not suitable + continue; + } auto region_blocks = TransformationOutlineFunction::GetRegionBlocks( GetIRContext(), entry_block, exit_block); @@ -122,5 +113,84 @@ void FuzzerPassOutlineFunctions::Apply() { } } +opt::BasicBlock* +FuzzerPassOutlineFunctions::MaybeGetEntryBlockSuitableForOutlining( + opt::BasicBlock* entry_block) { + // If the entry block is a loop header, we need to get or create its + // preheader and make it the entry block, if possible. + if (entry_block->IsLoopHeader()) { + auto predecessors = + GetIRContext()->cfg()->preds(entry_block->GetLabel()->result_id()); + + if (predecessors.size() < 2) { + // The header only has one predecessor (the back-edge block) and thus + // it is unreachable. The block cannot be adjusted to be suitable for + // outlining. + return nullptr; + } + + // Get or create a suitable preheader and make it become the entry block. + entry_block = + GetOrCreateSimpleLoopPreheader(entry_block->GetLabel()->result_id()); + } + + assert(!entry_block->IsLoopHeader() && + "The entry block cannot be a loop header at this point."); + + // If the entry block starts with OpPhi or OpVariable, try to split it. + if (entry_block->begin()->opcode() == SpvOpPhi || + entry_block->begin()->opcode() == SpvOpVariable) { + // Find the first non-OpPhi and non-OpVariable instruction. + auto non_phi_or_var_inst = &*entry_block->begin(); + while (non_phi_or_var_inst->opcode() == SpvOpPhi || + non_phi_or_var_inst->opcode() == SpvOpVariable) { + non_phi_or_var_inst = non_phi_or_var_inst->NextNode(); + } + + // Split the block. + uint32_t new_block_id = GetFuzzerContext()->GetFreshId(); + ApplyTransformation(TransformationSplitBlock( + MakeInstructionDescriptor(GetIRContext(), non_phi_or_var_inst), + new_block_id)); + + // The new entry block is the newly-created block. + entry_block = &*entry_block->GetParent()->FindBlock(new_block_id); + } + + return entry_block; +} + +opt::BasicBlock* +FuzzerPassOutlineFunctions::MaybeGetExitBlockSuitableForOutlining( + opt::BasicBlock* exit_block) { + // The exit block must not be a continue target. + assert(!GetIRContext()->GetStructuredCFGAnalysis()->IsContinueBlock( + exit_block->id()) && + "A candidate exit block cannot be a continue target."); + + // If the exit block is a merge block, try to split it and return the second + // block in the pair as the exit block. + if (GetIRContext()->GetStructuredCFGAnalysis()->IsMergeBlock( + exit_block->id())) { + uint32_t new_block_id = GetFuzzerContext()->GetFreshId(); + + // Find the first non-OpPhi instruction, after which to split. + auto split_before = &*exit_block->begin(); + while (split_before->opcode() == SpvOpPhi) { + split_before = split_before->NextNode(); + } + + if (!MaybeApplyTransformation(TransformationSplitBlock( + MakeInstructionDescriptor(GetIRContext(), split_before), + new_block_id))) { + return nullptr; + } + + return &*exit_block->GetParent()->FindBlock(new_block_id); + } + + return exit_block; +} + } // namespace fuzz } // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_outline_functions.h b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_outline_functions.h index 6532ed9a3..02022aa7c 100644 --- a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_outline_functions.h +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_outline_functions.h @@ -32,6 +32,31 @@ class FuzzerPassOutlineFunctions : public FuzzerPass { ~FuzzerPassOutlineFunctions(); void Apply() override; + + // Returns a block suitable to be an entry block for a region that can be + // outlined, i.e. a block that is not a loop header and that does not start + // with OpPhi or OpVariable. In particular, it returns: + // - |entry_block| if it is suitable + // - otherwise, a block found by: + // - looking for or creating a new preheader, if |entry_block| is a loop + // header + // - splitting the candidate entry block, if it starts with OpPhi or + // OpVariable. + // Returns nullptr if a suitable block cannot be found following the + // instructions above. + opt::BasicBlock* MaybeGetEntryBlockSuitableForOutlining( + opt::BasicBlock* entry_block); + + // Returns: + // - |exit_block| if it is not a merge block + // - the second block obtained by splitting |exit_block|, if |exit_block| is a + // merge block. + // Assumes that |exit_block| is not a continue target. + // The block returned by this function should be suitable to be the exit block + // of a region that can be outlined. + // Returns nullptr if |exit_block| is a merge block and it cannot be split. + opt::BasicBlock* MaybeGetExitBlockSuitableForOutlining( + opt::BasicBlock* exit_block); }; } // namespace fuzz diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_permute_instructions.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_permute_instructions.cpp new file mode 100644 index 000000000..6867053c6 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_permute_instructions.cpp @@ -0,0 +1,64 @@ +// 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 "source/fuzz/fuzzer_pass_permute_instructions.h" + +#include "source/fuzz/fuzzer_context.h" +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/instruction_descriptor.h" +#include "source/fuzz/transformation_move_instruction_down.h" + +namespace spvtools { +namespace fuzz { + +FuzzerPassPermuteInstructions::FuzzerPassPermuteInstructions( + opt::IRContext* ir_context, TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations) + : FuzzerPass(ir_context, transformation_context, fuzzer_context, + transformations) {} + +FuzzerPassPermuteInstructions::~FuzzerPassPermuteInstructions() = default; + +void FuzzerPassPermuteInstructions::Apply() { + // We are iterating over all instructions in all basic blocks. + for (auto& function : *GetIRContext()->module()) { + for (auto& block : function) { + // We need to collect all instructions in the block into a separate vector + // since application of the transformation below might invalidate + // iterators. + std::vector instructions; + for (auto& instruction : block) { + instructions.push_back(&instruction); + } + + // We consider all instructions in reverse to increase the possible number + // of applied transformations. + for (auto it = instructions.rbegin(); it != instructions.rend(); ++it) { + if (!GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext()->GetChanceOfPermutingInstructions())) { + continue; + } + + while (MaybeApplyTransformation(TransformationMoveInstructionDown( + MakeInstructionDescriptor(GetIRContext(), *it)))) { + // Apply the transformation as many times as possible. + } + } + } + } +} + +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_permute_instructions.h b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_permute_instructions.h new file mode 100644 index 000000000..e02ddfae0 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_permute_instructions.h @@ -0,0 +1,40 @@ +// 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_INSTRUCTIONS_H_ +#define SOURCE_FUZZ_FUZZER_PASS_PERMUTE_INSTRUCTIONS_H_ + +#include "source/fuzz/fuzzer_pass.h" + +namespace spvtools { +namespace fuzz { + +// Permutes instructions in every block of all while preserving the module's +// semantics. +class FuzzerPassPermuteInstructions : public FuzzerPass { + public: + FuzzerPassPermuteInstructions( + opt::IRContext* ir_context, TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations); + + ~FuzzerPassPermuteInstructions() override; + + void Apply() override; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_FUZZER_PASS_PERMUTE_INSTRUCTIONS_H_ diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_propagate_instructions_up.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_propagate_instructions_up.cpp new file mode 100644 index 000000000..2042d7c2c --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_propagate_instructions_up.cpp @@ -0,0 +1,64 @@ +// 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 "source/fuzz/fuzzer_pass_propagate_instructions_up.h" + +#include "source/fuzz/fuzzer_context.h" +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/instruction_descriptor.h" +#include "source/fuzz/transformation_propagate_instruction_up.h" + +namespace spvtools { +namespace fuzz { + +FuzzerPassPropagateInstructionsUp::FuzzerPassPropagateInstructionsUp( + opt::IRContext* ir_context, TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations) + : FuzzerPass(ir_context, transformation_context, fuzzer_context, + transformations) {} + +FuzzerPassPropagateInstructionsUp::~FuzzerPassPropagateInstructionsUp() = + default; + +void FuzzerPassPropagateInstructionsUp::Apply() { + for (const auto& function : *GetIRContext()->module()) { + for (const auto& block : function) { + if (!GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext()->GetChanceOfPropagatingInstructionsUp())) { + continue; + } + + if (TransformationPropagateInstructionUp::IsApplicableToBlock( + GetIRContext(), block.id())) { + std::map fresh_ids; + for (auto id : GetIRContext()->cfg()->preds(block.id())) { + auto& fresh_id = fresh_ids[id]; + + if (!fresh_id) { + // Create a fresh id if it hasn't been created yet. |fresh_id| will + // be default-initialized to 0 in this case. + fresh_id = GetFuzzerContext()->GetFreshId(); + } + } + + ApplyTransformation( + TransformationPropagateInstructionUp(block.id(), fresh_ids)); + } + } + } +} + +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_propagate_instructions_up.h b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_propagate_instructions_up.h new file mode 100644 index 000000000..d915b31eb --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_propagate_instructions_up.h @@ -0,0 +1,40 @@ +// 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_PROPAGATE_INSTRUCTIONS_UP_H_ +#define SOURCE_FUZZ_FUZZER_PASS_PROPAGATE_INSTRUCTIONS_UP_H_ + +#include "source/fuzz/fuzzer_pass.h" + +namespace spvtools { +namespace fuzz { + +// Decides whether to propagate instructions from some block into its +// predecessors. +class FuzzerPassPropagateInstructionsUp : public FuzzerPass { + public: + FuzzerPassPropagateInstructionsUp( + opt::IRContext* ir_context, TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations); + + ~FuzzerPassPropagateInstructionsUp() override; + + void Apply() override; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_FUZZER_PASS_PROPAGATE_INSTRUCTIONS_UP_H_ diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.cpp new file mode 100644 index 000000000..d506de6b4 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.cpp @@ -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. + +#include "source/fuzz/fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.h" + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/transformation_replace_add_sub_mul_with_carrying_extended.h" + +namespace spvtools { +namespace fuzz { + +namespace { +const uint32_t kArithmeticInstructionIndexLeftInOperand = 0; +} // namespace + +FuzzerPassReplaceAddsSubsMulsWithCarryingExtended:: + FuzzerPassReplaceAddsSubsMulsWithCarryingExtended( + opt::IRContext* ir_context, + TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations) + : FuzzerPass(ir_context, transformation_context, fuzzer_context, + transformations) {} + +FuzzerPassReplaceAddsSubsMulsWithCarryingExtended:: + ~FuzzerPassReplaceAddsSubsMulsWithCarryingExtended() = default; + +void FuzzerPassReplaceAddsSubsMulsWithCarryingExtended::Apply() { + for (auto& function : *GetIRContext()->module()) { + for (auto& block : function) { + for (auto& instruction : block) { + // Randomly decide whether to apply the transformation. + if (!GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext() + ->GetChanceOfReplacingAddSubMulWithCarryingExtended())) { + continue; + } + + // Check if the transformation can be applied to this instruction. + if (!TransformationReplaceAddSubMulWithCarryingExtended:: + IsInstructionSuitable(GetIRContext(), instruction)) { + continue; + } + + // Get the operand type id. We know that both operands have the same + // type. + uint32_t operand_type_id = + GetIRContext() + ->get_def_use_mgr() + ->GetDef(instruction.GetSingleWordInOperand( + kArithmeticInstructionIndexLeftInOperand)) + ->type_id(); + + // Ensure the required struct type exists. The struct type is based on + // the operand type. + FindOrCreateStructType({operand_type_id, operand_type_id}); + + ApplyTransformation(TransformationReplaceAddSubMulWithCarryingExtended( + GetFuzzerContext()->GetFreshId(), instruction.result_id())); + } + } + } +} +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.h b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.h new file mode 100644 index 000000000..f224d2bc3 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.h @@ -0,0 +1,42 @@ +// 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 SPIRV_TOOLS_FUZZER_PASS_REPLACE_ADDS_SUBS_MULS_WITH_CARRYING_EXTENDED_H +#define SPIRV_TOOLS_FUZZER_PASS_REPLACE_ADDS_SUBS_MULS_WITH_CARRYING_EXTENDED_H + +#include "source/fuzz/fuzzer_pass.h" + +namespace spvtools { +namespace fuzz { + +// A fuzzer pass that replaces instructions OpIAdd, OpISub, OpIMul with pairs of +// instructions. The first one (OpIAddCarry, OpISubBorrow, OpUMulExtended, +// OpSMulExtended) computes the result into a struct. The second one extracts +// the appropriate component from the struct to yield the original result. +class FuzzerPassReplaceAddsSubsMulsWithCarryingExtended : public FuzzerPass { + public: + FuzzerPassReplaceAddsSubsMulsWithCarryingExtended( + opt::IRContext* ir_context, TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations); + + ~FuzzerPassReplaceAddsSubsMulsWithCarryingExtended() override; + + void Apply() override; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SPIRV_TOOLS_FUZZER_PASS_REPLACE_ADDS_SUBS_MULS_WITH_CARRYING_EXTENDED_H diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_replace_linear_algebra_instructions.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_replace_linear_algebra_instructions.cpp index 984b7c342..c3e65789e 100644 --- a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_replace_linear_algebra_instructions.cpp +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_replace_linear_algebra_instructions.cpp @@ -36,23 +36,27 @@ FuzzerPassReplaceLinearAlgebraInstructions:: void FuzzerPassReplaceLinearAlgebraInstructions::Apply() { // For each instruction, checks whether it is a linear algebra instruction. In // this case, the transformation is randomly applied. - GetIRContext()->module()->ForEachInst([this](opt::Instruction* instruction) { - if (!spvOpcodeIsLinearAlgebra(instruction->opcode())) { - return; - } + for (auto& function : *GetIRContext()->module()) { + for (auto& block : function) { + for (auto& instruction : block) { + if (!spvOpcodeIsLinearAlgebra(instruction.opcode())) { + continue; + } - if (!GetFuzzerContext()->ChoosePercentage( - GetFuzzerContext() - ->GetChanceOfReplacingLinearAlgebraInstructions())) { - return; - } + if (!GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext() + ->GetChanceOfReplacingLinearAlgebraInstructions())) { + continue; + } - ApplyTransformation(TransformationReplaceLinearAlgebraInstruction( - GetFuzzerContext()->GetFreshIds( - TransformationReplaceLinearAlgebraInstruction:: - GetRequiredFreshIdCount(GetIRContext(), instruction)), - MakeInstructionDescriptor(GetIRContext(), instruction))); - }); + ApplyTransformation(TransformationReplaceLinearAlgebraInstruction( + GetFuzzerContext()->GetFreshIds( + TransformationReplaceLinearAlgebraInstruction:: + GetRequiredFreshIdCount(GetIRContext(), &instruction)), + MakeInstructionDescriptor(GetIRContext(), &instruction))); + } + } + } } } // namespace fuzz diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_util.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer_util.cpp index b2d568183..f9564bc0e 100644 --- a/3rdparty/spirv-tools/source/fuzz/fuzzer_util.cpp +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_util.cpp @@ -826,6 +826,12 @@ uint32_t UpdateFunctionType(opt::IRContext* ir_context, uint32_t function_id, operand_ids.insert(operand_ids.end(), parameter_type_ids.begin(), parameter_type_ids.end()); + // A trivial case - we change nothing. + if (FindFunctionType(ir_context, operand_ids) == + old_function_type->result_id()) { + return old_function_type->result_id(); + } + if (ir_context->get_def_use_mgr()->NumUsers(old_function_type) == 1 && FindFunctionType(ir_context, operand_ids) == 0) { // We can change |old_function_type| only if it's used once in the module @@ -849,17 +855,16 @@ uint32_t UpdateFunctionType(opt::IRContext* ir_context, uint32_t function_id, // existing one or create a new one. auto type_id = FindOrCreateFunctionType( ir_context, new_function_type_result_id, operand_ids); + assert(type_id != old_function_type->result_id() && + "We should've handled this case above"); - if (type_id != old_function_type->result_id()) { - function->DefInst().SetInOperand(1, {type_id}); + function->DefInst().SetInOperand(1, {type_id}); - // DefUseManager hasn't been updated yet, so if the following condition is - // true, then |old_function_type| will have no users when this function - // returns. We might as well remove it. - if (ir_context->get_def_use_mgr()->NumUsers(old_function_type) == 1) { - old_function_type->RemoveFromList(); - delete old_function_type; - } + // DefUseManager hasn't been updated yet, so if the following condition is + // true, then |old_function_type| will have no users when this function + // returns. We might as well remove it. + if (ir_context->get_def_use_mgr()->NumUsers(old_function_type) == 1) { + ir_context->KillInst(old_function_type); } return type_id; @@ -1312,6 +1317,31 @@ MapToRepeatedUInt32Pair(const std::map& data) { return result; } +opt::Instruction* GetLastInsertBeforeInstruction(opt::IRContext* ir_context, + uint32_t block_id, + SpvOp opcode) { + // CFG::block uses std::map::at which throws an exception when |block_id| is + // invalid. The error message is unhelpful, though. Thus, we test that + // |block_id| is valid here. + const auto* label_inst = ir_context->get_def_use_mgr()->GetDef(block_id); + (void)label_inst; // Make compilers happy in release mode. + assert(label_inst && label_inst->opcode() == SpvOpLabel && + "|block_id| is invalid"); + + auto* block = ir_context->cfg()->block(block_id); + auto it = block->rbegin(); + assert(it != block->rend() && "Basic block can't be empty"); + + if (block->GetMergeInst()) { + ++it; + assert(it != block->rend() && + "|block| must have at least two instructions:" + "terminator and a merge instruction"); + } + + return CanInsertOpcodeBeforeInstruction(opcode, &*it) ? &*it : nullptr; +} + } // 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 343c94e80..cb01ca70b 100644 --- a/3rdparty/spirv-tools/source/fuzz/fuzzer_util.h +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_util.h @@ -316,6 +316,10 @@ opt::Function* GetFunctionFromParameterId(opt::IRContext* ir_context, // more users, it is removed from the module. Returns the result id of the // OpTypeFunction instruction that is used as a type of the function with // |function_id|. +// +// CAUTION: When the old type of the function is removed from the module, its +// memory is deallocated. Be sure not to use any pointers to the old +// type when this function returns. uint32_t UpdateFunctionType(opt::IRContext* ir_context, uint32_t function_id, uint32_t new_function_type_result_id, uint32_t return_type_id, @@ -483,6 +487,12 @@ std::map RepeatedUInt32PairToMap( google::protobuf::RepeatedPtrField MapToRepeatedUInt32Pair(const std::map& data); +// Returns the last instruction in |block_id| before which an instruction with +// opcode |opcode| can be inserted, or nullptr if there is no such instruction. +opt::Instruction* GetLastInsertBeforeInstruction(opt::IRContext* ir_context, + uint32_t block_id, + SpvOp opcode); + } // namespace fuzzerutil } // namespace fuzz diff --git a/3rdparty/spirv-tools/source/fuzz/id_use_descriptor.cpp b/3rdparty/spirv-tools/source/fuzz/id_use_descriptor.cpp index eb8589df5..4e10146fa 100644 --- a/3rdparty/spirv-tools/source/fuzz/id_use_descriptor.cpp +++ b/3rdparty/spirv-tools/source/fuzz/id_use_descriptor.cpp @@ -52,7 +52,7 @@ protobufs::IdUseDescriptor MakeIdUseDescriptorFromUse( opt::IRContext* context, opt::Instruction* inst, uint32_t in_operand_index) { const auto& in_operand = inst->GetInOperand(in_operand_index); - assert(in_operand.type == SPV_OPERAND_TYPE_ID); + assert(spvIsInIdType(in_operand.type)); return MakeIdUseDescriptor(in_operand.words[0], MakeInstructionDescriptor(context, inst), in_operand_index); diff --git a/3rdparty/spirv-tools/source/fuzz/protobufs/spvtoolsfuzz.proto b/3rdparty/spirv-tools/source/fuzz/protobufs/spvtoolsfuzz.proto index 43219a1df..15a9b020a 100644 --- a/3rdparty/spirv-tools/source/fuzz/protobufs/spvtoolsfuzz.proto +++ b/3rdparty/spirv-tools/source/fuzz/protobufs/spvtoolsfuzz.proto @@ -408,6 +408,10 @@ message Transformation { TransformationReplaceCopyMemoryWithLoadStore replace_copy_memory_with_load_store = 61; TransformationReplaceLoadStoreWithCopyMemory replace_load_store_with_copy_memory = 62; TransformationAddLoopPreheader add_loop_preheader = 63; + TransformationMoveInstructionDown move_instruction_down = 64; + TransformationMakeVectorOperationDynamic make_vector_operation_dynamic = 65; + TransformationReplaceAddSubMulWithCarryingExtended replace_add_sub_mul_with_carrying_extended = 66; + TransformationPropagateInstructionUp propagate_instruction_up = 67; // Add additional option using the next available number. } } @@ -1105,6 +1109,21 @@ message TransformationLoad { } +message TransformationMakeVectorOperationDynamic { + + // A transformation that replaces the OpCompositeExtract and OpCompositeInsert + // instructions with the OpVectorExtractDynamic and OpVectorInsertDynamic instructions. + + // The composite instruction result id. + uint32 instruction_result_id = 1; + + // The OpCompositeExtract/Insert instructions accept integer literals as indices to the composite object. + // However, the OpVectorInsert/ExtractDynamic instructions require its single index to be an integer instruction. + // This is the result id of the integer instruction. + uint32 constant_index_id = 2; + +} + message TransformationMergeBlocks { // A transformation that merges a block with its predecessor. @@ -1124,6 +1143,15 @@ message TransformationMoveBlockDown { uint32 block_id = 1; } +message TransformationMoveInstructionDown { + + // Swaps |instruction| with the next instruction in the block. + + // The instruction to move down. + InstructionDescriptor instruction = 1; + +} + message TransformationOutlineFunction { // A transformation that outlines a single-entry single-exit region of a @@ -1212,6 +1240,26 @@ message TransformationPermutePhiOperands { } +message TransformationPropagateInstructionUp { + + // Propagates an instruction in the block into the block's predecessors. + // Concretely, this transformation clones some particular instruction from + // the |block_id| into every block's predecessor and replaces the original + // instruction with OpPhi. Take a look at the transformation class to learn + // more about how we choose what instruction to propagate. + + // Id of the block to propagate an instruction from. + uint32 block_id = 1; + + // A map from the id of some predecessor of the |block_id| to the fresh id. + // The map contains a fresh id for at least every predecessor of the |block_id|. + // The instruction is propagated by creating a number of clones - one clone for + // each predecessor. Fresh ids from this field are used as result ids of cloned + // instructions. + repeated UInt32Pair predecessor_id_to_fresh_id = 2; + +} + message TransformationPushIdThroughVariable { // A transformation that makes |value_synonym_id| and |value_id| to be @@ -1259,6 +1307,24 @@ message TransformationRecordSynonymousConstants { } +message TransformationReplaceAddSubMulWithCarryingExtended { + + // Replaces OpIAdd with OpIAddCarry, OpISub with OpISubBorrow, OpIMul + // with OpUMulExtended or OpSMulExtended (depending on the signedness + // of the operands) and stores the result into a |struct_fresh_id|. + // In the original instruction the result type id and the type ids of + // the operands must be the same. Then the transformation extracts + // the first element of the result into the original |result_id|. + // This value is the same as the result of the original instruction. + + // The fresh id of the intermediate result. + uint32 struct_fresh_id = 1; + + // The result id of the original instruction. + uint32 result_id = 2; + +} + message TransformationReplaceParameterWithGlobal { // Removes parameter with result id |parameter_id| from its function diff --git a/3rdparty/spirv-tools/source/fuzz/transformation.cpp b/3rdparty/spirv-tools/source/fuzz/transformation.cpp index 8e03279ca..c3f3befc9 100644 --- a/3rdparty/spirv-tools/source/fuzz/transformation.cpp +++ b/3rdparty/spirv-tools/source/fuzz/transformation.cpp @@ -54,13 +54,17 @@ #include "source/fuzz/transformation_function_call.h" #include "source/fuzz/transformation_invert_comparison_operator.h" #include "source/fuzz/transformation_load.h" +#include "source/fuzz/transformation_make_vector_operation_dynamic.h" #include "source/fuzz/transformation_merge_blocks.h" #include "source/fuzz/transformation_move_block_down.h" +#include "source/fuzz/transformation_move_instruction_down.h" #include "source/fuzz/transformation_outline_function.h" #include "source/fuzz/transformation_permute_function_parameters.h" #include "source/fuzz/transformation_permute_phi_operands.h" +#include "source/fuzz/transformation_propagate_instruction_up.h" #include "source/fuzz/transformation_push_id_through_variable.h" #include "source/fuzz/transformation_record_synonymous_constants.h" +#include "source/fuzz/transformation_replace_add_sub_mul_with_carrying_extended.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_copy_memory_with_load_store.h" @@ -190,10 +194,17 @@ std::unique_ptr Transformation::FromMessage( message.invert_comparison_operator()); case protobufs::Transformation::TransformationCase::kLoad: return MakeUnique(message.load()); + case protobufs::Transformation::TransformationCase:: + kMakeVectorOperationDynamic: + return MakeUnique( + message.make_vector_operation_dynamic()); case protobufs::Transformation::TransformationCase::kMergeBlocks: return MakeUnique(message.merge_blocks()); case protobufs::Transformation::TransformationCase::kMoveBlockDown: return MakeUnique(message.move_block_down()); + case protobufs::Transformation::TransformationCase::kMoveInstructionDown: + return MakeUnique( + message.move_instruction_down()); case protobufs::Transformation::TransformationCase::kOutlineFunction: return MakeUnique( message.outline_function()); @@ -204,6 +215,9 @@ std::unique_ptr Transformation::FromMessage( case protobufs::Transformation::TransformationCase::kPermutePhiOperands: return MakeUnique( message.permute_phi_operands()); + case protobufs::Transformation::TransformationCase::kPropagateInstructionUp: + return MakeUnique( + message.propagate_instruction_up()); case protobufs::Transformation::TransformationCase::kPushIdThroughVariable: return MakeUnique( message.push_id_through_variable()); @@ -212,9 +226,9 @@ std::unique_ptr Transformation::FromMessage( return MakeUnique( message.record_synonymous_constants()); case protobufs::Transformation::TransformationCase:: - kReplaceParameterWithGlobal: - return MakeUnique( - message.replace_parameter_with_global()); + kReplaceAddSubMulWithCarryingExtended: + return MakeUnique( + message.replace_add_sub_mul_with_carrying_extended()); case protobufs::Transformation::TransformationCase:: kReplaceBooleanConstantWithConstantBinary: return MakeUnique( @@ -242,6 +256,10 @@ std::unique_ptr Transformation::FromMessage( kReplaceLoadStoreWithCopyMemory: return MakeUnique( message.replace_load_store_with_copy_memory()); + case protobufs::Transformation::TransformationCase:: + kReplaceParameterWithGlobal: + return MakeUnique( + message.replace_parameter_with_global()); case protobufs::Transformation::TransformationCase:: kReplaceParamsWithStruct: return MakeUnique( diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_add_function.cpp b/3rdparty/spirv-tools/source/fuzz/transformation_add_function.cpp index 66b379ef6..44c7d0539 100644 --- a/3rdparty/spirv-tools/source/fuzz/transformation_add_function.cpp +++ b/3rdparty/spirv-tools/source/fuzz/transformation_add_function.cpp @@ -363,6 +363,22 @@ bool TransformationAddFunction::TryToMakeFunctionLivesafe( return true; } +uint32_t TransformationAddFunction::GetBackEdgeBlockId( + opt::IRContext* ir_context, uint32_t loop_header_block_id) { + const auto* loop_header_block = + ir_context->cfg()->block(loop_header_block_id); + assert(loop_header_block && "|loop_header_block_id| is invalid"); + + for (auto pred : ir_context->cfg()->preds(loop_header_block_id)) { + if (ir_context->GetDominatorAnalysis(loop_header_block->GetParent()) + ->Dominates(loop_header_block_id, pred)) { + return pred; + } + } + + return 0; +} + bool TransformationAddFunction::TryToAddLoopLimiters( opt::IRContext* ir_context, opt::Function* added_function) const { // Collect up all the loop headers so that we can subsequently add loop @@ -474,14 +490,8 @@ bool TransformationAddFunction::TryToAddLoopLimiters( for (auto loop_header : loop_headers) { // Look for the loop's back-edge block. This is a predecessor of the loop // header that is dominated by the loop header. - uint32_t back_edge_block_id = 0; - for (auto pred : ir_context->cfg()->preds(loop_header->id())) { - if (ir_context->GetDominatorAnalysis(added_function) - ->Dominates(loop_header->id(), pred)) { - back_edge_block_id = pred; - break; - } - } + const auto back_edge_block_id = + GetBackEdgeBlockId(ir_context, loop_header->id()); if (!back_edge_block_id) { // The loop's back-edge block must be unreachable. This means that the // loop cannot iterate, so there is no need to make it lifesafe; we can @@ -692,9 +702,7 @@ bool TransformationAddFunction::TryToAddLoopLimiters( back_edge_block_terminator->SetOpcode(SpvOpBranchConditional); back_edge_block_terminator->SetInOperands(opt::Instruction::OperandList( {{SPV_OPERAND_TYPE_ID, {loop_limiter_info.compare_id()}}, - {SPV_OPERAND_TYPE_ID, {loop_header->MergeBlockId()} - - }, + {SPV_OPERAND_TYPE_ID, {loop_header->MergeBlockId()}}, {SPV_OPERAND_TYPE_ID, {loop_header->id()}}})); } diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_add_function.h b/3rdparty/spirv-tools/source/fuzz/transformation_add_function.h index 4a84c705b..4ebf171c8 100644 --- a/3rdparty/spirv-tools/source/fuzz/transformation_add_function.h +++ b/3rdparty/spirv-tools/source/fuzz/transformation_add_function.h @@ -65,6 +65,12 @@ class TransformationAddFunction : public Transformation { opt::IRContext* ir_context, const opt::Instruction& composite_type_inst, uint32_t index_id); + // Returns id of the back-edge block, given the corresponding + // |loop_header_block_id|. |loop_header_block_id| must be the id of a loop + // header block. Returns 0 if the loop has no back-edge block. + static uint32_t GetBackEdgeBlockId(opt::IRContext* ir_context, + uint32_t loop_header_block_id); + private: // Attempts to create a function from the series of instructions in // |message_.instruction| and add it to |ir_context|. diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_add_parameter.cpp b/3rdparty/spirv-tools/source/fuzz/transformation_add_parameter.cpp index cc32362e2..6c0ab281e 100644 --- a/3rdparty/spirv-tools/source/fuzz/transformation_add_parameter.cpp +++ b/3rdparty/spirv-tools/source/fuzz/transformation_add_parameter.cpp @@ -14,8 +14,6 @@ #include "source/fuzz/transformation_add_parameter.h" -#include - #include "source/fuzz/fuzzer_util.h" namespace spvtools { @@ -72,13 +70,13 @@ void TransformationAddParameter::Apply( auto* function = fuzzerutil::FindFunction(ir_context, message_.function_id()); assert(function && "Can't find the function"); - auto parameter_type_id = + const auto new_parameter_type_id = fuzzerutil::GetTypeId(ir_context, message_.initializer_id()); - assert(parameter_type_id != 0 && "Initializer has invalid type"); + assert(new_parameter_type_id != 0 && "Initializer has invalid type"); // Add new parameters to the function. function->AddParameter(MakeUnique( - ir_context, SpvOpFunctionParameter, parameter_type_id, + ir_context, SpvOpFunctionParameter, new_parameter_type_id, message_.parameter_fresh_id(), opt::Instruction::OperandList())); fuzzerutil::UpdateModuleIdBound(ir_context, message_.parameter_fresh_id()); @@ -92,43 +90,30 @@ void TransformationAddParameter::Apply( message_.parameter_fresh_id()); // Fix all OpFunctionCall instructions. - ir_context->get_def_use_mgr()->ForEachUser( - &function->DefInst(), [this](opt::Instruction* call) { - if (call->opcode() != SpvOpFunctionCall || - call->GetSingleWordInOperand(0) != message_.function_id()) { - return; - } + for (auto* inst : fuzzerutil::GetCallers(ir_context, function->result_id())) { + inst->AddOperand({SPV_OPERAND_TYPE_ID, {message_.initializer_id()}}); + } - call->AddOperand({SPV_OPERAND_TYPE_ID, {message_.initializer_id()}}); - }); + // Update function's type. + { + // We use a separate scope here since |old_function_type| might become a + // dangling pointer after the call to the fuzzerutil::UpdateFunctionType. - auto* old_function_type = fuzzerutil::GetFunctionType(ir_context, function); - assert(old_function_type && "Function must have a valid type"); + const auto* old_function_type = + fuzzerutil::GetFunctionType(ir_context, function); + assert(old_function_type && "Function must have a valid type"); - if (ir_context->get_def_use_mgr()->NumUsers(old_function_type) == 1) { - // Adjust existing function type if it is used only by this function. - old_function_type->AddOperand({SPV_OPERAND_TYPE_ID, {parameter_type_id}}); - - // We must make sure that all dependencies of |old_function_type| are - // evaluated before |old_function_type| (i.e. the domination rules are not - // broken). Thus, we move |old_function_type| to the end of the list of all - // types in the module. - old_function_type->RemoveFromList(); - ir_context->AddType(std::unique_ptr(old_function_type)); - } else { - // Otherwise, either create a new type or use an existing one. - std::vector type_ids; - type_ids.reserve(old_function_type->NumInOperands() + 1); - - for (uint32_t i = 0, n = old_function_type->NumInOperands(); i < n; ++i) { - type_ids.push_back(old_function_type->GetSingleWordInOperand(i)); + std::vector parameter_type_ids; + for (uint32_t i = 1; i < old_function_type->NumInOperands(); ++i) { + parameter_type_ids.push_back( + old_function_type->GetSingleWordInOperand(i)); } - type_ids.push_back(parameter_type_id); + parameter_type_ids.push_back(new_parameter_type_id); - function->DefInst().SetInOperand( - 1, {fuzzerutil::FindOrCreateFunctionType( - ir_context, message_.function_type_fresh_id(), type_ids)}); + fuzzerutil::UpdateFunctionType( + ir_context, function->result_id(), message_.function_type_fresh_id(), + old_function_type->GetSingleWordInOperand(0), parameter_type_ids); } // Make sure our changes are analyzed. diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_add_type_float.cpp b/3rdparty/spirv-tools/source/fuzz/transformation_add_type_float.cpp index c0c434bb9..9f43c3edc 100644 --- a/3rdparty/spirv-tools/source/fuzz/transformation_add_type_float.cpp +++ b/3rdparty/spirv-tools/source/fuzz/transformation_add_type_float.cpp @@ -36,6 +36,28 @@ bool TransformationAddTypeFloat::IsApplicable( return false; } + // Checks float type width capabilities. + switch (message_.width()) { + case 16: + // The Float16 capability must be present. + if (!ir_context->get_feature_mgr()->HasCapability(SpvCapabilityFloat16)) { + return false; + } + break; + case 32: + // No capabilities needed. + break; + case 64: + // The Float64 capability must be present. + if (!ir_context->get_feature_mgr()->HasCapability(SpvCapabilityFloat64)) { + return false; + } + break; + default: + assert(false && "Unexpected float type width"); + return false; + } + // Applicable if there is no float type with this width already declared in // the module. return fuzzerutil::MaybeGetFloatType(ir_context, message_.width()) == 0; diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_add_type_int.cpp b/3rdparty/spirv-tools/source/fuzz/transformation_add_type_int.cpp index 20759fc84..e39a23dfd 100644 --- a/3rdparty/spirv-tools/source/fuzz/transformation_add_type_int.cpp +++ b/3rdparty/spirv-tools/source/fuzz/transformation_add_type_int.cpp @@ -38,6 +38,34 @@ bool TransformationAddTypeInt::IsApplicable( return false; } + // Checks integer type width capabilities. + switch (message_.width()) { + case 8: + // The Int8 capability must be present. + if (!ir_context->get_feature_mgr()->HasCapability(SpvCapabilityInt8)) { + return false; + } + break; + case 16: + // The Int16 capability must be present. + if (!ir_context->get_feature_mgr()->HasCapability(SpvCapabilityInt16)) { + return false; + } + break; + case 32: + // No capabilities needed. + break; + case 64: + // The Int64 capability must be present. + if (!ir_context->get_feature_mgr()->HasCapability(SpvCapabilityInt64)) { + return false; + } + break; + default: + assert(false && "Unexpected integer type width"); + return false; + } + // Applicable if there is no int type with this width and signedness already // declared in the module. return fuzzerutil::MaybeGetIntegerType(ir_context, message_.width(), diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_make_vector_operation_dynamic.cpp b/3rdparty/spirv-tools/source/fuzz/transformation_make_vector_operation_dynamic.cpp new file mode 100644 index 000000000..c960676c5 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/transformation_make_vector_operation_dynamic.cpp @@ -0,0 +1,111 @@ +// 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_make_vector_operation_dynamic.h" + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/instruction_descriptor.h" + +namespace spvtools { +namespace fuzz { + +TransformationMakeVectorOperationDynamic:: + TransformationMakeVectorOperationDynamic( + const spvtools::fuzz::protobufs:: + TransformationMakeVectorOperationDynamic& message) + : message_(message) {} + +TransformationMakeVectorOperationDynamic:: + TransformationMakeVectorOperationDynamic(uint32_t instruction_result_id, + uint32_t constant_index_id) { + message_.set_instruction_result_id(instruction_result_id); + message_.set_constant_index_id(constant_index_id); +} + +bool TransformationMakeVectorOperationDynamic::IsApplicable( + opt::IRContext* ir_context, const TransformationContext& /*unused*/) const { + // |instruction| must be a vector operation. + auto instruction = + ir_context->get_def_use_mgr()->GetDef(message_.instruction_result_id()); + if (!IsVectorOperation(ir_context, instruction)) { + return false; + } + + // |constant_index_instruction| must be defined as an integer instruction. + auto constant_index_instruction = + ir_context->get_def_use_mgr()->GetDef(message_.constant_index_id()); + if (!constant_index_instruction || !constant_index_instruction->type_id() || + !ir_context->get_type_mgr() + ->GetType(constant_index_instruction->type_id()) + ->AsInteger()) { + return false; + } + + return true; +} + +void TransformationMakeVectorOperationDynamic::Apply( + opt::IRContext* ir_context, TransformationContext* /*unused*/) const { + auto instruction = + ir_context->get_def_use_mgr()->GetDef(message_.instruction_result_id()); + + // The OpVectorInsertDynamic instruction has the vector and component operands + // in reverse order in relation to the OpCompositeInsert corresponding + // operands. + if (instruction->opcode() == SpvOpCompositeInsert) { + std::swap(instruction->GetInOperand(0), instruction->GetInOperand(1)); + } + + // Sets the literal operand to the equivalent constant. + instruction->SetInOperand( + instruction->opcode() == SpvOpCompositeExtract ? 1 : 2, + {message_.constant_index_id()}); + + // Sets the |instruction| opcode to the corresponding vector dynamic opcode. + instruction->SetOpcode(instruction->opcode() == SpvOpCompositeExtract + ? SpvOpVectorExtractDynamic + : SpvOpVectorInsertDynamic); +} + +protobufs::Transformation TransformationMakeVectorOperationDynamic::ToMessage() + const { + protobufs::Transformation result; + *result.mutable_make_vector_operation_dynamic() = message_; + return result; +} + +bool TransformationMakeVectorOperationDynamic::IsVectorOperation( + opt::IRContext* ir_context, opt::Instruction* instruction) { + // |instruction| must be defined and must be an OpCompositeExtract/Insert + // instruction. + if (!instruction || (instruction->opcode() != SpvOpCompositeExtract && + instruction->opcode() != SpvOpCompositeInsert)) { + return false; + } + + // The composite must be a vector. + auto composite_instruction = + ir_context->get_def_use_mgr()->GetDef(instruction->GetSingleWordInOperand( + instruction->opcode() == SpvOpCompositeExtract ? 0 : 1)); + if (!ir_context->get_type_mgr() + ->GetType(composite_instruction->type_id()) + ->AsVector()) { + return false; + } + + return true; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_make_vector_operation_dynamic.h b/3rdparty/spirv-tools/source/fuzz/transformation_make_vector_operation_dynamic.h new file mode 100644 index 000000000..e7d17494f --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/transformation_make_vector_operation_dynamic.h @@ -0,0 +1,63 @@ +// 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_MAKE_VECTOR_OPERATION_DYNAMIC_H_ +#define SOURCE_FUZZ_TRANSFORMATION_MAKE_VECTOR_OPERATION_DYNAMIC_H_ + +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" +#include "source/fuzz/transformation.h" +#include "source/fuzz/transformation_context.h" +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace fuzz { + +class TransformationMakeVectorOperationDynamic : public Transformation { + public: + explicit TransformationMakeVectorOperationDynamic( + const protobufs::TransformationMakeVectorOperationDynamic& message); + + TransformationMakeVectorOperationDynamic(uint32_t instruction_result_id, + uint32_t constant_index_id); + + // - |message_.instruction_result_id| must be the result id of an + // OpCompositeExtract/Insert instruction such that the composite operand is a + // vector. + // - |message_.constant_index_id| must be the result id of an integer + // instruction such that its value equals the indexing literal of the + // OpCompositeExtract/Insert instruction. + bool IsApplicable( + opt::IRContext* ir_context, + const TransformationContext& transformation_context) const override; + + // Replaces the OpCompositeExtract and OpCompositeInsert instructions with the + // OpVectorExtractDynamic and OpVectorInsertDynamic instructions. + void Apply(opt::IRContext* ir_context, + TransformationContext* transformation_context) const override; + + protobufs::Transformation ToMessage() const override; + + // Checks |instruction| is defined, is an OpCompositeExtract/Insert + // instruction and the composite operand is a vector. + static bool IsVectorOperation(opt::IRContext* ir_context, + opt::Instruction* instruction); + + private: + protobufs::TransformationMakeVectorOperationDynamic message_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_TRANSFORMATION_MAKE_VECTOR_OPERATION_DYNAMIC_H_ diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_move_instruction_down.cpp b/3rdparty/spirv-tools/source/fuzz/transformation_move_instruction_down.cpp new file mode 100644 index 000000000..7e383540b --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/transformation_move_instruction_down.cpp @@ -0,0 +1,219 @@ +// 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 "source/fuzz/transformation_move_instruction_down.h" + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/instruction_descriptor.h" + +namespace spvtools { +namespace fuzz { + +TransformationMoveInstructionDown::TransformationMoveInstructionDown( + const protobufs::TransformationMoveInstructionDown& message) + : message_(message) {} + +TransformationMoveInstructionDown::TransformationMoveInstructionDown( + const protobufs::InstructionDescriptor& instruction) { + *message_.mutable_instruction() = instruction; +} + +bool TransformationMoveInstructionDown::IsApplicable( + opt::IRContext* ir_context, const TransformationContext& /*unused*/) const { + // |instruction| must be valid. + auto* inst = FindInstruction(message_.instruction(), ir_context); + if (!inst) { + return false; + } + + // Instruction's opcode must be supported by this transformation. + if (!IsOpcodeSupported(inst->opcode())) { + return false; + } + + auto* inst_block = ir_context->get_instr_block(inst); + assert(inst_block && + "Global instructions and function parameters are not supported"); + + auto inst_it = fuzzerutil::GetIteratorForInstruction(inst_block, inst); + assert(inst_it != inst_block->end() && + "Can't get an iterator for the instruction"); + + // |instruction| can't be the last instruction in the block. + auto successor_it = ++inst_it; + if (successor_it == inst_block->end()) { + return false; + } + + // Check that we can insert |instruction| after |inst_it|. + auto successors_successor_it = ++inst_it; + if (successors_successor_it == inst_block->end() || + !fuzzerutil::CanInsertOpcodeBeforeInstruction(inst->opcode(), + successors_successor_it)) { + return false; + } + + // Check that |instruction|'s successor doesn't depend on the |instruction|. + if (inst->result_id()) { + for (uint32_t i = 0; i < successor_it->NumInOperands(); ++i) { + const auto& operand = successor_it->GetInOperand(i); + if (operand.type == SPV_OPERAND_TYPE_ID && + operand.words[0] == inst->result_id()) { + return false; + } + } + } + + return true; +} + +void TransformationMoveInstructionDown::Apply( + opt::IRContext* ir_context, TransformationContext* /*unused*/) const { + auto* inst = FindInstruction(message_.instruction(), ir_context); + assert(inst && + "The instruction should've been validated in the IsApplicable"); + + auto inst_it = fuzzerutil::GetIteratorForInstruction( + ir_context->get_instr_block(inst), inst); + + // Move the instruction down in the block. + inst->InsertAfter(&*++inst_it); + + ir_context->InvalidateAnalyses(opt::IRContext::kAnalysisNone); +} + +protobufs::Transformation TransformationMoveInstructionDown::ToMessage() const { + protobufs::Transformation result; + *result.mutable_move_instruction_down() = message_; + return result; +} + +bool TransformationMoveInstructionDown::IsOpcodeSupported(SpvOp opcode) { + // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3605): + // We only support "simple" instructions that work don't with memory. + // We should extend this so that we support the ones that modify the memory + // too. + switch (opcode) { + case SpvOpNop: + case SpvOpUndef: + case SpvOpAccessChain: + case SpvOpInBoundsAccessChain: + case SpvOpArrayLength: + case SpvOpVectorExtractDynamic: + case SpvOpVectorInsertDynamic: + case SpvOpVectorShuffle: + case SpvOpCompositeConstruct: + case SpvOpCompositeExtract: + case SpvOpCompositeInsert: + case SpvOpCopyObject: + case SpvOpTranspose: + case SpvOpConvertFToU: + case SpvOpConvertFToS: + case SpvOpConvertSToF: + case SpvOpConvertUToF: + case SpvOpUConvert: + case SpvOpSConvert: + case SpvOpFConvert: + case SpvOpQuantizeToF16: + case SpvOpSatConvertSToU: + case SpvOpSatConvertUToS: + case SpvOpBitcast: + case SpvOpSNegate: + case SpvOpFNegate: + case SpvOpIAdd: + case SpvOpFAdd: + case SpvOpISub: + case SpvOpFSub: + case SpvOpIMul: + case SpvOpFMul: + case SpvOpUDiv: + case SpvOpSDiv: + case SpvOpFDiv: + case SpvOpUMod: + case SpvOpSRem: + case SpvOpSMod: + case SpvOpFRem: + case SpvOpFMod: + case SpvOpVectorTimesScalar: + case SpvOpMatrixTimesScalar: + case SpvOpVectorTimesMatrix: + case SpvOpMatrixTimesVector: + case SpvOpMatrixTimesMatrix: + case SpvOpOuterProduct: + case SpvOpDot: + case SpvOpIAddCarry: + case SpvOpISubBorrow: + case SpvOpUMulExtended: + case SpvOpSMulExtended: + case SpvOpAny: + case SpvOpAll: + case SpvOpIsNan: + case SpvOpIsInf: + case SpvOpIsFinite: + case SpvOpIsNormal: + case SpvOpSignBitSet: + case SpvOpLessOrGreater: + case SpvOpOrdered: + case SpvOpUnordered: + case SpvOpLogicalEqual: + case SpvOpLogicalNotEqual: + case SpvOpLogicalOr: + case SpvOpLogicalAnd: + case SpvOpLogicalNot: + case SpvOpSelect: + case SpvOpIEqual: + case SpvOpINotEqual: + case SpvOpUGreaterThan: + case SpvOpSGreaterThan: + case SpvOpUGreaterThanEqual: + case SpvOpSGreaterThanEqual: + case SpvOpULessThan: + case SpvOpSLessThan: + case SpvOpULessThanEqual: + case SpvOpSLessThanEqual: + case SpvOpFOrdEqual: + case SpvOpFUnordEqual: + case SpvOpFOrdNotEqual: + case SpvOpFUnordNotEqual: + case SpvOpFOrdLessThan: + case SpvOpFUnordLessThan: + case SpvOpFOrdGreaterThan: + case SpvOpFUnordGreaterThan: + case SpvOpFOrdLessThanEqual: + case SpvOpFUnordLessThanEqual: + case SpvOpFOrdGreaterThanEqual: + case SpvOpFUnordGreaterThanEqual: + case SpvOpShiftRightLogical: + case SpvOpShiftRightArithmetic: + case SpvOpShiftLeftLogical: + case SpvOpBitwiseOr: + case SpvOpBitwiseXor: + case SpvOpBitwiseAnd: + case SpvOpNot: + case SpvOpBitFieldInsert: + case SpvOpBitFieldSExtract: + case SpvOpBitFieldUExtract: + case SpvOpBitReverse: + case SpvOpBitCount: + case SpvOpCopyLogical: + case SpvOpPtrEqual: + case SpvOpPtrNotEqual: + return true; + default: + return false; + } +} + +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_move_instruction_down.h b/3rdparty/spirv-tools/source/fuzz/transformation_move_instruction_down.h new file mode 100644 index 000000000..3f3c30211 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/transformation_move_instruction_down.h @@ -0,0 +1,62 @@ +// 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_MOVE_INSTRUCTION_DOWN_H_ +#define SOURCE_FUZZ_TRANSFORMATION_MOVE_INSTRUCTION_DOWN_H_ + +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" +#include "source/fuzz/transformation.h" +#include "source/fuzz/transformation_context.h" +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace fuzz { + +class TransformationMoveInstructionDown : public Transformation { + public: + explicit TransformationMoveInstructionDown( + const protobufs::TransformationMoveInstructionDown& message); + + explicit TransformationMoveInstructionDown( + const protobufs::InstructionDescriptor& instruction); + + // - |instruction| should be a descriptor of a valid instruction in the module + // - |instruction|'s opcode should be supported by this transformation + // - neither |instruction| nor its successor may be the last instruction in + // the block + // - |instruction|'s successor may not be dependent on the |instruction| + // - it should be possible to insert |instruction|'s opcode after its + // successor + bool IsApplicable( + opt::IRContext* ir_context, + const TransformationContext& transformation_context) const override; + + // Swaps |instruction| with its successor. + void Apply(opt::IRContext* ir_context, + TransformationContext* transformation_context) const override; + + protobufs::Transformation ToMessage() const override; + + private: + // Returns true if the |opcode| is supported by this transformation (i.e. + // we can move an instruction with this opcode). + static bool IsOpcodeSupported(SpvOp opcode); + + protobufs::TransformationMoveInstructionDown message_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_TRANSFORMATION_MOVE_INSTRUCTION_DOWN_H_ diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_propagate_instruction_up.cpp b/3rdparty/spirv-tools/source/fuzz/transformation_propagate_instruction_up.cpp new file mode 100644 index 000000000..adf3a5169 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/transformation_propagate_instruction_up.cpp @@ -0,0 +1,402 @@ +// 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 "source/fuzz/transformation_propagate_instruction_up.h" + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/instruction_descriptor.h" + +namespace spvtools { +namespace fuzz { +namespace { + +uint32_t GetResultIdFromLabelId(const opt::Instruction& phi_inst, + uint32_t label_id) { + assert(phi_inst.opcode() == SpvOpPhi && "|phi_inst| is not an OpPhi"); + + for (uint32_t i = 1; i < phi_inst.NumInOperands(); i += 2) { + if (phi_inst.GetSingleWordInOperand(i) == label_id) { + return phi_inst.GetSingleWordInOperand(i - 1); + } + } + + return 0; +} + +bool ContainsPointers(const opt::analysis::Type& type) { + switch (type.kind()) { + case opt::analysis::Type::kPointer: + return true; + case opt::analysis::Type::kStruct: + return std::any_of(type.AsStruct()->element_types().begin(), + type.AsStruct()->element_types().end(), + [](const opt::analysis::Type* element_type) { + return ContainsPointers(*element_type); + }); + default: + return false; + } +} + +bool HasValidDependencies(opt::IRContext* ir_context, opt::Instruction* inst) { + const auto* inst_block = ir_context->get_instr_block(inst); + assert(inst_block && + "This function shouldn't be applied to global instructions or function" + "parameters"); + + for (uint32_t i = 0; i < inst->NumInOperands(); ++i) { + const auto& operand = inst->GetInOperand(i); + if (operand.type != SPV_OPERAND_TYPE_ID) { + // Consider only operands. + continue; + } + + auto* dependency = ir_context->get_def_use_mgr()->GetDef(operand.words[0]); + assert(dependency && "Operand has invalid id"); + + if (ir_context->get_instr_block(dependency) == inst_block && + dependency->opcode() != SpvOpPhi) { + // |dependency| is "valid" if it's an OpPhi from the same basic block or + // an instruction from a different basic block. + return false; + } + } + + return true; +} + +} // namespace + +TransformationPropagateInstructionUp::TransformationPropagateInstructionUp( + const protobufs::TransformationPropagateInstructionUp& message) + : message_(message) {} + +TransformationPropagateInstructionUp::TransformationPropagateInstructionUp( + uint32_t block_id, + const std::map& predecessor_id_to_fresh_id) { + message_.set_block_id(block_id); + *message_.mutable_predecessor_id_to_fresh_id() = + fuzzerutil::MapToRepeatedUInt32Pair(predecessor_id_to_fresh_id); +} + +bool TransformationPropagateInstructionUp::IsApplicable( + opt::IRContext* ir_context, const TransformationContext& /*unused*/) const { + // Check that we can apply this transformation to the |block_id|. + if (!IsApplicableToBlock(ir_context, message_.block_id())) { + return false; + } + + const auto predecessor_id_to_fresh_id = fuzzerutil::RepeatedUInt32PairToMap( + message_.predecessor_id_to_fresh_id()); + for (auto id : ir_context->cfg()->preds(message_.block_id())) { + // Each predecessor must have a fresh id in the |predecessor_id_to_fresh_id| + // map. + if (!predecessor_id_to_fresh_id.count(id)) { + return false; + } + } + + std::vector maybe_fresh_ids; + maybe_fresh_ids.reserve(predecessor_id_to_fresh_id.size()); + for (const auto& entry : predecessor_id_to_fresh_id) { + maybe_fresh_ids.push_back(entry.second); + } + + // All ids must be unique and fresh. + return !fuzzerutil::HasDuplicates(maybe_fresh_ids) && + std::all_of(maybe_fresh_ids.begin(), maybe_fresh_ids.end(), + [ir_context](uint32_t id) { + return fuzzerutil::IsFreshId(ir_context, id); + }); +} + +void TransformationPropagateInstructionUp::Apply( + opt::IRContext* ir_context, TransformationContext* /*unused*/) const { + auto* inst = GetInstructionToPropagate(ir_context, message_.block_id()); + assert(inst && + "The block must have at least one supported instruction to propagate"); + assert(inst->result_id() && inst->type_id() && + "|inst| must have a result id and a type id"); + + opt::Instruction::OperandList op_phi_operands; + const auto predecessor_id_to_fresh_id = fuzzerutil::RepeatedUInt32PairToMap( + message_.predecessor_id_to_fresh_id()); + std::unordered_set visited_predecessors; + for (auto predecessor_id : ir_context->cfg()->preds(message_.block_id())) { + // A block can have multiple identical predecessors. + if (visited_predecessors.count(predecessor_id)) { + continue; + } + + visited_predecessors.insert(predecessor_id); + + auto new_result_id = predecessor_id_to_fresh_id.at(predecessor_id); + + // Compute InOperands for the OpPhi instruction to be inserted later. + op_phi_operands.push_back({SPV_OPERAND_TYPE_ID, {new_result_id}}); + op_phi_operands.push_back({SPV_OPERAND_TYPE_ID, {predecessor_id}}); + + // Create a clone of the |inst| to be inserted into the |predecessor_id|. + std::unique_ptr clone(inst->Clone(ir_context)); + clone->SetResultId(new_result_id); + + fuzzerutil::UpdateModuleIdBound(ir_context, new_result_id); + + // Adjust |clone|'s operands to account for possible dependencies on OpPhi + // instructions from the same basic block. + for (uint32_t i = 0; i < clone->NumInOperands(); ++i) { + auto& operand = clone->GetInOperand(i); + if (operand.type != SPV_OPERAND_TYPE_ID) { + // Consider only ids. + continue; + } + + const auto* dependency_inst = + ir_context->get_def_use_mgr()->GetDef(operand.words[0]); + assert(dependency_inst && "|clone| depends on an invalid id"); + + if (ir_context->get_instr_block(dependency_inst->result_id()) != + ir_context->cfg()->block(message_.block_id())) { + // We don't need to adjust anything if |dependency_inst| is from a + // different block, a global instruction or a function parameter. + continue; + } + + assert(dependency_inst->opcode() == SpvOpPhi && + "Propagated instruction can depend only on OpPhis from the same " + "basic block or instructions from different basic blocks"); + + auto new_id = GetResultIdFromLabelId(*dependency_inst, predecessor_id); + assert(new_id && "OpPhi instruction is missing a predecessor"); + operand.words[0] = new_id; + } + + auto* insert_before_inst = fuzzerutil::GetLastInsertBeforeInstruction( + ir_context, predecessor_id, clone->opcode()); + assert(insert_before_inst && "Can't insert |clone| into |predecessor_id"); + + insert_before_inst->InsertBefore(std::move(clone)); + } + + // Insert an OpPhi instruction into the basic block of |inst|. + ir_context->get_instr_block(inst)->begin()->InsertBefore( + MakeUnique(ir_context, SpvOpPhi, inst->type_id(), + inst->result_id(), + std::move(op_phi_operands))); + + // Remove |inst| from the basic block. + ir_context->KillInst(inst); + + // We have changed the module so most analyzes are now invalid. + ir_context->InvalidateAnalysesExceptFor( + opt::IRContext::Analysis::kAnalysisNone); +} + +protobufs::Transformation TransformationPropagateInstructionUp::ToMessage() + const { + protobufs::Transformation result; + *result.mutable_propagate_instruction_up() = message_; + return result; +} + +bool TransformationPropagateInstructionUp::IsOpcodeSupported(SpvOp opcode) { + // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3605): + // We only support "simple" instructions that don't work with memory. + // We should extend this so that we support the ones that modify the memory + // too. + switch (opcode) { + case SpvOpUndef: + case SpvOpAccessChain: + case SpvOpInBoundsAccessChain: + case SpvOpArrayLength: + case SpvOpVectorExtractDynamic: + case SpvOpVectorInsertDynamic: + case SpvOpVectorShuffle: + case SpvOpCompositeConstruct: + case SpvOpCompositeExtract: + case SpvOpCompositeInsert: + case SpvOpCopyObject: + case SpvOpTranspose: + case SpvOpConvertFToU: + case SpvOpConvertFToS: + case SpvOpConvertSToF: + case SpvOpConvertUToF: + case SpvOpUConvert: + case SpvOpSConvert: + case SpvOpFConvert: + case SpvOpQuantizeToF16: + case SpvOpSatConvertSToU: + case SpvOpSatConvertUToS: + case SpvOpBitcast: + case SpvOpSNegate: + case SpvOpFNegate: + case SpvOpIAdd: + case SpvOpFAdd: + case SpvOpISub: + case SpvOpFSub: + case SpvOpIMul: + case SpvOpFMul: + case SpvOpUDiv: + case SpvOpSDiv: + case SpvOpFDiv: + case SpvOpUMod: + case SpvOpSRem: + case SpvOpSMod: + case SpvOpFRem: + case SpvOpFMod: + case SpvOpVectorTimesScalar: + case SpvOpMatrixTimesScalar: + case SpvOpVectorTimesMatrix: + case SpvOpMatrixTimesVector: + case SpvOpMatrixTimesMatrix: + case SpvOpOuterProduct: + case SpvOpDot: + case SpvOpIAddCarry: + case SpvOpISubBorrow: + case SpvOpUMulExtended: + case SpvOpSMulExtended: + case SpvOpAny: + case SpvOpAll: + case SpvOpIsNan: + case SpvOpIsInf: + case SpvOpIsFinite: + case SpvOpIsNormal: + case SpvOpSignBitSet: + case SpvOpLessOrGreater: + case SpvOpOrdered: + case SpvOpUnordered: + case SpvOpLogicalEqual: + case SpvOpLogicalNotEqual: + case SpvOpLogicalOr: + case SpvOpLogicalAnd: + case SpvOpLogicalNot: + case SpvOpSelect: + case SpvOpIEqual: + case SpvOpINotEqual: + case SpvOpUGreaterThan: + case SpvOpSGreaterThan: + case SpvOpUGreaterThanEqual: + case SpvOpSGreaterThanEqual: + case SpvOpULessThan: + case SpvOpSLessThan: + case SpvOpULessThanEqual: + case SpvOpSLessThanEqual: + case SpvOpFOrdEqual: + case SpvOpFUnordEqual: + case SpvOpFOrdNotEqual: + case SpvOpFUnordNotEqual: + case SpvOpFOrdLessThan: + case SpvOpFUnordLessThan: + case SpvOpFOrdGreaterThan: + case SpvOpFUnordGreaterThan: + case SpvOpFOrdLessThanEqual: + case SpvOpFUnordLessThanEqual: + case SpvOpFOrdGreaterThanEqual: + case SpvOpFUnordGreaterThanEqual: + case SpvOpShiftRightLogical: + case SpvOpShiftRightArithmetic: + case SpvOpShiftLeftLogical: + case SpvOpBitwiseOr: + case SpvOpBitwiseXor: + case SpvOpBitwiseAnd: + case SpvOpNot: + case SpvOpBitFieldInsert: + case SpvOpBitFieldSExtract: + case SpvOpBitFieldUExtract: + case SpvOpBitReverse: + case SpvOpBitCount: + case SpvOpCopyLogical: + case SpvOpPtrEqual: + case SpvOpPtrNotEqual: + return true; + default: + return false; + } +} + +opt::Instruction* +TransformationPropagateInstructionUp::GetInstructionToPropagate( + opt::IRContext* ir_context, uint32_t block_id) { + auto* block = ir_context->cfg()->block(block_id); + assert(block && "|block_id| is invalid"); + + for (auto& inst : *block) { + // We look for the first instruction in the block that satisfies the + // following rules: + // - it's not an OpPhi + // - it must be supported by this transformation + // - it may depend only on instructions from different basic blocks or on + // OpPhi instructions from the same basic block. + if (inst.opcode() == SpvOpPhi || !IsOpcodeSupported(inst.opcode()) || + !inst.type_id() || !inst.result_id()) { + continue; + } + + const auto* inst_type = ir_context->get_type_mgr()->GetType(inst.type_id()); + assert(inst_type && "|inst| has invalid type"); + + if (!ir_context->get_feature_mgr()->HasCapability( + SpvCapabilityVariablePointersStorageBuffer) && + ContainsPointers(*inst_type)) { + // OpPhi supports pointer operands only with VariablePointers or + // VariablePointersStorageBuffer capabilities. + // + // Note that VariablePointers capability implicitly declares + // VariablePointersStorageBuffer capability. + continue; + } + + if (!HasValidDependencies(ir_context, &inst)) { + continue; + } + + return &inst; + } + + return nullptr; +} + +bool TransformationPropagateInstructionUp::IsApplicableToBlock( + opt::IRContext* ir_context, uint32_t block_id) { + // Check that |block_id| is valid. + const auto* label_inst = ir_context->get_def_use_mgr()->GetDef(block_id); + if (!label_inst || label_inst->opcode() != SpvOpLabel) { + return false; + } + + // Check that |block| has predecessors. + const auto& predecessors = ir_context->cfg()->preds(block_id); + if (predecessors.empty()) { + return false; + } + + // The block must contain an instruction to propagate. + const auto* inst_to_propagate = + GetInstructionToPropagate(ir_context, block_id); + if (!inst_to_propagate) { + return false; + } + + // We should be able to insert |inst_to_propagate| into every predecessor of + // |block|. + return std::all_of(predecessors.begin(), predecessors.end(), + [ir_context, inst_to_propagate](uint32_t predecessor_id) { + return fuzzerutil::GetLastInsertBeforeInstruction( + ir_context, predecessor_id, + inst_to_propagate->opcode()) != nullptr; + }); +} + +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_propagate_instruction_up.h b/3rdparty/spirv-tools/source/fuzz/transformation_propagate_instruction_up.h new file mode 100644 index 000000000..8e2374962 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/transformation_propagate_instruction_up.h @@ -0,0 +1,89 @@ +// 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_PROPAGATE_INSTRUCTION_UP_H_ +#define SOURCE_FUZZ_TRANSFORMATION_PROPAGATE_INSTRUCTION_UP_H_ + +#include + +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" +#include "source/fuzz/transformation.h" +#include "source/fuzz/transformation_context.h" +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace fuzz { + +class TransformationPropagateInstructionUp : public Transformation { + public: + explicit TransformationPropagateInstructionUp( + const protobufs::TransformationPropagateInstructionUp& message); + + TransformationPropagateInstructionUp( + uint32_t block_id, + const std::map& predecessor_id_to_fresh_id); + + // - |block_id| must be a valid result id of some OpLabel instruction. + // - |block_id| must have at least one predecessor + // - |block_id| must contain an instruction that can be propagated using this + // transformation + // - the instruction can be propagated if: + // - it's not an OpPhi + // - it is supported by this transformation + // - it depends only on instructions from different basic blocks or on + // OpPhi instructions from the same basic block + // - it should be possible to insert the propagated instruction at the end of + // each |block_id|'s predecessor + // - |predecessor_id_to_fresh_id| must have an entry for at least every + // predecessor of |block_id| + // - each value in the |predecessor_id_to_fresh_id| map must be a fresh id + // - all fresh ids in the |predecessor_id_to_fresh_id| must be unique + bool IsApplicable( + opt::IRContext* ir_context, + const TransformationContext& transformation_context) const override; + + // Inserts a copy of the propagated instruction into each |block_id|'s + // predecessor. Replaces the original instruction with an OpPhi referring + // inserted copies. + void Apply(opt::IRContext* ir_context, + TransformationContext* transformation_context) const override; + + protobufs::Transformation ToMessage() const override; + + // Returns true if this transformation can be applied to the block with id + // |block_id|. Concretely, returns true iff: + // - |block_id| is a valid id of some block in the module + // - |block_id| has predecessors + // - |block_id| contains an instruction that can be propagated + // - it is possible to insert the propagated instruction into every + // |block_id|'s predecessor + static bool IsApplicableToBlock(opt::IRContext* ir_context, + uint32_t block_id); + + private: + // Returns the instruction that will be propagated into the predecessors of + // the |block_id|. Returns nullptr if no such an instruction exists. + static opt::Instruction* GetInstructionToPropagate(opt::IRContext* ir_context, + uint32_t block_id); + + // Returns true if |opcode| is supported by this transformation. + static bool IsOpcodeSupported(SpvOp opcode); + + protobufs::TransformationPropagateInstructionUp message_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_TRANSFORMATION_PROPAGATE_INSTRUCTION_UP_H_ diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_replace_add_sub_mul_with_carrying_extended.cpp b/3rdparty/spirv-tools/source/fuzz/transformation_replace_add_sub_mul_with_carrying_extended.cpp new file mode 100644 index 000000000..ea84cf255 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/transformation_replace_add_sub_mul_with_carrying_extended.cpp @@ -0,0 +1,232 @@ +// 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_replace_add_sub_mul_with_carrying_extended.h" + +#include "source/fuzz/fuzzer_util.h" + +namespace spvtools { +namespace fuzz { + +namespace { +const uint32_t kOpCompositeExtractIndexLowOrderBits = 0; +const uint32_t kArithmeticInstructionIndexLeftInOperand = 0; +const uint32_t kArithmeticInstructionIndexRightInOperand = 1; +} // namespace + +TransformationReplaceAddSubMulWithCarryingExtended:: + TransformationReplaceAddSubMulWithCarryingExtended( + const spvtools::fuzz::protobufs:: + TransformationReplaceAddSubMulWithCarryingExtended& message) + : message_(message) {} + +TransformationReplaceAddSubMulWithCarryingExtended:: + TransformationReplaceAddSubMulWithCarryingExtended(uint32_t struct_fresh_id, + uint32_t result_id) { + message_.set_struct_fresh_id(struct_fresh_id); + message_.set_result_id(result_id); +} + +bool TransformationReplaceAddSubMulWithCarryingExtended::IsApplicable( + opt::IRContext* ir_context, const TransformationContext& /*unused*/) const { + // |message_.struct_fresh_id| must be fresh. + if (!fuzzerutil::IsFreshId(ir_context, message_.struct_fresh_id())) { + return false; + } + + // |message_.result_id| must refer to a suitable OpIAdd, OpISub or OpIMul + // instruction. The instruction must be defined. + auto instruction = + ir_context->get_def_use_mgr()->GetDef(message_.result_id()); + if (instruction == nullptr) { + return false; + } + if (!TransformationReplaceAddSubMulWithCarryingExtended:: + IsInstructionSuitable(ir_context, *instruction)) { + return false; + } + + // The struct type for holding the intermediate result must exist in the + // module. The struct type is based on the operand type. + uint32_t operand_type_id = ir_context->get_def_use_mgr() + ->GetDef(instruction->GetSingleWordInOperand( + kArithmeticInstructionIndexLeftInOperand)) + ->type_id(); + + uint32_t struct_type_id = fuzzerutil::MaybeGetStructType( + ir_context, {operand_type_id, operand_type_id}); + if (struct_type_id == 0) { + return false; + } + return true; +} + +void TransformationReplaceAddSubMulWithCarryingExtended::Apply( + opt::IRContext* ir_context, TransformationContext* /*unused*/) const { + // |message_.struct_fresh_id| must be fresh. + assert(fuzzerutil::IsFreshId(ir_context, message_.struct_fresh_id()) && + "|message_.struct_fresh_id| must be fresh"); + + // Get the signedness of an operand if it is an int or the signedness of a + // component if it is a vector. + auto type_id = + ir_context->get_def_use_mgr()->GetDef(message_.result_id())->type_id(); + auto type = ir_context->get_type_mgr()->GetType(type_id); + bool operand_is_signed; + if (type->kind() == opt::analysis::Type::kVector) { + auto operand_type = type->AsVector()->element_type(); + operand_is_signed = operand_type->AsInteger()->IsSigned(); + } else { + operand_is_signed = type->AsInteger()->IsSigned(); + } + + auto original_instruction = + ir_context->get_def_use_mgr()->GetDef(message_.result_id()); + + fuzzerutil::UpdateModuleIdBound(ir_context, message_.struct_fresh_id()); + + // Determine the opcode of the new instruction that computes the result into a + // struct. + SpvOp new_instruction_opcode; + + switch (original_instruction->opcode()) { + case SpvOpIAdd: + new_instruction_opcode = SpvOpIAddCarry; + break; + case SpvOpISub: + new_instruction_opcode = SpvOpISubBorrow; + break; + case SpvOpIMul: + if (!operand_is_signed) { + new_instruction_opcode = SpvOpUMulExtended; + } else { + new_instruction_opcode = SpvOpSMulExtended; + } + break; + default: + assert(false && "The instruction has an unsupported opcode."); + return; + } + // Get the type of struct type id holding the intermediate result based on the + // operand type. + uint32_t operand_type_id = + ir_context->get_def_use_mgr() + ->GetDef(original_instruction->GetSingleWordInOperand( + kArithmeticInstructionIndexLeftInOperand)) + ->type_id(); + + uint32_t struct_type_id = fuzzerutil::MaybeGetStructType( + ir_context, {operand_type_id, operand_type_id}); + // Avoid unused variables in release mode. + (void)struct_type_id; + assert(struct_type_id && "The struct type must exist in the module."); + + // Insert the new instruction that computes the result into a struct before + // the |original_instruction|. + original_instruction->InsertBefore(MakeUnique( + ir_context, new_instruction_opcode, struct_type_id, + message_.struct_fresh_id(), + opt::Instruction::OperandList( + {{SPV_OPERAND_TYPE_ID, + {original_instruction->GetSingleWordInOperand( + kArithmeticInstructionIndexLeftInOperand)}}, + {SPV_OPERAND_TYPE_ID, + {original_instruction->GetSingleWordInOperand( + kArithmeticInstructionIndexRightInOperand)}}}))); + + // Insert the OpCompositeExtract after the added instruction. This instruction + // takes the first component of the struct which represents low-order bits of + // the operation. This is the original result. + original_instruction->InsertBefore(MakeUnique( + ir_context, SpvOpCompositeExtract, original_instruction->type_id(), + message_.result_id(), + opt::Instruction::OperandList( + {{SPV_OPERAND_TYPE_ID, {message_.struct_fresh_id()}}, + {SPV_OPERAND_TYPE_LITERAL_INTEGER, + {kOpCompositeExtractIndexLowOrderBits}}}))); + + // Remove the original instruction. + ir_context->KillInst(original_instruction); + + // We have modified the module so most analyzes are now invalid. + ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone); +} + +bool TransformationReplaceAddSubMulWithCarryingExtended::IsInstructionSuitable( + opt::IRContext* ir_context, const opt::Instruction& instruction) { + auto instruction_opcode = instruction.opcode(); + + // Only instructions OpIAdd, OpISub, OpIMul are supported. + switch (instruction_opcode) { + case SpvOpIAdd: + case SpvOpISub: + case SpvOpIMul: + break; + default: + return false; + } + uint32_t operand_1_type_id = + ir_context->get_def_use_mgr() + ->GetDef(instruction.GetSingleWordInOperand( + kArithmeticInstructionIndexLeftInOperand)) + ->type_id(); + + uint32_t operand_2_type_id = + ir_context->get_def_use_mgr() + ->GetDef(instruction.GetSingleWordInOperand( + kArithmeticInstructionIndexRightInOperand)) + ->type_id(); + + uint32_t result_type_id = instruction.type_id(); + + // Both type ids of the operands and the result type ids must be equal. + if (operand_1_type_id != operand_2_type_id) { + return false; + } + if (operand_2_type_id != result_type_id) { + return false; + } + + // In case of OpIAdd and OpISub, the type must be unsigned. + auto type = ir_context->get_type_mgr()->GetType(instruction.type_id()); + + switch (instruction_opcode) { + case SpvOpIAdd: + case SpvOpISub: { + // In case of OpIAdd and OpISub if the operand is a vector, the component + // type must be unsigned. Otherwise (if the operand is an int), the + // operand must be unsigned. + bool operand_is_signed = + type->AsVector() + ? type->AsVector()->element_type()->AsInteger()->IsSigned() + : type->AsInteger()->IsSigned(); + if (operand_is_signed) { + return false; + } + } break; + default: + break; + } + return true; +} + +protobufs::Transformation +TransformationReplaceAddSubMulWithCarryingExtended::ToMessage() const { + protobufs::Transformation result; + *result.mutable_replace_add_sub_mul_with_carrying_extended() = message_; + return result; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_replace_add_sub_mul_with_carrying_extended.h b/3rdparty/spirv-tools/source/fuzz/transformation_replace_add_sub_mul_with_carrying_extended.h new file mode 100644 index 000000000..db71779a3 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/transformation_replace_add_sub_mul_with_carrying_extended.h @@ -0,0 +1,69 @@ +// 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 SPIRV_TOOLS_TRANSFORMATION_REPLACE_ADD_SUB_MUL_WITH_CARRYING_EXTENDED_H +#define SPIRV_TOOLS_TRANSFORMATION_REPLACE_ADD_SUB_MUL_WITH_CARRYING_EXTENDED_H + +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" +#include "source/fuzz/transformation.h" +#include "source/fuzz/transformation_context.h" +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace fuzz { + +class TransformationReplaceAddSubMulWithCarryingExtended + : public Transformation { + public: + explicit TransformationReplaceAddSubMulWithCarryingExtended( + const protobufs::TransformationReplaceAddSubMulWithCarryingExtended& + message); + + explicit TransformationReplaceAddSubMulWithCarryingExtended( + uint32_t struct_fresh_id, uint32_t result_id); + + // - |message_.struct_fresh_id| must be fresh. + // - |message_.result_id| must refer to an OpIAdd or OpISub or OpIMul + // instruction. In this instruction the result type id and the type ids of + // the operands must be the same. + // - The type of struct holding the intermediate result must exists in the + // module. + // - For OpIAdd, OpISub both operands must be unsigned. + bool IsApplicable( + opt::IRContext* ir_context, + const TransformationContext& transformation_context) const override; + + // A transformation that replaces instructions OpIAdd, OpISub, OpIMul with + // pairs of instructions. The first one (OpIAddCarry, OpISubBorrow, + // OpUMulExtended, OpSMulExtended) computes the result into a struct. The + // second one extracts the appropriate component from the struct to yield the + // original result. + void Apply(opt::IRContext* ir_context, + TransformationContext* transformation_context) const override; + + protobufs::Transformation ToMessage() const override; + + // Checks if an OpIAdd, OpISub or OpIMul instruction can be used by the + // transformation. + bool static IsInstructionSuitable(opt::IRContext* ir_context, + const opt::Instruction& instruction); + + private: + protobufs::TransformationReplaceAddSubMulWithCarryingExtended message_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SPIRV_TOOLS_TRANSFORMATION_REPLACE_ADD_SUB_MUL_WITH_CARRYING_EXTENDED_H 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 a8f94954a..8de7201b8 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 @@ -90,6 +90,40 @@ TransformationReplaceConstantWithUniform::MakeLoadInstruction( operands_for_load); } +opt::Instruction* +TransformationReplaceConstantWithUniform::GetInsertBeforeInstruction( + opt::IRContext* ir_context) const { + auto* result = + FindInstructionContainingUse(message_.id_use_descriptor(), ir_context); + if (!result) { + return nullptr; + } + + // The use might be in an OpPhi instruction. + if (result->opcode() == SpvOpPhi) { + // OpPhi instructions must be the first instructions in a block. Thus, we + // can't insert above the OpPhi instruction. Given the predecessor block + // that corresponds to the id use, get the last instruction in that block + // above which we can insert OpAccessChain and OpLoad. + return fuzzerutil::GetLastInsertBeforeInstruction( + ir_context, + result->GetSingleWordInOperand( + message_.id_use_descriptor().in_operand_index() + 1), + SpvOpLoad); + } + + // The only operand that we could've replaced in the OpBranchConditional is + // the condition id. But that operand has a boolean type and uniform variables + // can't store booleans (see the spec on OpTypeBool). Thus, |result| can't be + // an OpBranchConditional. + assert(result->opcode() != SpvOpBranchConditional && + "OpBranchConditional has no operands to replace"); + + assert(fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpLoad, result) && + "We should be able to insert OpLoad and OpAccessChain at this point"); + return result; +} + bool TransformationReplaceConstantWithUniform::IsApplicable( opt::IRContext* ir_context, const TransformationContext& transformation_context) const { @@ -188,6 +222,12 @@ bool TransformationReplaceConstantWithUniform::IsApplicable( } } + // Once all checks are completed, we should be able to safely insert + // OpAccessChain and OpLoad into the module. + assert(GetInsertBeforeInstruction(ir_context) && + "There must exist an instruction that we can use to insert " + "OpAccessChain and OpLoad above"); + return true; } @@ -195,7 +235,7 @@ void TransformationReplaceConstantWithUniform::Apply( spvtools::opt::IRContext* ir_context, TransformationContext* /*unused*/) const { // Get the instruction that contains the id use we wish to replace. - auto instruction_containing_constant_use = + auto* instruction_containing_constant_use = FindInstructionContainingUse(message_.id_use_descriptor(), ir_context); assert(instruction_containing_constant_use && "Precondition requires that the id use can be found."); @@ -210,12 +250,17 @@ void TransformationReplaceConstantWithUniform::Apply( ->GetDef(message_.id_use_descriptor().id_of_interest()) ->type_id(); + // Get an instruction that will be used to insert OpAccessChain and OpLoad. + auto* insert_before_inst = GetInsertBeforeInstruction(ir_context); + assert(insert_before_inst && + "There must exist an insertion point for OpAccessChain and OpLoad"); + // Add an access chain instruction to target the uniform element. - instruction_containing_constant_use->InsertBefore( + insert_before_inst->InsertBefore( MakeAccessChainInstruction(ir_context, constant_type_id)); // Add a load from this access chain. - instruction_containing_constant_use->InsertBefore( + insert_before_inst->InsertBefore( MakeLoadInstruction(ir_context, constant_type_id)); // Adjust the instruction containing the usage of the constant so that this diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_replace_constant_with_uniform.h b/3rdparty/spirv-tools/source/fuzz/transformation_replace_constant_with_uniform.h index b72407c85..c507c322b 100644 --- a/3rdparty/spirv-tools/source/fuzz/transformation_replace_constant_with_uniform.h +++ b/3rdparty/spirv-tools/source/fuzz/transformation_replace_constant_with_uniform.h @@ -84,6 +84,11 @@ class TransformationReplaceConstantWithUniform : public Transformation { std::unique_ptr MakeLoadInstruction( spvtools::opt::IRContext* ir_context, uint32_t constant_type_id) const; + // OpAccessChain and OpLoad will be inserted above the instruction returned + // by this function. Returns nullptr if no such instruction is present. + opt::Instruction* GetInsertBeforeInstruction( + opt::IRContext* ir_context) const; + protobufs::TransformationReplaceConstantWithUniform message_; }; diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_replace_copy_object_with_store_load.cpp b/3rdparty/spirv-tools/source/fuzz/transformation_replace_copy_object_with_store_load.cpp index 05e8cdaec..6bf7d46d4 100644 --- a/3rdparty/spirv-tools/source/fuzz/transformation_replace_copy_object_with_store_load.cpp +++ b/3rdparty/spirv-tools/source/fuzz/transformation_replace_copy_object_with_store_load.cpp @@ -59,14 +59,6 @@ bool TransformationReplaceCopyObjectWithStoreLoad::IsApplicable( return false; } - // It must be valid to insert the OpStore and OpLoad instruction before it. - if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpStore, - copy_object_instruction) || - !fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpLoad, - copy_object_instruction)) { - return false; - } - // A pointer type instruction pointing to the value type must be defined. auto pointer_type_id = fuzzerutil::MaybeGetPointerType( ir_context, copy_object_instruction->type_id(), 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 fbbeab2e6..8ebfbe03f 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 @@ -109,7 +109,7 @@ protobufs::Transformation TransformationReplaceIdWithSynonym::ToMessage() bool TransformationReplaceIdWithSynonym::UseCanBeReplacedWithSynonym( opt::IRContext* ir_context, opt::Instruction* use_instruction, uint32_t use_in_operand_index) { - if (use_instruction->opcode() == SpvOpAccessChain && + if (spvOpcodeIsAccessChain(use_instruction->opcode()) && use_in_operand_index > 0) { // This is an access chain index. If the (sub-)object being accessed by the // given index has struct type then we cannot replace the use with a diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_replace_load_store_with_copy_memory.cpp b/3rdparty/spirv-tools/source/fuzz/transformation_replace_load_store_with_copy_memory.cpp index 52964bfa6..a42ffb11b 100644 --- a/3rdparty/spirv-tools/source/fuzz/transformation_replace_load_store_with_copy_memory.cpp +++ b/3rdparty/spirv-tools/source/fuzz/transformation_replace_load_store_with_copy_memory.cpp @@ -45,26 +45,18 @@ bool TransformationReplaceLoadStoreWithCopyMemory::IsApplicable( opt::IRContext* ir_context, const TransformationContext& /*unused*/) const { // This transformation is only applicable to the pair of OpLoad and OpStore // instructions. - if (message_.load_instruction_descriptor().target_instruction_opcode() != - SpvOpLoad) { - return false; - } - if (message_.store_instruction_descriptor().target_instruction_opcode() != - SpvOpStore) { - return false; - } // The OpLoad instruction must be defined. auto load_instruction = FindInstruction(message_.load_instruction_descriptor(), ir_context); - if (!load_instruction) { + if (!load_instruction || load_instruction->opcode() != SpvOpLoad) { return false; } // The OpStore instruction must be defined. auto store_instruction = FindInstruction(message_.store_instruction_descriptor(), ir_context); - if (!store_instruction) { + if (!store_instruction || store_instruction->opcode() != SpvOpStore) { return false; } diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_replace_params_with_struct.cpp b/3rdparty/spirv-tools/source/fuzz/transformation_replace_params_with_struct.cpp index 7e7641541..8c683a3ce 100644 --- a/3rdparty/spirv-tools/source/fuzz/transformation_replace_params_with_struct.cpp +++ b/3rdparty/spirv-tools/source/fuzz/transformation_replace_params_with_struct.cpp @@ -101,8 +101,9 @@ bool TransformationReplaceParamsWithStruct::IsApplicable( return false; } - auto caller_id_to_fresh_composite_id = fuzzerutil::RepeatedUInt32PairToMap( - message_.caller_id_to_fresh_composite_id()); + const auto caller_id_to_fresh_composite_id = + fuzzerutil::RepeatedUInt32PairToMap( + message_.caller_id_to_fresh_composite_id()); // Check that |callee_id_to_fresh_composite_id| is valid. for (const auto* inst : @@ -151,24 +152,12 @@ void TransformationReplaceParamsWithStruct::Apply( // Compute indices of replaced parameters. This will be used to adjust // OpFunctionCall instructions and create OpCompositeConstruct instructions at // every call site. - std::vector indices_of_replaced_params; - { - // We want to destroy |params| after the loop because it will contain - // dangling pointers when we remove parameters from the function. - auto params = fuzzerutil::GetParameters(ir_context, function->result_id()); - for (auto id : message_.parameter_id()) { - auto it = std::find_if(params.begin(), params.end(), - [id](const opt::Instruction* param) { - return param->result_id() == id; - }); - assert(it != params.end() && "Parameter's id is invalid"); - indices_of_replaced_params.push_back( - static_cast(it - params.begin())); - } - } + const auto indices_of_replaced_params = + ComputeIndicesOfReplacedParameters(ir_context); - auto caller_id_to_fresh_composite_id = fuzzerutil::RepeatedUInt32PairToMap( - message_.caller_id_to_fresh_composite_id()); + const auto caller_id_to_fresh_composite_id = + fuzzerutil::RepeatedUInt32PairToMap( + message_.caller_id_to_fresh_composite_id()); // Update all function calls. for (auto* inst : fuzzerutil::GetCallers(ir_context, function->result_id())) { @@ -182,12 +171,13 @@ void TransformationReplaceParamsWithStruct::Apply( } // Remove arguments from the function call. We do it in a separate loop - // and in reverse order to make sure we have removed correct operands. - for (auto it = indices_of_replaced_params.rbegin(); - it != indices_of_replaced_params.rend(); ++it) { + // and in decreasing order to make sure we have removed correct operands. + for (auto index : std::set>( + indices_of_replaced_params.begin(), + indices_of_replaced_params.end())) { // +1 since the first in operand to OpFunctionCall is the result id of // the function. - inst->RemoveInOperand(*it + 1); + inst->RemoveInOperand(index + 1); } // Insert OpCompositeConstruct before the function call. @@ -305,5 +295,30 @@ uint32_t TransformationReplaceParamsWithStruct::MaybeGetRequiredStructType( return fuzzerutil::MaybeGetStructType(ir_context, component_type_ids); } +std::vector +TransformationReplaceParamsWithStruct::ComputeIndicesOfReplacedParameters( + opt::IRContext* ir_context) const { + assert(!message_.parameter_id().empty() && + "There must be at least one parameter to replace"); + + const auto* function = fuzzerutil::GetFunctionFromParameterId( + ir_context, message_.parameter_id(0)); + assert(function && "|parameter_id|s are invalid"); + + std::vector result; + + auto params = fuzzerutil::GetParameters(ir_context, function->result_id()); + for (auto id : message_.parameter_id()) { + auto it = std::find_if(params.begin(), params.end(), + [id](const opt::Instruction* param) { + return param->result_id() == id; + }); + assert(it != params.end() && "Parameter's id is invalid"); + result.push_back(static_cast(it - params.begin())); + } + + return result; +} + } // namespace fuzz } // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_replace_params_with_struct.h b/3rdparty/spirv-tools/source/fuzz/transformation_replace_params_with_struct.h index d2ce20452..7e40de897 100644 --- a/3rdparty/spirv-tools/source/fuzz/transformation_replace_params_with_struct.h +++ b/3rdparty/spirv-tools/source/fuzz/transformation_replace_params_with_struct.h @@ -73,6 +73,12 @@ class TransformationReplaceParamsWithStruct : public Transformation { // transformation (see docs on the IsApplicable method to learn more). uint32_t MaybeGetRequiredStructType(opt::IRContext* ir_context) const; + // Returns a vector of indices of parameters to replace. Concretely, i'th + // element is the index of the parameter with result id |parameter_id[i]| in + // its function. + std::vector ComputeIndicesOfReplacedParameters( + opt::IRContext* ir_context) const; + protobufs::TransformationReplaceParamsWithStruct message_; }; diff --git a/3rdparty/spirv-tools/source/opcode.cpp b/3rdparty/spirv-tools/source/opcode.cpp index f93cfd371..c41e41d6c 100644 --- a/3rdparty/spirv-tools/source/opcode.cpp +++ b/3rdparty/spirv-tools/source/opcode.cpp @@ -719,3 +719,15 @@ std::vector spvOpcodeMemorySemanticsOperandIndices(SpvOp opcode) { return {}; } } + +bool spvOpcodeIsAccessChain(SpvOp opcode) { + switch (opcode) { + case SpvOpAccessChain: + case SpvOpInBoundsAccessChain: + case SpvOpPtrAccessChain: + case SpvOpInBoundsPtrAccessChain: + return true; + default: + return false; + } +} diff --git a/3rdparty/spirv-tools/source/opcode.h b/3rdparty/spirv-tools/source/opcode.h index 9aeba76e0..d79909d26 100644 --- a/3rdparty/spirv-tools/source/opcode.h +++ b/3rdparty/spirv-tools/source/opcode.h @@ -144,4 +144,7 @@ bool spvOpcodeIsImageSample(SpvOp opcode); // operands for |opcode|. std::vector spvOpcodeMemorySemanticsOperandIndices(SpvOp opcode); +// Returns true for opcodes that represents access chain instructions. +bool spvOpcodeIsAccessChain(SpvOp opcode); + #endif // SOURCE_OPCODE_H_ diff --git a/3rdparty/spirv-tools/source/opt/CMakeLists.txt b/3rdparty/spirv-tools/source/opt/CMakeLists.txt index 090aeac56..3630a0605 100644 --- a/3rdparty/spirv-tools/source/opt/CMakeLists.txt +++ b/3rdparty/spirv-tools/source/opt/CMakeLists.txt @@ -226,7 +226,7 @@ set(SPIRV_TOOLS_OPT_SOURCES wrap_opkill.cpp ) -if(MSVC) +if(MSVC AND (NOT ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang"))) # Enable parallel builds across four cores for this lib add_definitions(/MP4) endif() diff --git a/3rdparty/spirv-tools/source/opt/aggressive_dead_code_elim_pass.cpp b/3rdparty/spirv-tools/source/opt/aggressive_dead_code_elim_pass.cpp index 71bbed1d5..c8688a311 100644 --- a/3rdparty/spirv-tools/source/opt/aggressive_dead_code_elim_pass.cpp +++ b/3rdparty/spirv-tools/source/opt/aggressive_dead_code_elim_pass.cpp @@ -22,6 +22,7 @@ #include "source/cfa.h" #include "source/latest_version_glsl_std_450_header.h" +#include "source/opt/eliminate_dead_functions_util.h" #include "source/opt/iterator.h" #include "source/opt/reflect.h" #include "source/spirv_constant.h" @@ -727,8 +728,8 @@ bool AggressiveDCEPass::EliminateDeadFunctions() { funcIter != get_module()->end();) { if (live_function_set.count(&*funcIter) == 0) { modified = true; - EliminateFunction(&*funcIter); - funcIter = funcIter.Erase(); + funcIter = + eliminatedeadfunctionsutil::EliminateFunction(context(), &funcIter); } else { ++funcIter; } @@ -737,12 +738,6 @@ bool AggressiveDCEPass::EliminateDeadFunctions() { return modified; } -void AggressiveDCEPass::EliminateFunction(Function* func) { - // Remove all of the instruction in the function body - func->ForEachInst([this](Instruction* inst) { context()->KillInst(inst); }, - true); -} - bool AggressiveDCEPass::ProcessGlobalValues() { // Remove debug and annotation statements referencing dead instructions. // This must be done before killing the instructions, otherwise there are diff --git a/3rdparty/spirv-tools/source/opt/aggressive_dead_code_elim_pass.h b/3rdparty/spirv-tools/source/opt/aggressive_dead_code_elim_pass.h index 2ce5b5770..f02e729fd 100644 --- a/3rdparty/spirv-tools/source/opt/aggressive_dead_code_elim_pass.h +++ b/3rdparty/spirv-tools/source/opt/aggressive_dead_code_elim_pass.h @@ -127,9 +127,6 @@ class AggressiveDCEPass : public MemPass { // Erases functions that are unreachable from the entry points of the module. bool EliminateDeadFunctions(); - // Removes |func| from the module and deletes all its instructions. - void EliminateFunction(Function* func); - // For function |func|, mark all Stores to non-function-scope variables // and block terminating instructions as live. Recursively mark the values // they use. When complete, mark any non-live instructions to be deleted. diff --git a/3rdparty/spirv-tools/source/opt/dead_insert_elim_pass.cpp b/3rdparty/spirv-tools/source/opt/dead_insert_elim_pass.cpp index 7d5634383..46f4f1248 100644 --- a/3rdparty/spirv-tools/source/opt/dead_insert_elim_pass.cpp +++ b/3rdparty/spirv-tools/source/opt/dead_insert_elim_pass.cpp @@ -196,6 +196,7 @@ bool DeadInsertElimPass::EliminateDeadInsertsOnePass(Function* func) { } const uint32_t id = ii->result_id(); get_def_use_mgr()->ForEachUser(id, [&ii, this](Instruction* user) { + if (user->IsOpenCL100DebugInstr()) return; switch (user->opcode()) { case SpvOpCompositeInsert: case SpvOpPhi: diff --git a/3rdparty/spirv-tools/source/opt/eliminate_dead_functions_util.cpp b/3rdparty/spirv-tools/source/opt/eliminate_dead_functions_util.cpp index 8a3895931..6b5234bb5 100644 --- a/3rdparty/spirv-tools/source/opt/eliminate_dead_functions_util.cpp +++ b/3rdparty/spirv-tools/source/opt/eliminate_dead_functions_util.cpp @@ -21,9 +21,35 @@ namespace eliminatedeadfunctionsutil { Module::iterator EliminateFunction(IRContext* context, Module::iterator* func_iter) { + bool first_func = *func_iter == context->module()->begin(); + bool seen_func_end = false; (*func_iter) - ->ForEachInst([context](Instruction* inst) { context->KillInst(inst); }, - true); + ->ForEachInst( + [context, first_func, func_iter, &seen_func_end](Instruction* inst) { + if (inst->opcode() == SpvOpFunctionEnd) { + seen_func_end = true; + } + // Move non-semantic instructions to the previous function or + // global values if this is the first function. + if (seen_func_end && inst->opcode() == SpvOpExtInst) { + assert(inst->IsNonSemanticInstruction()); + std::unique_ptr clone(inst->Clone(context)); + context->ForgetUses(inst); + context->AnalyzeDefUse(clone.get()); + if (first_func) { + context->AddGlobalValue(std::move(clone)); + } else { + auto prev_func_iter = *func_iter; + --prev_func_iter; + prev_func_iter->AddNonSemanticInstruction(std::move(clone)); + } + inst->ToNop(); + } else { + context->KillNonSemanticInfo(inst); + context->KillInst(inst); + } + }, + true, true); return func_iter->Erase(); } diff --git a/3rdparty/spirv-tools/source/opt/folding_rules.cpp b/3rdparty/spirv-tools/source/opt/folding_rules.cpp index 1c8cdc893..010eec9c1 100644 --- a/3rdparty/spirv-tools/source/opt/folding_rules.cpp +++ b/3rdparty/spirv-tools/source/opt/folding_rules.cpp @@ -1467,7 +1467,7 @@ FoldingRule CompositeConstructFeedingExtract() { type_mgr->GetType(element_def->type_id())->AsVector(); if (element_type) { uint32_t vector_size = element_type->element_count(); - if (vector_size < element_index) { + if (vector_size <= element_index) { // The element we want comes after this vector. element_index -= vector_size; } else { diff --git a/3rdparty/spirv-tools/source/opt/function.cpp b/3rdparty/spirv-tools/source/opt/function.cpp index 320f8cabf..21ce0c6ae 100644 --- a/3rdparty/spirv-tools/source/opt/function.cpp +++ b/3rdparty/spirv-tools/source/opt/function.cpp @@ -47,31 +47,40 @@ Function* Function::Clone(IRContext* ctx) const { } clone->SetFunctionEnd(std::unique_ptr(EndInst()->Clone(ctx))); + + clone->non_semantic_.reserve(non_semantic_.size()); + for (auto& non_semantic : non_semantic_) { + clone->AddNonSemanticInstruction( + std::unique_ptr(non_semantic->Clone(ctx))); + } return clone; } void Function::ForEachInst(const std::function& f, - bool run_on_debug_line_insts) { + bool run_on_debug_line_insts, + bool run_on_non_semantic_insts) { WhileEachInst( [&f](Instruction* inst) { f(inst); return true; }, - run_on_debug_line_insts); + run_on_debug_line_insts, run_on_non_semantic_insts); } void Function::ForEachInst(const std::function& f, - bool run_on_debug_line_insts) const { + bool run_on_debug_line_insts, + bool run_on_non_semantic_insts) const { WhileEachInst( [&f](const Instruction* inst) { f(inst); return true; }, - run_on_debug_line_insts); + run_on_debug_line_insts, run_on_non_semantic_insts); } bool Function::WhileEachInst(const std::function& f, - bool run_on_debug_line_insts) { + bool run_on_debug_line_insts, + bool run_on_non_semantic_insts) { if (def_inst_) { if (!def_inst_->WhileEachInst(f, run_on_debug_line_insts)) { return false; @@ -99,13 +108,26 @@ bool Function::WhileEachInst(const std::function& f, } } - if (end_inst_) return end_inst_->WhileEachInst(f, run_on_debug_line_insts); + if (end_inst_) { + if (!end_inst_->WhileEachInst(f, run_on_debug_line_insts)) { + return false; + } + } + + if (run_on_non_semantic_insts) { + for (auto& non_semantic : non_semantic_) { + if (!non_semantic->WhileEachInst(f, run_on_debug_line_insts)) { + return false; + } + } + } return true; } bool Function::WhileEachInst(const std::function& f, - bool run_on_debug_line_insts) const { + bool run_on_debug_line_insts, + bool run_on_non_semantic_insts) const { if (def_inst_) { if (!static_cast(def_inst_.get()) ->WhileEachInst(f, run_on_debug_line_insts)) { @@ -133,9 +155,21 @@ bool Function::WhileEachInst(const std::function& f, } } - if (end_inst_) - return static_cast(end_inst_.get()) - ->WhileEachInst(f, run_on_debug_line_insts); + if (end_inst_) { + if (!static_cast(end_inst_.get()) + ->WhileEachInst(f, run_on_debug_line_insts)) { + return false; + } + } + + if (run_on_non_semantic_insts) { + for (auto& non_semantic : non_semantic_) { + if (!static_cast(non_semantic.get()) + ->WhileEachInst(f, run_on_debug_line_insts)) { + return false; + } + } + } return true; } diff --git a/3rdparty/spirv-tools/source/opt/function.h b/3rdparty/spirv-tools/source/opt/function.h index f5035f08b..1d11a097f 100644 --- a/3rdparty/spirv-tools/source/opt/function.h +++ b/3rdparty/spirv-tools/source/opt/function.h @@ -79,6 +79,11 @@ class Function { // Saves the given function end instruction. inline void SetFunctionEnd(std::unique_ptr end_inst); + // Add a non-semantic instruction that succeeds this function in the module. + // These instructions are maintained in the order they are added. + inline void AddNonSemanticInstruction( + std::unique_ptr non_semantic); + // Returns the given function end instruction. inline Instruction* EndInst() { return end_inst_.get(); } inline const Instruction* EndInst() const { return end_inst_.get(); } @@ -115,19 +120,24 @@ class Function { } // Runs the given function |f| on instructions in this function, in order, - // and optionally on debug line instructions that might precede them. + // and optionally on debug line instructions that might precede them and + // non-semantic instructions that succceed the function. void ForEachInst(const std::function& f, - bool run_on_debug_line_insts = false); + bool run_on_debug_line_insts = false, + bool run_on_non_semantic_insts = false); void ForEachInst(const std::function& f, - bool run_on_debug_line_insts = false) const; + bool run_on_debug_line_insts = false, + bool run_on_non_semantic_insts = false) const; // Runs the given function |f| on instructions in this function, in order, - // and optionally on debug line instructions that might precede them. - // If |f| returns false, iteration is terminated and this function returns - // false. + // and optionally on debug line instructions that might precede them and + // non-semantic instructions that succeed the function. If |f| returns + // false, iteration is terminated and this function returns false. bool WhileEachInst(const std::function& f, - bool run_on_debug_line_insts = false); + bool run_on_debug_line_insts = false, + bool run_on_non_semantic_insts = false); bool WhileEachInst(const std::function& f, - bool run_on_debug_line_insts = false) const; + bool run_on_debug_line_insts = false, + bool run_on_non_semantic_insts = false) const; // Runs the given function |f| on each parameter instruction in this function, // in order, and optionally on debug line instructions that might precede @@ -172,6 +182,8 @@ class Function { std::vector> blocks_; // The OpFunctionEnd instruction. std::unique_ptr end_inst_; + // Non-semantic instructions succeeded by this function. + std::vector> non_semantic_; }; // Pretty-prints |func| to |str|. Returns |str|. @@ -235,6 +247,11 @@ inline void Function::SetFunctionEnd(std::unique_ptr end_inst) { end_inst_ = std::move(end_inst); } +inline void Function::AddNonSemanticInstruction( + std::unique_ptr non_semantic) { + non_semantic_.emplace_back(std::move(non_semantic)); +} + } // namespace opt } // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/instruction.cpp b/3rdparty/spirv-tools/source/opt/instruction.cpp index 126848e71..8cc3d79a8 100644 --- a/3rdparty/spirv-tools/source/opt/instruction.cpp +++ b/3rdparty/spirv-tools/source/opt/instruction.cpp @@ -183,8 +183,9 @@ void Instruction::ToBinaryWithoutAttachedDebugInsts( std::vector* binary) const { const uint32_t num_words = 1 + NumOperandWords(); binary->push_back((num_words << 16) | static_cast(opcode_)); - for (const auto& operand : operands_) + for (const auto& operand : operands_) { binary->insert(binary->end(), operand.words.begin(), operand.words.end()); + } } void Instruction::ReplaceOperands(const OperandList& new_operands) { @@ -283,8 +284,7 @@ bool Instruction::IsVulkanStorageImage() const { // Check if the image is sampled. If we do not know for sure that it is, // then assume it is a storage image. - auto s = base_type->GetSingleWordInOperand(kTypeImageSampledIndex); - return s != 1; + return base_type->GetSingleWordInOperand(kTypeImageSampledIndex) != 1; } bool Instruction::IsVulkanSampledImage() const { @@ -318,8 +318,7 @@ bool Instruction::IsVulkanSampledImage() const { // Check if the image is sampled. If we know for sure that it is, // then return true. - auto s = base_type->GetSingleWordInOperand(kTypeImageSampledIndex); - return s == 1; + return base_type->GetSingleWordInOperand(kTypeImageSampledIndex) == 1; } bool Instruction::IsVulkanStorageTexelBuffer() const { @@ -502,16 +501,16 @@ uint32_t Instruction::GetTypeComponent(uint32_t element) const { return subtype; } -Instruction* Instruction::InsertBefore(std::unique_ptr&& i) { - i.get()->InsertBefore(this); - return i.release(); +Instruction* Instruction::InsertBefore(std::unique_ptr&& inst) { + inst.get()->InsertBefore(this); + return inst.release(); } Instruction* Instruction::InsertBefore( std::vector>&& list) { Instruction* first_node = list.front().get(); - for (auto& i : list) { - i.release()->InsertBefore(this); + for (auto& inst : list) { + inst.release()->InsertBefore(this); } list.clear(); return first_node; @@ -568,10 +567,13 @@ bool Instruction::IsValidBasePointer() const { } OpenCLDebugInfo100Instructions Instruction::GetOpenCL100DebugOpcode() const { - if (opcode() != SpvOpExtInst) return OpenCLDebugInfo100InstructionsMax; - - if (!context()->get_feature_mgr()->GetExtInstImportId_OpenCL100DebugInfo()) + if (opcode() != SpvOpExtInst) { return OpenCLDebugInfo100InstructionsMax; + } + + if (!context()->get_feature_mgr()->GetExtInstImportId_OpenCL100DebugInfo()) { + return OpenCLDebugInfo100InstructionsMax; + } if (GetSingleWordInOperand(kExtInstSetIdInIdx) != context()->get_feature_mgr()->GetExtInstImportId_OpenCL100DebugInfo()) { @@ -622,6 +624,7 @@ bool Instruction::IsFoldableByFoldScalar() const { if (!folder.IsFoldableOpcode(opcode())) { return false; } + Instruction* type = context()->get_def_use_mgr()->GetDef(type_id()); if (!folder.IsFoldableType(type)) { return false; @@ -889,6 +892,16 @@ bool Instruction::IsOpcodeSafeToDelete() const { } } +bool Instruction::IsNonSemanticInstruction() const { + if (!HasResultId()) return false; + if (opcode() != SpvOpExtInst) return false; + + auto import_inst = + context()->get_def_use_mgr()->GetDef(GetSingleWordInOperand(0)); + std::string import_name = import_inst->GetInOperand(0).AsString(); + return import_name.find("NonSemantic.") == 0; +} + void DebugScope::ToBinary(uint32_t type_id, uint32_t result_id, uint32_t ext_set, std::vector* binary) const { diff --git a/3rdparty/spirv-tools/source/opt/instruction.h b/3rdparty/spirv-tools/source/opt/instruction.h index 509c42fa3..067f69cfc 100644 --- a/3rdparty/spirv-tools/source/opt/instruction.h +++ b/3rdparty/spirv-tools/source/opt/instruction.h @@ -549,6 +549,9 @@ class Instruction : public utils::IntrusiveNodeBase { return GetOpenCL100DebugOpcode() != OpenCLDebugInfo100InstructionsMax; } + // Returns true if this instructions a non-semantic instruction. + bool IsNonSemanticInstruction() const; + // Dump this instruction on stderr. Useful when running interactive // debuggers. void Dump() const; @@ -749,21 +752,21 @@ inline void Instruction::ForEachInst( } inline void Instruction::ForEachId(const std::function& f) { - for (auto& opnd : operands_) - if (spvIsIdType(opnd.type)) f(&opnd.words[0]); + for (auto& operand : operands_) + if (spvIsIdType(operand.type)) f(&operand.words[0]); } inline void Instruction::ForEachId( const std::function& f) const { - for (const auto& opnd : operands_) - if (spvIsIdType(opnd.type)) f(&opnd.words[0]); + for (const auto& operand : operands_) + if (spvIsIdType(operand.type)) f(&operand.words[0]); } inline bool Instruction::WhileEachInId( const std::function& f) { - for (auto& opnd : operands_) { - if (spvIsInIdType(opnd.type)) { - if (!f(&opnd.words[0])) return false; + for (auto& operand : operands_) { + if (spvIsInIdType(operand.type) && !f(&operand.words[0])) { + return false; } } return true; @@ -771,9 +774,9 @@ inline bool Instruction::WhileEachInId( inline bool Instruction::WhileEachInId( const std::function& f) const { - for (const auto& opnd : operands_) { - if (spvIsInIdType(opnd.type)) { - if (!f(&opnd.words[0])) return false; + for (const auto& operand : operands_) { + if (spvIsInIdType(operand.type) && !f(&operand.words[0])) { + return false; } } return true; @@ -796,13 +799,13 @@ inline void Instruction::ForEachInId( inline bool Instruction::WhileEachInOperand( const std::function& f) { - for (auto& opnd : operands_) { - switch (opnd.type) { + for (auto& operand : operands_) { + switch (operand.type) { case SPV_OPERAND_TYPE_RESULT_ID: case SPV_OPERAND_TYPE_TYPE_ID: break; default: - if (!f(&opnd.words[0])) return false; + if (!f(&operand.words[0])) return false; break; } } @@ -811,13 +814,13 @@ inline bool Instruction::WhileEachInOperand( inline bool Instruction::WhileEachInOperand( const std::function& f) const { - for (const auto& opnd : operands_) { - switch (opnd.type) { + for (const auto& operand : operands_) { + switch (operand.type) { case SPV_OPERAND_TYPE_RESULT_ID: case SPV_OPERAND_TYPE_TYPE_ID: break; default: - if (!f(&opnd.words[0])) return false; + if (!f(&operand.words[0])) return false; break; } } @@ -826,16 +829,16 @@ inline bool Instruction::WhileEachInOperand( inline void Instruction::ForEachInOperand( const std::function& f) { - WhileEachInOperand([&f](uint32_t* op) { - f(op); + WhileEachInOperand([&f](uint32_t* operand) { + f(operand); return true; }); } inline void Instruction::ForEachInOperand( const std::function& f) const { - WhileEachInOperand([&f](const uint32_t* op) { - f(op); + WhileEachInOperand([&f](const uint32_t* operand) { + f(operand); return true; }); } diff --git a/3rdparty/spirv-tools/source/opt/ir_context.cpp b/3rdparty/spirv-tools/source/opt/ir_context.cpp index a56ff06c5..f147b0b73 100644 --- a/3rdparty/spirv-tools/source/opt/ir_context.cpp +++ b/3rdparty/spirv-tools/source/opt/ir_context.cpp @@ -213,6 +213,30 @@ Instruction* IRContext::KillInst(Instruction* inst) { return next_instruction; } +void IRContext::KillNonSemanticInfo(Instruction* inst) { + if (!inst->HasResultId()) return; + std::vector work_list; + std::vector to_kill; + std::unordered_set seen; + work_list.push_back(inst); + + while (!work_list.empty()) { + auto* i = work_list.back(); + work_list.pop_back(); + get_def_use_mgr()->ForEachUser( + i, [&work_list, &to_kill, &seen](Instruction* user) { + if (user->IsNonSemanticInstruction() && seen.insert(user).second) { + work_list.push_back(user); + to_kill.push_back(user); + } + }); + } + + for (auto* dead : to_kill) { + KillInst(dead); + } +} + bool IRContext::KillDef(uint32_t id) { Instruction* def = get_def_use_mgr()->GetDef(id); if (def != nullptr) { diff --git a/3rdparty/spirv-tools/source/opt/ir_context.h b/3rdparty/spirv-tools/source/opt/ir_context.h index b19365741..8c1b5d420 100644 --- a/3rdparty/spirv-tools/source/opt/ir_context.h +++ b/3rdparty/spirv-tools/source/opt/ir_context.h @@ -403,6 +403,9 @@ class IRContext { // instruction exists. Instruction* KillInst(Instruction* inst); + // Removes the non-semantic instruction tree that uses |inst|'s result id. + void KillNonSemanticInfo(Instruction* inst); + // Returns true if all of the given analyses are valid. bool AreAnalysesValid(Analysis set) { return (set & valid_analyses_) == set; } diff --git a/3rdparty/spirv-tools/source/opt/ir_loader.cpp b/3rdparty/spirv-tools/source/opt/ir_loader.cpp index acd41cd64..a10812e48 100644 --- a/3rdparty/spirv-tools/source/opt/ir_loader.cpp +++ b/3rdparty/spirv-tools/source/opt/ir_loader.cpp @@ -167,13 +167,22 @@ bool IrLoader::AddInstruction(const spv_parsed_instruction_t* inst) { } else if (IsTypeInst(opcode)) { module_->AddType(std::move(spv_inst)); } else if (IsConstantInst(opcode) || opcode == SpvOpVariable || - opcode == SpvOpUndef || - (opcode == SpvOpExtInst && - spvExtInstIsNonSemantic(inst->ext_inst_type))) { + opcode == SpvOpUndef) { module_->AddGlobalValue(std::move(spv_inst)); } else if (opcode == SpvOpExtInst && spvExtInstIsDebugInfo(inst->ext_inst_type)) { module_->AddExtInstDebugInfo(std::move(spv_inst)); + } else if (opcode == SpvOpExtInst && + spvExtInstIsNonSemantic(inst->ext_inst_type)) { + // If there are no functions, add the non-semantic instructions to the + // global values. Otherwise append it to the list of the last function. + auto func_begin = module_->begin(); + auto func_end = module_->end(); + if (func_begin == func_end) { + module_->AddGlobalValue(std::move(spv_inst)); + } else { + (--func_end)->AddNonSemanticInstruction(std::move(spv_inst)); + } } else { Errorf(consumer_, src, loc, "Unhandled inst type (opcode: %d) found outside function " diff --git a/3rdparty/spirv-tools/source/opt/local_access_chain_convert_pass.cpp b/3rdparty/spirv-tools/source/opt/local_access_chain_convert_pass.cpp index 9b8c112e1..9ff4ec6f0 100644 --- a/3rdparty/spirv-tools/source/opt/local_access_chain_convert_pass.cpp +++ b/3rdparty/spirv-tools/source/opt/local_access_chain_convert_pass.cpp @@ -77,6 +77,15 @@ void LocalAccessChainConvertPass::AppendConstantOperands( bool LocalAccessChainConvertPass::ReplaceAccessChainLoad( const Instruction* address_inst, Instruction* original_load) { // Build and append load of variable in ptrInst + if (address_inst->NumInOperands() == 1) { + // An access chain with no indices is essentially a copy. All that is + // needed is to propagate the address. + context()->ReplaceAllUsesWith( + address_inst->result_id(), + address_inst->GetSingleWordInOperand(kAccessChainPtrIdInIdx)); + return true; + } + std::vector> new_inst; uint32_t varId; uint32_t varPteTypeId; @@ -109,6 +118,18 @@ bool LocalAccessChainConvertPass::ReplaceAccessChainLoad( bool LocalAccessChainConvertPass::GenAccessChainStoreReplacement( const Instruction* ptrInst, uint32_t valId, std::vector>* newInsts) { + if (ptrInst->NumInOperands() == 1) { + // An access chain with no indices is essentially a copy. However, we still + // have to create a new store because the old ones will be deleted. + BuildAndAppendInst( + SpvOpStore, 0, 0, + {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, + {ptrInst->GetSingleWordInOperand(kAccessChainPtrIdInIdx)}}, + {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {valId}}}, + newInsts); + return true; + } + // Build and append load of variable in ptrInst uint32_t varId; uint32_t varPteTypeId; @@ -246,11 +267,13 @@ Pass::Status LocalAccessChainConvertPass::ConvertLocalAccessChains( if (!GenAccessChainStoreReplacement(ptrInst, valId, &newInsts)) { return Status::Failure; } + size_t num_of_instructions_to_skip = newInsts.size() - 1; dead_instructions.push_back(&*ii); ++ii; ii = ii.InsertBefore(std::move(newInsts)); - ++ii; - ++ii; + for (size_t i = 0; i < num_of_instructions_to_skip; ++i) { + ++ii; + } modified = true; } break; default: diff --git a/3rdparty/spirv-tools/source/opt/loop_descriptor.cpp b/3rdparty/spirv-tools/source/opt/loop_descriptor.cpp index 72f405c2a..b5b563098 100644 --- a/3rdparty/spirv-tools/source/opt/loop_descriptor.cpp +++ b/3rdparty/spirv-tools/source/opt/loop_descriptor.cpp @@ -191,14 +191,13 @@ bool Loop::GetInductionInitValue(const Instruction* induction, if (!constant) return false; if (value) { - const analysis::Integer* type = - constant->AsIntConstant()->type()->AsInteger(); - - if (type->IsSigned()) { - *value = constant->AsIntConstant()->GetS32BitValue(); - } else { - *value = constant->AsIntConstant()->GetU32BitValue(); + const analysis::Integer* type = constant->type()->AsInteger(); + if (!type) { + return false; } + + *value = type->IsSigned() ? constant->GetSignExtendedValue() + : constant->GetZeroExtendedValue(); } return true; @@ -682,22 +681,19 @@ bool Loop::FindNumberOfIterations(const Instruction* induction, if (!upper_bound) return false; // Must be integer because of the opcode on the condition. - int64_t condition_value = 0; + const analysis::Integer* type = upper_bound->type()->AsInteger(); - const analysis::Integer* type = - upper_bound->AsIntConstant()->type()->AsInteger(); - - if (type->width() > 32) { + if (!type || type->width() > 64) { return false; } - if (type->IsSigned()) { - condition_value = upper_bound->AsIntConstant()->GetS32BitValue(); - } else { - condition_value = upper_bound->AsIntConstant()->GetU32BitValue(); - } + int64_t condition_value = type->IsSigned() + ? upper_bound->GetSignExtendedValue() + : upper_bound->GetZeroExtendedValue(); // Find the instruction which is stepping through the loop. + // + // GetInductionStepOperation returns nullptr if |step_inst| is OpConstantNull. Instruction* step_inst = GetInductionStepOperation(induction); if (!step_inst) return false; diff --git a/3rdparty/spirv-tools/source/opt/module.cpp b/3rdparty/spirv-tools/source/opt/module.cpp index 2959d3d9f..670763152 100644 --- a/3rdparty/spirv-tools/source/opt/module.cpp +++ b/3rdparty/spirv-tools/source/opt/module.cpp @@ -98,7 +98,10 @@ void Module::ForEachInst(const std::function& f, DELEGATE(ext_inst_debuginfo_); DELEGATE(annotations_); DELEGATE(types_values_); - for (auto& i : functions_) i->ForEachInst(f, run_on_debug_line_insts); + for (auto& i : functions_) { + i->ForEachInst(f, run_on_debug_line_insts, + /* run_on_non_semantic_insts = */ true); + } #undef DELEGATE } @@ -120,8 +123,9 @@ void Module::ForEachInst(const std::function& f, for (auto& i : types_values_) DELEGATE(i); for (auto& i : ext_inst_debuginfo_) DELEGATE(i); for (auto& i : functions_) { - static_cast(i.get())->ForEachInst(f, - run_on_debug_line_insts); + static_cast(i.get())->ForEachInst( + f, run_on_debug_line_insts, + /* run_on_non_semantic_insts = */ true); } if (run_on_debug_line_insts) { for (auto& i : trailing_dbg_line_info_) DELEGATE(i); diff --git a/3rdparty/spirv-tools/source/reduce/CMakeLists.txt b/3rdparty/spirv-tools/source/reduce/CMakeLists.txt index 865510bb7..9480a43cb 100644 --- a/3rdparty/spirv-tools/source/reduce/CMakeLists.txt +++ b/3rdparty/spirv-tools/source/reduce/CMakeLists.txt @@ -70,7 +70,7 @@ set(SPIRV_TOOLS_REDUCE_SOURCES simple_conditional_branch_to_branch_reduction_opportunity.cpp ) -if(MSVC) +if(MSVC AND (NOT ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang"))) # Enable parallel builds across four cores for this lib add_definitions(/MP4) endif() diff --git a/3rdparty/spirv-tools/source/val/validate_extensions.cpp b/3rdparty/spirv-tools/source/val/validate_extensions.cpp index e22e12142..da14779ab 100644 --- a/3rdparty/spirv-tools/source/val/validate_extensions.cpp +++ b/3rdparty/spirv-tools/source/val/validate_extensions.cpp @@ -81,6 +81,7 @@ bool DoesDebugInfoOperandMatchExpectation( const ValidationState_t& _, const std::function& expectation, const Instruction* inst, uint32_t word_index) { + if (inst->words().size() <= word_index) return false; 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 || @@ -167,9 +168,16 @@ spv_result_t ValidateOperandLexicalScope( 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) { + const std::function& ext_inst_name, + bool allow_template_param) { std::function expectation = - [](OpenCLDebugInfo100Instructions dbg_inst) { + [&allow_template_param](OpenCLDebugInfo100Instructions dbg_inst) { + if (allow_template_param && + (dbg_inst == OpenCLDebugInfo100DebugTypeTemplateParameter || + dbg_inst == + OpenCLDebugInfo100DebugTypeTemplateTemplateParameter)) { + return true; + } return OpenCLDebugInfo100DebugTypeBasic <= dbg_inst && dbg_inst <= OpenCLDebugInfo100DebugTypePtrToMember; }; @@ -636,6 +644,45 @@ spv_result_t ValidateClspvReflectionInstruction(ValidationState_t& _, return SPV_SUCCESS; } +bool IsConstIntScalarTypeWith32Or64Bits(ValidationState_t& _, + Instruction* instr) { + if (instr->opcode() != SpvOpConstant) return false; + if (!_.IsIntScalarType(instr->type_id())) return false; + uint32_t size_in_bits = _.GetBitWidth(instr->type_id()); + return size_in_bits == 32 || size_in_bits == 64; +} + +bool IsConstWithIntScalarType(ValidationState_t& _, const Instruction* inst, + uint32_t word_index) { + auto* int_scalar_const = _.FindDef(inst->word(word_index)); + if (int_scalar_const->opcode() == SpvOpConstant && + _.IsIntScalarType(int_scalar_const->type_id())) { + return true; + } + return false; +} + +bool IsDebugVariableWithIntScalarType(ValidationState_t& _, + const Instruction* inst, + uint32_t word_index) { + auto* dbg_int_scalar_var = _.FindDef(inst->word(word_index)); + if (OpenCLDebugInfo100Instructions(dbg_int_scalar_var->word(4)) == + OpenCLDebugInfo100DebugLocalVariable || + OpenCLDebugInfo100Instructions(dbg_int_scalar_var->word(4)) == + OpenCLDebugInfo100DebugGlobalVariable) { + auto* dbg_type = _.FindDef(dbg_int_scalar_var->word(6)); + if (OpenCLDebugInfo100Instructions(dbg_type->word(4)) == + OpenCLDebugInfo100DebugTypeBasic && + (OpenCLDebugInfo100DebugBaseTypeAttributeEncoding(dbg_type->word(7)) == + OpenCLDebugInfo100Signed || + OpenCLDebugInfo100DebugBaseTypeAttributeEncoding(dbg_type->word(7)) == + OpenCLDebugInfo100Unsigned)) { + return true; + } + } + return false; +} + } // anonymous namespace spv_result_t ValidateExtension(ValidationState_t& _, const Instruction* inst) { @@ -2678,17 +2725,53 @@ spv_result_t ValidateExtInst(ValidationState_t& _, const Instruction* inst) { break; } case OpenCLDebugInfo100DebugTypeArray: { - auto validate_base_type = - ValidateOperandDebugType(_, "Base Type", inst, 5, ext_inst_name); + auto validate_base_type = ValidateOperandDebugType( + _, "Base Type", inst, 5, ext_inst_name, false); 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); + bool invalid = false; auto* component_count = _.FindDef(inst->word(i)); - if (!_.IsIntScalarType(component_count->type_id()) || - !component_count->word(3)) { + if (IsConstIntScalarTypeWith32Or64Bits(_, component_count)) { + // TODO: We need a spec discussion for the bindless array. + if (!component_count->word(3)) { + invalid = true; + } + } else if (component_count->words().size() > 6 && + (OpenCLDebugInfo100Instructions(component_count->word( + 4)) == OpenCLDebugInfo100DebugLocalVariable || + OpenCLDebugInfo100Instructions(component_count->word( + 4)) == OpenCLDebugInfo100DebugGlobalVariable)) { + auto* component_count_type = _.FindDef(component_count->word(6)); + if (component_count_type->words().size() > 7) { + if (OpenCLDebugInfo100Instructions(component_count_type->word( + 4)) != OpenCLDebugInfo100DebugTypeBasic || + OpenCLDebugInfo100DebugBaseTypeAttributeEncoding( + component_count_type->word(7)) != + OpenCLDebugInfo100Unsigned) { + invalid = true; + } else { + // DebugTypeBasic for DebugLocalVariable/DebugGlobalVariable + // must have Unsigned encoding and 32 or 64 as its size in bits. + Instruction* size_in_bits = + _.FindDef(component_count_type->word(6)); + if (!_.IsIntScalarType(size_in_bits->type_id()) || + (size_in_bits->word(3) != 32 && + size_in_bits->word(3) != 64)) { + invalid = true; + } + } + } else { + invalid = true; + } + } else { + invalid = true; + } + if (invalid) { return _.diag(SPV_ERROR_INVALID_DATA, inst) - << ext_inst_name() << ": Component Count must be positive " - << "integer"; + << ext_inst_name() << ": Component Count must be " + << "OpConstant with a 32- or 64-bits integer scalar type or " + << "DebugGlobalVariable or DebugLocalVariable with a 32- or " + << "64-bits unsigned integer scalar type"; } } break; @@ -2706,14 +2789,16 @@ spv_result_t ValidateExtInst(ValidationState_t& _, const Instruction* inst) { } case OpenCLDebugInfo100DebugTypeFunction: { auto* return_type = _.FindDef(inst->word(6)); + // TODO: We need a spec discussion that we have to allow return and + // parameter types of a DebugTypeFunction to have template parameter. if (return_type->opcode() != SpvOpTypeVoid) { auto validate_return = ValidateOperandDebugType( - _, "Return Type", inst, 6, ext_inst_name); + _, "Return Type", inst, 6, ext_inst_name, true); 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); + _, "Parameter Types", inst, word_index, ext_inst_name, true); if (validate_param != SPV_SUCCESS) return validate_param; } break; @@ -2727,7 +2812,7 @@ spv_result_t ValidateExtInst(ValidationState_t& _, const Instruction* inst) { }, inst, 6)) { auto validate_underlying_type = ValidateOperandDebugType( - _, "Underlying Types", inst, 6, ext_inst_name); + _, "Underlying Types", inst, 6, ext_inst_name, false); if (validate_underlying_type != SPV_SUCCESS) return validate_underlying_type; } @@ -2784,8 +2869,10 @@ spv_result_t ValidateExtInst(ValidationState_t& _, const Instruction* inst) { } case OpenCLDebugInfo100DebugTypeMember: { CHECK_OPERAND("Name", SpvOpString, 5); + // TODO: We need a spec discussion that we have to allow member types + // to have template parameter. auto validate_type = - ValidateOperandDebugType(_, "Type", inst, 6, ext_inst_name); + ValidateOperandDebugType(_, "Type", inst, 6, ext_inst_name, true); if (validate_type != SPV_SUCCESS) return validate_type; CHECK_DEBUG_OPERAND("Source", OpenCLDebugInfo100DebugSource, 7); CHECK_DEBUG_OPERAND("Parent", OpenCLDebugInfo100DebugTypeComposite, 10); @@ -2823,18 +2910,13 @@ spv_result_t ValidateExtInst(ValidationState_t& _, const Instruction* inst) { case OpenCLDebugInfo100DebugFunction: { CHECK_OPERAND("Name", SpvOpString, 5); auto validate_type = - ValidateOperandDebugType(_, "Type", inst, 6, ext_inst_name); + ValidateOperandDebugType(_, "Type", inst, 6, ext_inst_name, false); 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) { @@ -2852,7 +2934,7 @@ spv_result_t ValidateExtInst(ValidationState_t& _, const Instruction* inst) { case OpenCLDebugInfo100DebugFunctionDeclaration: { CHECK_OPERAND("Name", SpvOpString, 5); auto validate_type = - ValidateOperandDebugType(_, "Type", inst, 6, ext_inst_name); + ValidateOperandDebugType(_, "Type", inst, 6, ext_inst_name, false); if (validate_type != SPV_SUCCESS) return validate_type; CHECK_DEBUG_OPERAND("Source", OpenCLDebugInfo100DebugSource, 7); auto validate_parent = @@ -2870,9 +2952,6 @@ spv_result_t ValidateExtInst(ValidationState_t& _, const Instruction* inst) { 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; @@ -2884,8 +2963,10 @@ spv_result_t ValidateExtInst(ValidationState_t& _, const Instruction* inst) { } case OpenCLDebugInfo100DebugLocalVariable: { CHECK_OPERAND("Name", SpvOpString, 5); + // TODO: We need a spec discussion that we have to allow local variable + // types to have template parameter. auto validate_type = - ValidateOperandDebugType(_, "Type", inst, 6, ext_inst_name); + ValidateOperandDebugType(_, "Type", inst, 6, ext_inst_name, true); if (validate_type != SPV_SUCCESS) return validate_type; CHECK_DEBUG_OPERAND("Source", OpenCLDebugInfo100DebugSource, 7); auto validate_parent = @@ -2896,11 +2977,6 @@ spv_result_t ValidateExtInst(ValidationState_t& _, const Instruction* inst) { 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) { @@ -2920,18 +2996,120 @@ spv_result_t ValidateExtInst(ValidationState_t& _, const Instruction* inst) { } break; } + case OpenCLDebugInfo100DebugTypeTemplate: { + if (!DoesDebugInfoOperandMatchExpectation( + _, + [](OpenCLDebugInfo100Instructions dbg_inst) { + return dbg_inst == OpenCLDebugInfo100DebugTypeComposite || + dbg_inst == OpenCLDebugInfo100DebugFunction; + }, + inst, 5)) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << ext_inst_name() << ": " + << "expected operand Target must be DebugTypeComposite " + << "or DebugFunction"; + } + for (uint32_t word_index = 6; word_index < num_words; ++word_index) { + if (!DoesDebugInfoOperandMatchExpectation( + _, + [](OpenCLDebugInfo100Instructions dbg_inst) { + return dbg_inst == + OpenCLDebugInfo100DebugTypeTemplateParameter || + dbg_inst == + OpenCLDebugInfo100DebugTypeTemplateTemplateParameter; + }, + inst, word_index)) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << ext_inst_name() << ": " + << "expected operand Parameters must be " + << "DebugTypeTemplateParameter or " + << "DebugTypeTemplateTemplateParameter"; + } + } + break; + } + case OpenCLDebugInfo100DebugTypeTemplateParameter: { + CHECK_OPERAND("Name", SpvOpString, 5); + auto validate_actual_type = ValidateOperandDebugType( + _, "Actual Type", inst, 6, ext_inst_name, false); + if (validate_actual_type != SPV_SUCCESS) return validate_actual_type; + if (!DoesDebugInfoOperandMatchExpectation( + _, + [](OpenCLDebugInfo100Instructions dbg_inst) { + return dbg_inst == OpenCLDebugInfo100DebugInfoNone; + }, + inst, 7)) { + CHECK_OPERAND("Value", SpvOpConstant, 7); + } + CHECK_DEBUG_OPERAND("Source", OpenCLDebugInfo100DebugSource, 8); + break; + } + case OpenCLDebugInfo100DebugGlobalVariable: { + CHECK_OPERAND("Name", SpvOpString, 5); + auto validate_type = + ValidateOperandDebugType(_, "Type", inst, 6, ext_inst_name, false); + if (validate_type != SPV_SUCCESS) return validate_type; + CHECK_DEBUG_OPERAND("Source", OpenCLDebugInfo100DebugSource, 7); + auto validate_scope = + ValidateOperandLexicalScope(_, "Scope", inst, 10, ext_inst_name); + if (validate_scope != SPV_SUCCESS) return validate_scope; + CHECK_OPERAND("Linkage Name", SpvOpString, 11); + if (!DoesDebugInfoOperandMatchExpectation( + _, + [](OpenCLDebugInfo100Instructions dbg_inst) { + return dbg_inst == OpenCLDebugInfo100DebugInfoNone; + }, + inst, 12)) { + auto* operand = _.FindDef(inst->word(12)); + if (operand->opcode() != SpvOpVariable && + operand->opcode() != SpvOpConstant) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << ext_inst_name() << ": " + << "expected operand Variable must be a result id of " + "OpVariable or OpConstant or DebugInfoNone"; + } + } + if (num_words == 15) { + CHECK_DEBUG_OPERAND("Static Member Declaration", + OpenCLDebugInfo100DebugTypeMember, 14); + } + break; + } + case OpenCLDebugInfo100DebugInlinedAt: { + auto validate_scope = + ValidateOperandLexicalScope(_, "Scope", inst, 6, ext_inst_name); + if (validate_scope != SPV_SUCCESS) return validate_scope; + if (num_words == 8) { + CHECK_DEBUG_OPERAND("Inlined", OpenCLDebugInfo100DebugInlinedAt, 7); + } + break; + } + case OpenCLDebugInfo100DebugValue: { + CHECK_DEBUG_OPERAND("Local Variable", + OpenCLDebugInfo100DebugLocalVariable, 5); + CHECK_DEBUG_OPERAND("Expression", OpenCLDebugInfo100DebugExpression, 7); + + for (uint32_t word_index = 8; word_index < num_words; ++word_index) { + // TODO: The following code simply checks if it is a const int scalar + // or a DebugLocalVariable or DebugGlobalVariable, but we have to + // check it using the same validation for Indexes of OpAccessChain. + if (!IsConstWithIntScalarType(_, inst, word_index) && + !IsDebugVariableWithIntScalarType(_, inst, word_index)) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << ext_inst_name() << ": expected operand Indexes is " + << "OpConstant, DebugGlobalVariable, or " + << "type is OpConstant with an integer scalar type"; + } + } + 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: diff --git a/3rdparty/spirv-tools/source/val/validate_interfaces.cpp b/3rdparty/spirv-tools/source/val/validate_interfaces.cpp index 833734f4d..d16d48e26 100644 --- a/3rdparty/spirv-tools/source/val/validate_interfaces.cpp +++ b/3rdparty/spirv-tools/source/val/validate_interfaces.cpp @@ -437,6 +437,19 @@ spv_result_t GetLocationsForVariable( spv_result_t ValidateLocations(ValidationState_t& _, const Instruction* entry_point) { + // According to Vulkan 14.1 only the following execution models have + // locations assigned. + switch (entry_point->GetOperandAs(0)) { + case SpvExecutionModelVertex: + case SpvExecutionModelTessellationControl: + case SpvExecutionModelTessellationEvaluation: + case SpvExecutionModelGeometry: + case SpvExecutionModelFragment: + break; + default: + return SPV_SUCCESS; + } + // Locations are stored as a combined location and component values. std::unordered_set input_locations; std::unordered_set output_locations_index0;