From f12c40f5a675f65ce07387a270b547e2e921890f Mon Sep 17 00:00:00 2001 From: Stefano Milizia Date: Wed, 15 Jul 2020 11:58:29 +0000 Subject: [PATCH] spirv-fuzz: Fuzzer pass to interchange zero-like constants (#3524) This fuzzer pass: For each zero-like constant, either finds the existing definition of the corresponding toggled one (OpConstantNull becomes zero-valued scalar OpConstant or vice versa) or creates a new one if it doesn't exist and records that the two are synonyms For each use of these constants, probabilistically decides whether to change it with the corresponding toggled constant id (as described in #3486 ) Only uses inside blocks of instructions are considered and not, for example, in instructions declaring other constants. --- source/fuzz/CMakeLists.txt | 2 + source/fuzz/fuzzer.cpp | 6 +- source/fuzz/fuzzer_context.cpp | 4 + source/fuzz/fuzzer_context.h | 4 + source/fuzz/fuzzer_pass.cpp | 22 ++++ source/fuzz/fuzzer_pass.h | 6 + source/fuzz/fuzzer_pass_apply_id_synonyms.cpp | 5 +- ...r_pass_interchange_zero_like_constants.cpp | 124 ++++++++++++++++++ ...zer_pass_interchange_zero_like_constants.h | 63 +++++++++ source/fuzz/fuzzer_util.cpp | 6 + source/fuzz/fuzzer_util.h | 5 + 11 files changed, 243 insertions(+), 4 deletions(-) create mode 100644 source/fuzz/fuzzer_pass_interchange_zero_like_constants.cpp create mode 100644 source/fuzz/fuzzer_pass_interchange_zero_like_constants.h diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt index 0433f242d..1a816edd9 100644 --- a/source/fuzz/CMakeLists.txt +++ b/source/fuzz/CMakeLists.txt @@ -64,6 +64,7 @@ if(SPIRV_BUILD_FUZZER) fuzzer_pass_copy_objects.h fuzzer_pass_donate_modules.h fuzzer_pass_invert_comparison_operators.h + fuzzer_pass_interchange_zero_like_constants.h fuzzer_pass_merge_blocks.h fuzzer_pass_obfuscate_constants.h fuzzer_pass_outline_functions.h @@ -183,6 +184,7 @@ if(SPIRV_BUILD_FUZZER) fuzzer_pass_copy_objects.cpp fuzzer_pass_donate_modules.cpp fuzzer_pass_invert_comparison_operators.cpp + fuzzer_pass_interchange_zero_like_constants.cpp fuzzer_pass_merge_blocks.cpp fuzzer_pass_obfuscate_constants.cpp fuzzer_pass_outline_functions.cpp diff --git a/source/fuzz/fuzzer.cpp b/source/fuzz/fuzzer.cpp index ae896a3ce..edd6e1794 100644 --- a/source/fuzz/fuzzer.cpp +++ b/source/fuzz/fuzzer.cpp @@ -18,7 +18,6 @@ #include #include -#include "fuzzer_pass_adjust_memory_operands_masks.h" #include "source/fuzz/fact_manager.h" #include "source/fuzz/fuzzer_context.h" #include "source/fuzz/fuzzer_pass_add_access_chains.h" @@ -41,11 +40,13 @@ #include "source/fuzz/fuzzer_pass_adjust_branch_weights.h" #include "source/fuzz/fuzzer_pass_adjust_function_controls.h" #include "source/fuzz/fuzzer_pass_adjust_loop_controls.h" +#include "source/fuzz/fuzzer_pass_adjust_memory_operands_masks.h" #include "source/fuzz/fuzzer_pass_adjust_selection_controls.h" #include "source/fuzz/fuzzer_pass_apply_id_synonyms.h" #include "source/fuzz/fuzzer_pass_construct_composites.h" #include "source/fuzz/fuzzer_pass_copy_objects.h" #include "source/fuzz/fuzzer_pass_donate_modules.h" +#include "source/fuzz/fuzzer_pass_interchange_zero_like_constants.h" #include "source/fuzz/fuzzer_pass_invert_comparison_operators.h" #include "source/fuzz/fuzzer_pass_merge_blocks.h" #include "source/fuzz/fuzzer_pass_obfuscate_constants.h" @@ -330,6 +331,9 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run( MaybeAddPass( &final_passes, ir_context.get(), &transformation_context, &fuzzer_context, transformation_sequence_out); + MaybeAddPass( + &final_passes, ir_context.get(), &transformation_context, &fuzzer_context, + transformation_sequence_out); MaybeAddPass( &final_passes, ir_context.get(), &transformation_context, &fuzzer_context, transformation_sequence_out); diff --git a/source/fuzz/fuzzer_context.cpp b/source/fuzz/fuzzer_context.cpp index e244f7583..f2b81704e 100644 --- a/source/fuzz/fuzzer_context.cpp +++ b/source/fuzz/fuzzer_context.cpp @@ -64,6 +64,8 @@ const std::pair kChanceOfCopyingObject = {20, 50}; const std::pair kChanceOfDonatingAdditionalModule = {5, 50}; const std::pair kChanceOfGoingDeeperWhenMakingAccessChain = {50, 95}; +const std::pair kChanceOfInterchangingZeroLikeConstants = { + 10, 90}; const std::pair kChanceOfInvertingComparisonOperators = { 20, 50}; const std::pair kChanceOfMakingDonorLivesafe = {40, 60}; @@ -181,6 +183,8 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator, ChooseBetweenMinAndMax(kChanceOfDonatingAdditionalModule); chance_of_going_deeper_when_making_access_chain_ = ChooseBetweenMinAndMax(kChanceOfGoingDeeperWhenMakingAccessChain); + chance_of_interchanging_zero_like_constants_ = + ChooseBetweenMinAndMax(kChanceOfInterchangingZeroLikeConstants); chance_of_inverting_comparison_operators_ = ChooseBetweenMinAndMax(kChanceOfInvertingComparisonOperators); chance_of_making_donor_livesafe_ = diff --git a/source/fuzz/fuzzer_context.h b/source/fuzz/fuzzer_context.h index 61990e540..90539c88c 100644 --- a/source/fuzz/fuzzer_context.h +++ b/source/fuzz/fuzzer_context.h @@ -183,6 +183,9 @@ class FuzzerContext { uint32_t GetChanceOfGoingDeeperWhenMakingAccessChain() { return chance_of_going_deeper_when_making_access_chain_; } + uint32_t GetChanceOfInterchangingZeroLikeConstants() { + return chance_of_interchanging_zero_like_constants_; + } uint32_t GetChanceOfInvertingComparisonOperators() { return chance_of_inverting_comparison_operators_; } @@ -325,6 +328,7 @@ class FuzzerContext { uint32_t chance_of_copying_object_; uint32_t chance_of_donating_additional_module_; uint32_t chance_of_going_deeper_when_making_access_chain_; + uint32_t chance_of_interchanging_zero_like_constants_; uint32_t chance_of_inverting_comparison_operators_; uint32_t chance_of_making_donor_livesafe_; uint32_t chance_of_merging_blocks_; diff --git a/source/fuzz/fuzzer_pass.cpp b/source/fuzz/fuzzer_pass.cpp index 2034e4df5..1fdf8a589 100644 --- a/source/fuzz/fuzzer_pass.cpp +++ b/source/fuzz/fuzzer_pass.cpp @@ -20,6 +20,7 @@ #include "source/fuzz/instruction_descriptor.h" #include "source/fuzz/transformation_add_constant_boolean.h" #include "source/fuzz/transformation_add_constant_composite.h" +#include "source/fuzz/transformation_add_constant_null.h" #include "source/fuzz/transformation_add_constant_scalar.h" #include "source/fuzz/transformation_add_global_undef.h" #include "source/fuzz/transformation_add_type_boolean.h" @@ -373,6 +374,27 @@ uint32_t FuzzerPass::FindOrCreateGlobalUndef(uint32_t type_id) { return result; } +uint32_t FuzzerPass::FindOrCreateNullConstant(uint32_t type_id) { + // Find existing declaration + opt::analysis::NullConstant null_constant( + GetIRContext()->get_type_mgr()->GetType(type_id)); + auto existing_constant = + GetIRContext()->get_constant_mgr()->FindConstant(&null_constant); + + // Return if found + if (existing_constant) { + return GetIRContext() + ->get_constant_mgr() + ->GetDefiningInstruction(existing_constant) + ->result_id(); + } + + // Create new if not found + auto result = GetFuzzerContext()->GetFreshId(); + ApplyTransformation(TransformationAddConstantNull(result, type_id)); + return result; +} + std::pair, std::map>> FuzzerPass::GetAvailableBasicTypesAndPointers( SpvStorageClass storage_class) const { diff --git a/source/fuzz/fuzzer_pass.h b/source/fuzz/fuzzer_pass.h index b902895ac..4d905b5ea 100644 --- a/source/fuzz/fuzzer_pass.h +++ b/source/fuzz/fuzzer_pass.h @@ -192,6 +192,12 @@ class FuzzerPass { // If no such instruction exists, a transformation is applied to add it. uint32_t FindOrCreateGlobalUndef(uint32_t type_id); + // Returns the id of an OpNullConstant instruction of type |type_id|. If + // that instruction doesn't exist, it is added through a transformation. + // |type_id| must be a valid result id of an OpType* instruction that exists + // in the module. + uint32_t FindOrCreateNullConstant(uint32_t type_id); + // Define a *basic type* to be an integer, boolean or floating-point type, // or a matrix, vector, struct or fixed-size array built from basic types. In // particular, a basic type cannot contain an opaque type (such as an image), diff --git a/source/fuzz/fuzzer_pass_apply_id_synonyms.cpp b/source/fuzz/fuzzer_pass_apply_id_synonyms.cpp index 0ec93e1ba..122c2ddfe 100644 --- a/source/fuzz/fuzzer_pass_apply_id_synonyms.cpp +++ b/source/fuzz/fuzzer_pass_apply_id_synonyms.cpp @@ -73,10 +73,9 @@ void FuzzerPassApplyIdSynonyms::Apply() { continue; } // |use_index| is the absolute index of the operand. We require - // the index of the operand restricted to input operands only, so - // we subtract the number of non-input operands from |use_index|. + // the index of the operand restricted to input operands only. uint32_t use_in_operand_index = - use_index - use_inst->NumOperands() + use_inst->NumInOperands(); + fuzzerutil::InOperandIndexFromOperandIndex(*use_inst, use_index); if (!TransformationReplaceIdWithSynonym::UseCanBeReplacedWithSynonym( GetIRContext(), use_inst, use_in_operand_index)) { continue; diff --git a/source/fuzz/fuzzer_pass_interchange_zero_like_constants.cpp b/source/fuzz/fuzzer_pass_interchange_zero_like_constants.cpp new file mode 100644 index 000000000..ebf0ef445 --- /dev/null +++ b/source/fuzz/fuzzer_pass_interchange_zero_like_constants.cpp @@ -0,0 +1,124 @@ +// Copyright (c) 2020 Stefano Milizia +// 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_interchange_zero_like_constants.h" +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/id_use_descriptor.h" +#include "source/fuzz/transformation_record_synonymous_constants.h" +#include "source/fuzz/transformation_replace_id_with_synonym.h" + +namespace spvtools { +namespace fuzz { +FuzzerPassInterchangeZeroLikeConstants::FuzzerPassInterchangeZeroLikeConstants( + opt::IRContext* ir_context, TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations) + : FuzzerPass(ir_context, transformation_context, fuzzer_context, + transformations) {} + +FuzzerPassInterchangeZeroLikeConstants:: + ~FuzzerPassInterchangeZeroLikeConstants() = default; + +uint32_t FuzzerPassInterchangeZeroLikeConstants::FindOrCreateToggledConstant( + opt::Instruction* declaration) { + auto constant = GetIRContext()->get_constant_mgr()->FindDeclaredConstant( + declaration->result_id()); + + // This pass only toggles zero-like constants + if (!constant->IsZero()) { + return 0; + } + + if (constant->AsScalarConstant()) { + return FindOrCreateNullConstant(declaration->type_id()); + } else if (constant->AsNullConstant()) { + // Add declaration of equivalent scalar constant + auto kind = constant->type()->kind(); + if (kind == opt::analysis::Type::kBool || + kind == opt::analysis::Type::kInteger || + kind == opt::analysis::Type::kFloat) { + return FindOrCreateZeroConstant(declaration->type_id()); + } + } + + return 0; +} + +void FuzzerPassInterchangeZeroLikeConstants::MaybeAddUseToReplace( + opt::Instruction* use_inst, uint32_t use_index, uint32_t replacement_id, + std::vector>* + uses_to_replace) { + // Only consider this use if it is in a block + if (!GetIRContext()->get_instr_block(use_inst)) { + return; + } + + // Get the index of the operand restricted to input operands. + uint32_t in_operand_index = + fuzzerutil::InOperandIndexFromOperandIndex(*use_inst, use_index); + auto id_use_descriptor = + MakeIdUseDescriptorFromUse(GetIRContext(), use_inst, in_operand_index); + uses_to_replace->emplace_back( + std::make_pair(id_use_descriptor, replacement_id)); +} + +void FuzzerPassInterchangeZeroLikeConstants::Apply() { + // Make vector keeping track of all the uses we want to replace. + // This is a vector of pairs, where the first element is an id use descriptor + // identifying the use of a constant id and the second is the id that should + // be used to replace it. + std::vector> uses_to_replace; + + for (auto constant : GetIRContext()->GetConstants()) { + uint32_t constant_id = constant->result_id(); + uint32_t toggled_id = FindOrCreateToggledConstant(constant); + + if (!toggled_id) { + // Not a zero-like constant + continue; + } + + // Record synonymous constants + ApplyTransformation( + TransformationRecordSynonymousConstants(constant_id, toggled_id)); + + // Find all the uses of the constant and, for each, probabilistically + // decide whether to replace it. + GetIRContext()->get_def_use_mgr()->ForEachUse( + constant_id, + [this, toggled_id, &uses_to_replace](opt::Instruction* use_inst, + uint32_t use_index) -> void { + if (GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext() + ->GetChanceOfInterchangingZeroLikeConstants())) { + MaybeAddUseToReplace(use_inst, use_index, toggled_id, + &uses_to_replace); + } + }); + } + + // Replace the ids + for (auto use_to_replace : uses_to_replace) { + auto transformation = TransformationReplaceIdWithSynonym( + use_to_replace.first, use_to_replace.second); + if (transformation.IsApplicable(GetIRContext(), + *GetTransformationContext())) { + transformation.Apply(GetIRContext(), GetTransformationContext()); + *GetTransformations()->add_transformation() = transformation.ToMessage(); + } + } +} +} // namespace fuzz +} // namespace spvtools \ No newline at end of file diff --git a/source/fuzz/fuzzer_pass_interchange_zero_like_constants.h b/source/fuzz/fuzzer_pass_interchange_zero_like_constants.h new file mode 100644 index 000000000..4fcc44e0b --- /dev/null +++ b/source/fuzz/fuzzer_pass_interchange_zero_like_constants.h @@ -0,0 +1,63 @@ +// Copyright (c) 2020 Stefano Milizia +// 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_INTERCHANGE_ZERO_LIKE_CONSTANTS_ +#define SOURCE_FUZZ_FUZZER_PASS_INTERCHANGE_ZERO_LIKE_CONSTANTS_ + +#include "source/fuzz/fuzzer_pass.h" + +namespace spvtools { +namespace fuzz { + +// A pass that: +// - Finds all the zero-like constant definitions in the module and adds the +// definitions of the corresponding synonym, recording the fact that they +// are synonymous. If the synonym is already in the module, it does not +// add a new one. +// - For each use of a zero-like constant, decides whether to change it to the +// id of the toggled constant. +class FuzzerPassInterchangeZeroLikeConstants : public FuzzerPass { + public: + FuzzerPassInterchangeZeroLikeConstants( + opt::IRContext* ir_context, TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations); + + ~FuzzerPassInterchangeZeroLikeConstants() override; + + void Apply() override; + + private: + // Given the declaration of a zero-like constant, it finds or creates the + // corresponding toggled constant (a scalar constant of value 0 becomes a + // null constant of the same type and vice versa). + // Returns the id of the toggled instruction if the constant is zero-like, + // 0 otherwise. + uint32_t FindOrCreateToggledConstant(opt::Instruction* declaration); + + // Given an id use (described by an instruction and an index) and an id with + // which the original one should be replaced, adds a pair (with the elements + // being the corresponding id use descriptor and the replacement id) to + // |uses_to_replace| if the use is in an instruction block, otherwise does + // nothing. + void MaybeAddUseToReplace( + opt::Instruction* use_inst, uint32_t use_index, uint32_t replacement_id, + std::vector>* + uses_to_replace); +}; + +} // namespace fuzz +} // namespace spvtools +#endif // SOURCE_FUZZ_FUZZER_PASS_INTERCHANGE_ZERO_LIKE_CONSTANTS_ diff --git a/source/fuzz/fuzzer_util.cpp b/source/fuzz/fuzzer_util.cpp index 29a4cff77..a9b03b3bd 100644 --- a/source/fuzz/fuzzer_util.cpp +++ b/source/fuzz/fuzzer_util.cpp @@ -552,6 +552,12 @@ uint32_t MaybeGetPointerType(opt::IRContext* context, uint32_t pointee_type_id, return 0; } +uint32_t InOperandIndexFromOperandIndex(const opt::Instruction& inst, + uint32_t absolute_index) { + // Subtract the number of non-input operands from the index + return absolute_index - inst.NumOperands() + inst.NumInOperands(); +} + bool IsNullConstantSupported(const opt::analysis::Type& type) { return type.AsBool() || type.AsInteger() || type.AsFloat() || type.AsMatrix() || type.AsVector() || type.AsArray() || diff --git a/source/fuzz/fuzzer_util.h b/source/fuzz/fuzzer_util.h index 998900687..94dcc837b 100644 --- a/source/fuzz/fuzzer_util.h +++ b/source/fuzz/fuzzer_util.h @@ -213,6 +213,11 @@ SpvStorageClass GetStorageClassFromPointerType(opt::IRContext* context, uint32_t MaybeGetPointerType(opt::IRContext* context, uint32_t pointee_type_id, SpvStorageClass storage_class); +// Given an instruction |inst| and an operand absolute index |absolute_index|, +// returns the index of the operand restricted to the input operands. +uint32_t InOperandIndexFromOperandIndex(const opt::Instruction& inst, + uint32_t absolute_index); + // Returns true if and only if |type| is one of the types for which it is legal // to have an OpConstantNull value. bool IsNullConstantSupported(const opt::analysis::Type& type);