diff --git a/include/spirv-tools/libspirv.h b/include/spirv-tools/libspirv.h index 09dae224..16cdd1b1 100644 --- a/include/spirv-tools/libspirv.h +++ b/include/spirv-tools/libspirv.h @@ -715,6 +715,11 @@ SPIRV_TOOLS_EXPORT void spvFuzzerOptionsSetShrinkerStepLimit( SPIRV_TOOLS_EXPORT void spvFuzzerOptionsEnableFuzzerPassValidation( spv_fuzzer_options options); +// Enables all fuzzer passes during a fuzzing run (instead of a random subset +// of passes). +SPIRV_TOOLS_EXPORT void spvFuzzerOptionsEnableAllPasses( + spv_fuzzer_options options); + // Encodes the given SPIR-V assembly text to its binary representation. The // length parameter specifies the number of bytes for text. Encoded binary will // be stored into *binary. Any error will be written into *diagnostic if diff --git a/include/spirv-tools/libspirv.hpp b/include/spirv-tools/libspirv.hpp index 5dddf8bf..2018e461 100644 --- a/include/spirv-tools/libspirv.hpp +++ b/include/spirv-tools/libspirv.hpp @@ -247,6 +247,9 @@ class FuzzerOptions { spvFuzzerOptionsEnableFuzzerPassValidation(options_); } + // See spvFuzzerOptionsEnableAllPasses. + void enable_all_passes() { spvFuzzerOptionsEnableAllPasses(options_); } + private: spv_fuzzer_options options_; }; diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt index fa5ed0a4..733bc569 100644 --- a/source/fuzz/CMakeLists.txt +++ b/source/fuzz/CMakeLists.txt @@ -118,6 +118,13 @@ if(SPIRV_BUILD_FUZZER) instruction_descriptor.h instruction_message.h overflow_id_source.h + pass_management/repeated_pass_instances.h + pass_management/repeated_pass_manager.h + pass_management/repeated_pass_manager_looped_with_recommendations.h + pass_management/repeated_pass_manager_random_with_recommendations.h + pass_management/repeated_pass_manager_simple.h + pass_management/repeated_pass_recommender.h + pass_management/repeated_pass_recommender_standard.h protobufs/spirvfuzz_protobufs.h pseudo_random_generator.h random_generator.h @@ -285,6 +292,12 @@ if(SPIRV_BUILD_FUZZER) instruction_descriptor.cpp instruction_message.cpp overflow_id_source.cpp + pass_management/repeated_pass_manager.cpp + pass_management/repeated_pass_manager_looped_with_recommendations.cpp + pass_management/repeated_pass_manager_random_with_recommendations.cpp + pass_management/repeated_pass_manager_simple.cpp + pass_management/repeated_pass_recommender.cpp + pass_management/repeated_pass_recommender_standard.cpp pseudo_random_generator.cpp random_generator.cpp replayer.cpp diff --git a/source/fuzz/fuzzer.cpp b/source/fuzz/fuzzer.cpp index be530a50..79228db0 100644 --- a/source/fuzz/fuzzer.cpp +++ b/source/fuzz/fuzzer.cpp @@ -16,6 +16,7 @@ #include #include +#include #include "source/fuzz/fact_manager/fact_manager.h" #include "source/fuzz/fuzzer_context.h" @@ -81,6 +82,11 @@ #include "source/fuzz/fuzzer_pass_swap_commutable_operands.h" #include "source/fuzz/fuzzer_pass_swap_conditional_branch_operands.h" #include "source/fuzz/fuzzer_pass_toggle_access_chain_instruction.h" +#include "source/fuzz/pass_management/repeated_pass_manager.h" +#include "source/fuzz/pass_management/repeated_pass_manager_looped_with_recommendations.h" +#include "source/fuzz/pass_management/repeated_pass_manager_random_with_recommendations.h" +#include "source/fuzz/pass_management/repeated_pass_manager_simple.h" +#include "source/fuzz/pass_management/repeated_pass_recommender_standard.h" #include "source/fuzz/protobufs/spirvfuzz_protobufs.h" #include "source/fuzz/pseudo_random_generator.h" #include "source/fuzz/transformation_context.h" @@ -94,37 +100,21 @@ namespace fuzz { namespace { const uint32_t kIdBoundGap = 100; -const uint32_t kTransformationLimit = 500; - -const uint32_t kChanceOfApplyingAnotherPass = 85; - -// A convenience method to add a fuzzer pass to |passes| with probability 0.5. -// All fuzzer passes take |ir_context|, |transformation_context|, -// |fuzzer_context| and |transformation_sequence_out| as parameters. Extra -// arguments can be provided via |extra_args|. -template -void MaybeAddPass( - std::vector>* passes, - opt::IRContext* ir_context, TransformationContext* transformation_context, - FuzzerContext* fuzzer_context, - protobufs::TransformationSequence* transformation_sequence_out, - Args&&... extra_args) { - if (fuzzer_context->ChooseEven()) { - passes->push_back(MakeUnique(ir_context, transformation_context, - fuzzer_context, transformation_sequence_out, - std::forward(extra_args)...)); - } -} +const uint32_t kTransformationLimit = 2000; } // namespace -Fuzzer::Fuzzer(spv_target_env target_env, uint32_t seed, +Fuzzer::Fuzzer(spv_target_env target_env, uint32_t seed, bool enable_all_passes, + RepeatedPassStrategy repeated_pass_strategy, bool validate_after_each_fuzzer_pass, spv_validator_options validator_options) : target_env_(target_env), seed_(seed), + enable_all_passes_(enable_all_passes), + repeated_pass_strategy_(repeated_pass_strategy), validate_after_each_fuzzer_pass_(validate_after_each_fuzzer_pass), - validator_options_(validator_options) {} + validator_options_(validator_options), + num_repeated_passes_applied_(0) {} Fuzzer::~Fuzzer() = default; @@ -132,6 +122,34 @@ void Fuzzer::SetMessageConsumer(MessageConsumer consumer) { consumer_ = std::move(consumer); } +template +void Fuzzer::MaybeAddRepeatedPass( + RepeatedPassInstances* pass_instances, opt::IRContext* ir_context, + TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformation_sequence_out, + Args&&... extra_args) const { + if (enable_all_passes_ || fuzzer_context->ChooseEven()) { + pass_instances->SetPass(MakeUnique( + ir_context, transformation_context, fuzzer_context, + transformation_sequence_out, std::forward(extra_args)...)); + } +} + +template +void Fuzzer::MaybeAddFinalPass( + std::vector>* passes, + opt::IRContext* ir_context, TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformation_sequence_out, + Args&&... extra_args) const { + if (enable_all_passes_ || fuzzer_context->ChooseEven()) { + passes->push_back(MakeUnique( + ir_context, transformation_context, fuzzer_context, + transformation_sequence_out, std::forward(extra_args)...)); + } +} + bool Fuzzer::ApplyPassAndCheckValidity( FuzzerPass* pass, const opt::IRContext& ir_context, const spvtools::SpirvTools& tools) const { @@ -155,7 +173,7 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run( const protobufs::FactSequence& initial_facts, const std::vector& donor_suppliers, std::vector* binary_out, - protobufs::TransformationSequence* transformation_sequence_out) const { + protobufs::TransformationSequence* transformation_sequence_out) { // Check compatibility between the library version being linked with and the // header files being used. GOOGLE_PROTOBUF_VERIFY_VERSION; @@ -198,214 +216,233 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run( TransformationContext transformation_context(&fact_manager, validator_options_); - // Apply some semantics-preserving passes. - // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3764): Enable - // certain passes to run with a higher priority than the others. - std::vector> passes; - while (passes.empty()) { - 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); - 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); - 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); - 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); - 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); - 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); - MaybeAddPass( - &passes, ir_context.get(), &transformation_context, &fuzzer_context, - transformation_sequence_out, donor_suppliers); - MaybeAddPass( - &passes, ir_context.get(), &transformation_context, &fuzzer_context, - transformation_sequence_out); - MaybeAddPass( - &passes, ir_context.get(), &transformation_context, &fuzzer_context, - transformation_sequence_out); - 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); - 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); - 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); - 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); - 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); - MaybeAddPass( - &passes, ir_context.get(), &transformation_context, &fuzzer_context, - transformation_sequence_out); - MaybeAddPass( - &passes, ir_context.get(), &transformation_context, &fuzzer_context, - transformation_sequence_out); + RepeatedPassInstances pass_instances{}; + do { + // Each call to MaybeAddRepeatedPass randomly decides whether the given pass + // should be enabled, and adds an instance of the pass to |pass_instances| + // if it is enabled. + // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3764): Consider + // enabling some passes always, or with higher probability. + MaybeAddRepeatedPass( + &pass_instances, ir_context.get(), &transformation_context, + &fuzzer_context, transformation_sequence_out); + MaybeAddRepeatedPass( + &pass_instances, ir_context.get(), &transformation_context, + &fuzzer_context, transformation_sequence_out); + MaybeAddRepeatedPass( + &pass_instances, ir_context.get(), &transformation_context, + &fuzzer_context, transformation_sequence_out); + MaybeAddRepeatedPass( + &pass_instances, ir_context.get(), &transformation_context, + &fuzzer_context, transformation_sequence_out); + MaybeAddRepeatedPass( + &pass_instances, ir_context.get(), &transformation_context, + &fuzzer_context, transformation_sequence_out); + MaybeAddRepeatedPass( + &pass_instances, ir_context.get(), &transformation_context, + &fuzzer_context, transformation_sequence_out); + MaybeAddRepeatedPass( + &pass_instances, ir_context.get(), &transformation_context, + &fuzzer_context, transformation_sequence_out); + MaybeAddRepeatedPass( + &pass_instances, ir_context.get(), &transformation_context, + &fuzzer_context, transformation_sequence_out); + MaybeAddRepeatedPass( + &pass_instances, ir_context.get(), &transformation_context, + &fuzzer_context, transformation_sequence_out); + MaybeAddRepeatedPass( + &pass_instances, ir_context.get(), &transformation_context, + &fuzzer_context, transformation_sequence_out); + MaybeAddRepeatedPass( + &pass_instances, ir_context.get(), &transformation_context, + &fuzzer_context, transformation_sequence_out); + MaybeAddRepeatedPass( + &pass_instances, ir_context.get(), &transformation_context, + &fuzzer_context, transformation_sequence_out); + MaybeAddRepeatedPass( + &pass_instances, ir_context.get(), &transformation_context, + &fuzzer_context, transformation_sequence_out); + MaybeAddRepeatedPass( + &pass_instances, ir_context.get(), &transformation_context, + &fuzzer_context, transformation_sequence_out); + MaybeAddRepeatedPass( + &pass_instances, ir_context.get(), &transformation_context, + &fuzzer_context, transformation_sequence_out); + MaybeAddRepeatedPass( + &pass_instances, ir_context.get(), &transformation_context, + &fuzzer_context, transformation_sequence_out); + MaybeAddRepeatedPass( + &pass_instances, ir_context.get(), &transformation_context, + &fuzzer_context, transformation_sequence_out); + MaybeAddRepeatedPass( + &pass_instances, ir_context.get(), &transformation_context, + &fuzzer_context, transformation_sequence_out); + MaybeAddRepeatedPass( + &pass_instances, ir_context.get(), &transformation_context, + &fuzzer_context, transformation_sequence_out); + MaybeAddRepeatedPass( + &pass_instances, ir_context.get(), &transformation_context, + &fuzzer_context, transformation_sequence_out); + MaybeAddRepeatedPass( + &pass_instances, ir_context.get(), &transformation_context, + &fuzzer_context, transformation_sequence_out); + MaybeAddRepeatedPass( + &pass_instances, ir_context.get(), &transformation_context, + &fuzzer_context, transformation_sequence_out); + MaybeAddRepeatedPass( + &pass_instances, ir_context.get(), &transformation_context, + &fuzzer_context, transformation_sequence_out); + MaybeAddRepeatedPass( + &pass_instances, ir_context.get(), &transformation_context, + &fuzzer_context, transformation_sequence_out); + MaybeAddRepeatedPass( + &pass_instances, ir_context.get(), &transformation_context, + &fuzzer_context, transformation_sequence_out, donor_suppliers); + MaybeAddRepeatedPass( + &pass_instances, ir_context.get(), &transformation_context, + &fuzzer_context, transformation_sequence_out); + MaybeAddRepeatedPass( + &pass_instances, ir_context.get(), &transformation_context, + &fuzzer_context, transformation_sequence_out); + MaybeAddRepeatedPass( + &pass_instances, ir_context.get(), &transformation_context, + &fuzzer_context, transformation_sequence_out); + MaybeAddRepeatedPass( + &pass_instances, ir_context.get(), &transformation_context, + &fuzzer_context, transformation_sequence_out); + MaybeAddRepeatedPass( + &pass_instances, ir_context.get(), &transformation_context, + &fuzzer_context, transformation_sequence_out); + MaybeAddRepeatedPass( + &pass_instances, ir_context.get(), &transformation_context, + &fuzzer_context, transformation_sequence_out); + MaybeAddRepeatedPass( + &pass_instances, ir_context.get(), &transformation_context, + &fuzzer_context, transformation_sequence_out); + MaybeAddRepeatedPass( + &pass_instances, ir_context.get(), &transformation_context, + &fuzzer_context, transformation_sequence_out); + MaybeAddRepeatedPass( + &pass_instances, ir_context.get(), &transformation_context, + &fuzzer_context, transformation_sequence_out); + MaybeAddRepeatedPass( + &pass_instances, ir_context.get(), &transformation_context, + &fuzzer_context, transformation_sequence_out); + MaybeAddRepeatedPass( + &pass_instances, ir_context.get(), &transformation_context, + &fuzzer_context, transformation_sequence_out); + MaybeAddRepeatedPass( + &pass_instances, ir_context.get(), &transformation_context, + &fuzzer_context, transformation_sequence_out); + MaybeAddRepeatedPass( + &pass_instances, ir_context.get(), &transformation_context, + &fuzzer_context, transformation_sequence_out); + MaybeAddRepeatedPass( + &pass_instances, ir_context.get(), &transformation_context, + &fuzzer_context, transformation_sequence_out); + MaybeAddRepeatedPass( + &pass_instances, ir_context.get(), &transformation_context, + &fuzzer_context, transformation_sequence_out); + MaybeAddRepeatedPass( + &pass_instances, ir_context.get(), &transformation_context, + &fuzzer_context, transformation_sequence_out); + MaybeAddRepeatedPass( + &pass_instances, ir_context.get(), &transformation_context, + &fuzzer_context, transformation_sequence_out); + MaybeAddRepeatedPass( + &pass_instances, ir_context.get(), &transformation_context, + &fuzzer_context, transformation_sequence_out); + MaybeAddRepeatedPass( + &pass_instances, ir_context.get(), &transformation_context, + &fuzzer_context, transformation_sequence_out); + MaybeAddRepeatedPass( + &pass_instances, ir_context.get(), &transformation_context, + &fuzzer_context, transformation_sequence_out); + MaybeAddRepeatedPass( + &pass_instances, ir_context.get(), &transformation_context, + &fuzzer_context, transformation_sequence_out); + MaybeAddRepeatedPass( + &pass_instances, ir_context.get(), &transformation_context, + &fuzzer_context, transformation_sequence_out); + MaybeAddRepeatedPass( + &pass_instances, ir_context.get(), &transformation_context, + &fuzzer_context, transformation_sequence_out); + MaybeAddRepeatedPass( + &pass_instances, ir_context.get(), &transformation_context, + &fuzzer_context, transformation_sequence_out); + MaybeAddRepeatedPass( + &pass_instances, ir_context.get(), &transformation_context, + &fuzzer_context, transformation_sequence_out); + MaybeAddRepeatedPass( + &pass_instances, ir_context.get(), &transformation_context, + &fuzzer_context, transformation_sequence_out); + // There is a theoretical possibility that no pass instances were created + // until now; loop again if so. + } while (pass_instances.GetPasses().empty()); + + RepeatedPassRecommenderStandard pass_recommender(&pass_instances, + &fuzzer_context); + + std::unique_ptr repeated_pass_manager = nullptr; + switch (repeated_pass_strategy_) { + case RepeatedPassStrategy::kSimple: + repeated_pass_manager = MakeUnique( + &fuzzer_context, &pass_instances); + break; + case RepeatedPassStrategy::kLoopedWithRecommendations: + repeated_pass_manager = + MakeUnique( + &fuzzer_context, &pass_instances, &pass_recommender); + break; + case RepeatedPassStrategy::kRandomWithRecommendations: + repeated_pass_manager = + MakeUnique( + &fuzzer_context, &pass_instances, &pass_recommender); + break; } - bool is_first = true; - while (static_cast( - transformation_sequence_out->transformation_size()) < - kTransformationLimit && - (is_first || - fuzzer_context.ChoosePercentage(kChanceOfApplyingAnotherPass))) { - is_first = false; - if (!ApplyPassAndCheckValidity( - passes[fuzzer_context.RandomIndex(passes)].get(), *ir_context, - tools)) { + do { + if (!ApplyPassAndCheckValidity(repeated_pass_manager->ChoosePass(), + *ir_context, tools)) { return Fuzzer::FuzzerResultStatus::kFuzzerPassLedToInvalidModule; } - } + } while ( + ShouldContinueFuzzing(*transformation_sequence_out, &fuzzer_context)); // Now apply some passes that it does not make sense to apply repeatedly, // as they do not unlock other passes. std::vector> final_passes; - MaybeAddPass( + MaybeAddFinalPass( &final_passes, ir_context.get(), &transformation_context, &fuzzer_context, transformation_sequence_out); - MaybeAddPass( + MaybeAddFinalPass( &final_passes, ir_context.get(), &transformation_context, &fuzzer_context, transformation_sequence_out); - MaybeAddPass( + MaybeAddFinalPass( &final_passes, ir_context.get(), &transformation_context, &fuzzer_context, transformation_sequence_out); - MaybeAddPass( + MaybeAddFinalPass( &final_passes, ir_context.get(), &transformation_context, &fuzzer_context, transformation_sequence_out); - MaybeAddPass( + MaybeAddFinalPass( &final_passes, ir_context.get(), &transformation_context, &fuzzer_context, transformation_sequence_out); - MaybeAddPass( + MaybeAddFinalPass( &final_passes, ir_context.get(), &transformation_context, &fuzzer_context, transformation_sequence_out); - MaybeAddPass( + MaybeAddFinalPass( &final_passes, ir_context.get(), &transformation_context, &fuzzer_context, transformation_sequence_out); - MaybeAddPass( + MaybeAddFinalPass( &final_passes, ir_context.get(), &transformation_context, &fuzzer_context, transformation_sequence_out); - MaybeAddPass( + MaybeAddFinalPass( &final_passes, ir_context.get(), &transformation_context, &fuzzer_context, transformation_sequence_out); - MaybeAddPass( + MaybeAddFinalPass( &final_passes, ir_context.get(), &transformation_context, &fuzzer_context, transformation_sequence_out); - MaybeAddPass( - &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( + MaybeAddFinalPass( &final_passes, ir_context.get(), &transformation_context, &fuzzer_context, transformation_sequence_out); for (auto& pass : final_passes) { @@ -420,5 +457,38 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run( return Fuzzer::FuzzerResultStatus::kComplete; } +bool Fuzzer::ShouldContinueFuzzing( + const protobufs::TransformationSequence& transformation_sequence_out, + FuzzerContext* fuzzer_context) { + // There's a risk that fuzzing could get stuck, if none of the enabled fuzzer + // passes are able to apply any transformations. To guard against this we + // count the number of times some repeated pass has been applied and ensure + // that fuzzing stops if the number of repeated passes hits the limit on the + // number of transformations that can be applied. + assert( + num_repeated_passes_applied_ <= kTransformationLimit && + "The number of repeated passes applied must not exceed its upper limit."); + if (num_repeated_passes_applied_ == kTransformationLimit) { + // Stop because fuzzing has got stuck. + return false; + } + auto transformations_applied_so_far = + static_cast(transformation_sequence_out.transformation_size()); + if (transformations_applied_so_far >= kTransformationLimit) { + // Stop because we have reached the transformation limit. + return false; + } + auto chance_of_continuing = static_cast( + 100.0 * (1.0 - (static_cast(transformations_applied_so_far) / + static_cast(kTransformationLimit)))); + if (!fuzzer_context->ChoosePercentage(chance_of_continuing)) { + // We have probabilistically decided to stop. + return false; + } + // Continue fuzzing! + num_repeated_passes_applied_++; + return true; +} + } // namespace fuzz } // namespace spvtools diff --git a/source/fuzz/fuzzer.h b/source/fuzz/fuzzer.h index a437d580..01b2c68e 100644 --- a/source/fuzz/fuzzer.h +++ b/source/fuzz/fuzzer.h @@ -18,8 +18,11 @@ #include #include +#include "source/fuzz/fuzzer_context.h" #include "source/fuzz/fuzzer_pass.h" #include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/pass_management/repeated_pass_instances.h" +#include "source/fuzz/pass_management/repeated_pass_recommender.h" #include "source/fuzz/protobufs/spirvfuzz_protobufs.h" #include "spirv-tools/libspirv.hpp" @@ -38,11 +41,24 @@ class Fuzzer { kInitialBinaryInvalid, }; + // Each field of this enum corresponds to an available repeated pass + // strategy, and is used to decide which kind of RepeatedPassManager object + // to create. + enum class RepeatedPassStrategy { + kSimple, + kRandomWithRecommendations, + kLoopedWithRecommendations + }; + // Constructs a fuzzer from the given target environment |target_env|. |seed| - // is a seed for pseudo-random number generation. - // |validate_after_each_fuzzer_pass| controls whether the validator will be - // invoked after every fuzzer pass is applied. - Fuzzer(spv_target_env target_env, uint32_t seed, + // is a seed for pseudo-random number generation. If |enable_all_passes| is + // true then all fuzzer passes will be enabled, otherwise a random subset of + // fuzzer passes will be enabled. |validate_after_each_fuzzer_pass| controls + // whether the validator will be invoked after every fuzzer pass is applied, + // and |validator_options| provides the options that should be used during + // validation if so. + Fuzzer(spv_target_env target_env, uint32_t seed, bool enable_all_passes, + RepeatedPassStrategy repeated_pass_strategy, bool validate_after_each_fuzzer_pass, spv_validator_options validator_options); @@ -69,9 +85,43 @@ class Fuzzer { const protobufs::FactSequence& initial_facts, const std::vector& donor_suppliers, std::vector* binary_out, - protobufs::TransformationSequence* transformation_sequence_out) const; + protobufs::TransformationSequence* transformation_sequence_out); private: + // A convenience method to add a repeated fuzzer pass to |pass_instances| with + // probability 0.5, or with probability 1 if |enable_all_passes_| is true. + // + // All fuzzer passes take |ir_context|, |transformation_context|, + // |fuzzer_context| and |transformation_sequence_out| as parameters. Extra + // arguments can be provided via |extra_args|. + template + void MaybeAddRepeatedPass( + RepeatedPassInstances* pass_instances, opt::IRContext* ir_context, + TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformation_sequence_out, + Args&&... extra_args) const; + + // A convenience method to add a final fuzzer pass to |passes| with + // probability 0.5, or with probability 1 if |enable_all_passes_| is true. + // + // All fuzzer passes take |ir_context|, |transformation_context|, + // |fuzzer_context| and |transformation_sequence_out| as parameters. Extra + // arguments can be provided via |extra_args|. + template + void MaybeAddFinalPass( + std::vector>* passes, + opt::IRContext* ir_context, TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformation_sequence_out, + Args&&... extra_args) const; + + // Decides whether to apply more repeated passes. The probability decreases as + // the number of transformations that have been applied increases. + bool ShouldContinueFuzzing( + const protobufs::TransformationSequence& transformation_sequence_out, + FuzzerContext* fuzzer_context); + // Applies |pass|, which must be a pass constructed with |ir_context|, and // then returns true if and only if |ir_context| is valid. |tools| is used to // check validity. @@ -88,11 +138,23 @@ class Fuzzer { // Seed for random number generator. const uint32_t seed_; + // Determines whether all passes should be enabled, vs. having passes be + // probabilistically enabled. + bool enable_all_passes_; + + // Controls which type of RepeatedPassManager object to create. + RepeatedPassStrategy repeated_pass_strategy_; + // Determines whether the validator should be invoked after every fuzzer pass. bool validate_after_each_fuzzer_pass_; // Options to control validation. spv_validator_options validator_options_; + + // The number of repeated fuzzer passes that have been applied is kept track + // of, in order to enforce a hard limit on the number of times such passes + // can be applied. + uint32_t num_repeated_passes_applied_; }; } // namespace fuzz diff --git a/source/fuzz/fuzzer_context.cpp b/source/fuzz/fuzzer_context.cpp index 47d4a8b5..416ac711 100644 --- a/source/fuzz/fuzzer_context.cpp +++ b/source/fuzz/fuzzer_context.cpp @@ -23,7 +23,11 @@ namespace { // Default pairs of probabilities for applying various // transformations. All values are percentages. Keep them in alphabetical order. +const std::pair + kChanceOfAcceptingRepeatedPassRecommendation = {70, 100}; const std::pair kChanceOfAddingAccessChain = {5, 50}; +const std::pair kChanceOfAddingAnotherPassToPassLoop = {85, + 95}; const std::pair kChanceOfAddingAnotherStructField = {20, 90}; const std::pair kChanceOfAddingArrayOrStructType = {20, 90}; @@ -168,8 +172,12 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator, kGetDefaultMaxNumberOfParametersReplacedWithStruct), go_deeper_in_constant_obfuscation_( kDefaultGoDeeperInConstantObfuscation) { + chance_of_accepting_repeated_pass_recommendation_ = + ChooseBetweenMinAndMax(kChanceOfAcceptingRepeatedPassRecommendation); chance_of_adding_access_chain_ = ChooseBetweenMinAndMax(kChanceOfAddingAccessChain); + chance_of_adding_another_pass_to_pass_loop_ = + ChooseBetweenMinAndMax(kChanceOfAddingAnotherPassToPassLoop); chance_of_adding_another_struct_field_ = ChooseBetweenMinAndMax(kChanceOfAddingAnotherStructField); chance_of_adding_array_or_struct_type_ = diff --git a/source/fuzz/fuzzer_context.h b/source/fuzz/fuzzer_context.h index 28bb5755..79042e7a 100644 --- a/source/fuzz/fuzzer_context.h +++ b/source/fuzz/fuzzer_context.h @@ -106,9 +106,15 @@ class FuzzerContext { // Probabilities associated with applying various transformations. // Keep them in alphabetical order. + uint32_t GetChanceOfAcceptingRepeatedPassRecommendation() { + return chance_of_accepting_repeated_pass_recommendation_; + } uint32_t GetChanceOfAddingAccessChain() { return chance_of_adding_access_chain_; } + uint32_t GetChanceOfAddingAnotherPassToPassLoop() { + return chance_of_adding_another_pass_to_pass_loop_; + } uint32_t GetChanceOfAddingAnotherStructField() { return chance_of_adding_another_struct_field_; } @@ -379,7 +385,9 @@ class FuzzerContext { // Probabilities associated with applying various transformations. // Keep them in alphabetical order. + uint32_t chance_of_accepting_repeated_pass_recommendation_; uint32_t chance_of_adding_access_chain_; + uint32_t chance_of_adding_another_pass_to_pass_loop_; uint32_t chance_of_adding_another_struct_field_; uint32_t chance_of_adding_array_or_struct_type_; uint32_t chance_of_adding_bit_instruction_synonym_; diff --git a/source/fuzz/fuzzer_pass_add_composite_inserts.cpp b/source/fuzz/fuzzer_pass_add_composite_inserts.cpp index 10744093..515407bc 100644 --- a/source/fuzz/fuzzer_pass_add_composite_inserts.cpp +++ b/source/fuzz/fuzzer_pass_add_composite_inserts.cpp @@ -81,8 +81,7 @@ void FuzzerPassAddCompositeInserts::Apply() { } // No components of the composite can be pointers. - // TODO: - // (https://github.com/KhronosGroup/SPIRV-Tools/issues/3658) + // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3658): // Structs can have components of pointer type. // FindOrCreateZeroConstant cannot be called on a // pointer. We ignore pointers for now. Consider adding diff --git a/source/fuzz/pass_management/repeated_pass_instances.h b/source/fuzz/pass_management/repeated_pass_instances.h new file mode 100644 index 00000000..fdcb0552 --- /dev/null +++ b/source/fuzz/pass_management/repeated_pass_instances.h @@ -0,0 +1,173 @@ +// 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_REPEATED_PASS_INSTANCES_H_ +#define SOURCE_FUZZ_REPEATED_PASS_INSTANCES_H_ + +#include "source/fuzz/fuzzer_pass_add_access_chains.h" +#include "source/fuzz/fuzzer_pass_add_bit_instruction_synonyms.h" +#include "source/fuzz/fuzzer_pass_add_composite_inserts.h" +#include "source/fuzz/fuzzer_pass_add_composite_types.h" +#include "source/fuzz/fuzzer_pass_add_copy_memory.h" +#include "source/fuzz/fuzzer_pass_add_dead_blocks.h" +#include "source/fuzz/fuzzer_pass_add_dead_breaks.h" +#include "source/fuzz/fuzzer_pass_add_dead_continues.h" +#include "source/fuzz/fuzzer_pass_add_equation_instructions.h" +#include "source/fuzz/fuzzer_pass_add_function_calls.h" +#include "source/fuzz/fuzzer_pass_add_global_variables.h" +#include "source/fuzz/fuzzer_pass_add_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_opphi_synonyms.h" +#include "source/fuzz/fuzzer_pass_add_parameters.h" +#include "source/fuzz/fuzzer_pass_add_relaxed_decorations.h" +#include "source/fuzz/fuzzer_pass_add_stores.h" +#include "source/fuzz/fuzzer_pass_add_synonyms.h" +#include "source/fuzz/fuzzer_pass_add_vector_shuffle_instructions.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_duplicate_regions_with_selections.h" +#include "source/fuzz/fuzzer_pass_flatten_conditional_branches.h" +#include "source/fuzz/fuzzer_pass_inline_functions.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_mutate_pointers.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_propagate_instructions_up.h" +#include "source/fuzz/fuzzer_pass_push_ids_through_variables.h" +#include "source/fuzz/fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.h" +#include "source/fuzz/fuzzer_pass_replace_copy_memories_with_loads_stores.h" +#include "source/fuzz/fuzzer_pass_replace_copy_objects_with_stores_loads.h" +#include "source/fuzz/fuzzer_pass_replace_irrelevant_ids.h" +#include "source/fuzz/fuzzer_pass_replace_linear_algebra_instructions.h" +#include "source/fuzz/fuzzer_pass_replace_loads_stores_with_copy_memories.h" +#include "source/fuzz/fuzzer_pass_replace_opphi_ids_from_dead_predecessors.h" +#include "source/fuzz/fuzzer_pass_replace_opselects_with_conditional_branches.h" +#include "source/fuzz/fuzzer_pass_replace_parameter_with_global.h" +#include "source/fuzz/fuzzer_pass_replace_params_with_struct.h" +#include "source/fuzz/fuzzer_pass_split_blocks.h" +#include "source/fuzz/fuzzer_pass_swap_conditional_branch_operands.h" + +namespace spvtools { +namespace fuzz { + +// This class has a distinct member for each repeated fuzzer pass (i.e., a +// fuzzer pass that it makes sense to run multiple times). If a member is null +// then we do not have an instance of that fuzzer pass, i.e. it is disabled. +// The class also provides access to the set of passes that are enabled. +class RepeatedPassInstances { +// This macro should be invoked below for every repeated fuzzer pass. If a +// repeated fuzzer pass is called FuzzerPassFoo then the macro invocation: +// +// REPEATED_PASS_INSTANCE(Foo); +// +// should be used. This adds a private member of type FuzzerPassFoo*, and +// provides the following public methods: +// +// // Requires that SetPass has not been called previously with FuzzerPassFoo. +// // Adds |pass| to the set of known pass instances. +// void SetPass(std::unique_ptr pass); +// +// // Returns a pointer to a pass instance of type FuzzerPassFoo that was +// // previously registered via SetPass(), or nullptr if no such instance was +// // registered +// FuzzerPassFoo* GetFoo(); +#define REPEATED_PASS_INSTANCE(NAME) \ + public: \ + FuzzerPass##NAME* Get##NAME() const { return NAME##_; } \ + void SetPass(std::unique_ptr pass) { \ + assert(NAME##_ == nullptr && "Attempt to set pass multiple times."); \ + NAME##_ = pass.get(); \ + passes_.push_back(std::move(pass)); \ + } \ + \ + private: \ + FuzzerPass##NAME* NAME##_ = nullptr + + REPEATED_PASS_INSTANCE(AddAccessChains); + REPEATED_PASS_INSTANCE(AddBitInstructionSynonyms); + REPEATED_PASS_INSTANCE(AddCompositeInserts); + REPEATED_PASS_INSTANCE(AddCompositeTypes); + REPEATED_PASS_INSTANCE(AddCopyMemory); + REPEATED_PASS_INSTANCE(AddDeadBlocks); + REPEATED_PASS_INSTANCE(AddDeadBreaks); + REPEATED_PASS_INSTANCE(AddDeadContinues); + REPEATED_PASS_INSTANCE(AddEquationInstructions); + REPEATED_PASS_INSTANCE(AddFunctionCalls); + REPEATED_PASS_INSTANCE(AddGlobalVariables); + REPEATED_PASS_INSTANCE(AddImageSampleUnusedComponents); + REPEATED_PASS_INSTANCE(AddLoads); + REPEATED_PASS_INSTANCE(AddLocalVariables); + REPEATED_PASS_INSTANCE(AddLoopPreheaders); + REPEATED_PASS_INSTANCE(AddOpPhiSynonyms); + REPEATED_PASS_INSTANCE(AddParameters); + REPEATED_PASS_INSTANCE(AddRelaxedDecorations); + REPEATED_PASS_INSTANCE(AddStores); + REPEATED_PASS_INSTANCE(AddSynonyms); + REPEATED_PASS_INSTANCE(AddVectorShuffleInstructions); + REPEATED_PASS_INSTANCE(ApplyIdSynonyms); + REPEATED_PASS_INSTANCE(ConstructComposites); + REPEATED_PASS_INSTANCE(CopyObjects); + REPEATED_PASS_INSTANCE(DonateModules); + REPEATED_PASS_INSTANCE(DuplicateRegionsWithSelections); + REPEATED_PASS_INSTANCE(FlattenConditionalBranches); + REPEATED_PASS_INSTANCE(InlineFunctions); + REPEATED_PASS_INSTANCE(InvertComparisonOperators); + REPEATED_PASS_INSTANCE(MakeVectorOperationsDynamic); + REPEATED_PASS_INSTANCE(MergeBlocks); + REPEATED_PASS_INSTANCE(MutatePointers); + REPEATED_PASS_INSTANCE(ObfuscateConstants); + REPEATED_PASS_INSTANCE(OutlineFunctions); + REPEATED_PASS_INSTANCE(PermuteBlocks); + REPEATED_PASS_INSTANCE(PermuteFunctionParameters); + REPEATED_PASS_INSTANCE(PermuteInstructions); + REPEATED_PASS_INSTANCE(PropagateInstructionsUp); + REPEATED_PASS_INSTANCE(PushIdsThroughVariables); + REPEATED_PASS_INSTANCE(ReplaceAddsSubsMulsWithCarryingExtended); + REPEATED_PASS_INSTANCE(ReplaceCopyMemoriesWithLoadsStores); + REPEATED_PASS_INSTANCE(ReplaceCopyObjectsWithStoresLoads); + REPEATED_PASS_INSTANCE(ReplaceLoadsStoresWithCopyMemories); + REPEATED_PASS_INSTANCE(ReplaceIrrelevantIds); + REPEATED_PASS_INSTANCE(ReplaceOpPhiIdsFromDeadPredecessors); + REPEATED_PASS_INSTANCE(ReplaceOpSelectsWithConditionalBranches); + REPEATED_PASS_INSTANCE(ReplaceParameterWithGlobal); + REPEATED_PASS_INSTANCE(ReplaceLinearAlgebraInstructions); + REPEATED_PASS_INSTANCE(ReplaceParamsWithStruct); + REPEATED_PASS_INSTANCE(SplitBlocks); + REPEATED_PASS_INSTANCE(SwapBranchConditionalOperands); +#undef REPEATED_PASS_INSTANCE + + public: + // Yields the sequence of fuzzer pass instances that have been registered. + const std::vector>& GetPasses() const { + return passes_; + } + + private: + // The distinct fuzzer pass instances that have been registered via SetPass(). + std::vector> passes_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_REPEATED_PASS_INSTANCES_H_ diff --git a/source/fuzz/pass_management/repeated_pass_manager.cpp b/source/fuzz/pass_management/repeated_pass_manager.cpp new file mode 100644 index 00000000..032f2645 --- /dev/null +++ b/source/fuzz/pass_management/repeated_pass_manager.cpp @@ -0,0 +1,27 @@ +// 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/pass_management/repeated_pass_manager.h" + +namespace spvtools { +namespace fuzz { + +RepeatedPassManager::RepeatedPassManager(FuzzerContext* fuzzer_context, + RepeatedPassInstances* pass_instances) + : fuzzer_context_(fuzzer_context), pass_instances_(pass_instances) {} + +RepeatedPassManager::~RepeatedPassManager() = default; + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/pass_management/repeated_pass_manager.h b/source/fuzz/pass_management/repeated_pass_manager.h new file mode 100644 index 00000000..29b5fcc9 --- /dev/null +++ b/source/fuzz/pass_management/repeated_pass_manager.h @@ -0,0 +1,55 @@ +// 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_REPEATED_PASS_MANAGER_H_ +#define SOURCE_FUZZ_REPEATED_PASS_MANAGER_H_ + +#include "source/fuzz/fuzzer_context.h" +#include "source/fuzz/fuzzer_pass.h" +#include "source/fuzz/pass_management/repeated_pass_instances.h" + +namespace spvtools { +namespace fuzz { + +// An interface to encapsulate the manner in which the sequence of repeated +// passes that are applied during fuzzing is chosen. An implementation of this +// interface could, for example, keep track of the history of passes that have +// been run and bias the selection of future passes according to this history. +class RepeatedPassManager { + public: + RepeatedPassManager(FuzzerContext* fuzzer_context, + RepeatedPassInstances* pass_instances); + + virtual ~RepeatedPassManager(); + + // Returns the fuzzer pass instance that should be run next. + virtual FuzzerPass* ChoosePass() = 0; + + protected: + FuzzerContext* GetFuzzerContext() { return fuzzer_context_; } + + RepeatedPassInstances* GetPassInstances() { return pass_instances_; } + + private: + // Provided in order to allow the pass manager to make random decisions. + FuzzerContext* fuzzer_context_; + + // The repeated fuzzer passes that are enabled. + RepeatedPassInstances* pass_instances_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_REPEATED_PASS_MANAGER_H_ diff --git a/source/fuzz/pass_management/repeated_pass_manager_looped_with_recommendations.cpp b/source/fuzz/pass_management/repeated_pass_manager_looped_with_recommendations.cpp new file mode 100644 index 00000000..e8bd5458 --- /dev/null +++ b/source/fuzz/pass_management/repeated_pass_manager_looped_with_recommendations.cpp @@ -0,0 +1,49 @@ +// 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/pass_management/repeated_pass_manager_looped_with_recommendations.h" + +namespace spvtools { +namespace fuzz { + +RepeatedPassManagerLoopedWithRecommendations:: + RepeatedPassManagerLoopedWithRecommendations( + FuzzerContext* fuzzer_context, RepeatedPassInstances* pass_instances, + RepeatedPassRecommender* pass_recommender) + : RepeatedPassManager(fuzzer_context, pass_instances), next_pass_index_(0) { + auto& passes = GetPassInstances()->GetPasses(); + do { + FuzzerPass* current_pass = + passes[GetFuzzerContext()->RandomIndex(passes)].get(); + pass_loop_.push_back(current_pass); + for (auto future_pass : + pass_recommender->GetFuturePassRecommendations(*current_pass)) { + pass_loop_.push_back(future_pass); + } + } while (fuzzer_context->ChoosePercentage( + fuzzer_context->GetChanceOfAddingAnotherPassToPassLoop())); +} + +RepeatedPassManagerLoopedWithRecommendations:: + ~RepeatedPassManagerLoopedWithRecommendations() = default; + +FuzzerPass* RepeatedPassManagerLoopedWithRecommendations::ChoosePass() { + auto result = pass_loop_[next_pass_index_]; + next_pass_index_ = + (next_pass_index_ + 1) % static_cast(pass_loop_.size()); + return result; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/pass_management/repeated_pass_manager_looped_with_recommendations.h b/source/fuzz/pass_management/repeated_pass_manager_looped_with_recommendations.h new file mode 100644 index 00000000..25cef061 --- /dev/null +++ b/source/fuzz/pass_management/repeated_pass_manager_looped_with_recommendations.h @@ -0,0 +1,58 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_REPEATED_PASS_MANAGER_LOOPED_WITH_RECOMMENDATIONS_H_ +#define SOURCE_FUZZ_REPEATED_PASS_MANAGER_LOOPED_WITH_RECOMMENDATIONS_H_ + +#include + +#include "source/fuzz/pass_management/repeated_pass_manager.h" +#include "source/fuzz/pass_management/repeated_pass_recommender.h" + +namespace spvtools { +namespace fuzz { + +// On construction, this pass manager creates a sequence of fuzzer passes which +// is not changed thereafter. Passes from this sequence are served up in round +// robin fashion each time ChoosePass is invoked - i.e., the sequence is a "pass +// loop". +// +// The pass loop is constructed by repeatedly: +// - Randomly adding an enabled pass +// - Adding all recommended follow-on passes for this pass +// and probabilistically terminating this process. +class RepeatedPassManagerLoopedWithRecommendations + : public RepeatedPassManager { + public: + RepeatedPassManagerLoopedWithRecommendations( + FuzzerContext* fuzzer_context, RepeatedPassInstances* pass_instances, + RepeatedPassRecommender* pass_recommender); + + ~RepeatedPassManagerLoopedWithRecommendations() override; + + FuzzerPass* ChoosePass() override; + + private: + // The loop of fuzzer passes to be applied, populated on construction. + std::vector pass_loop_; + + // An index into |pass_loop_| specifying which pass should be served up next + // time ChoosePass is invoked. + uint32_t next_pass_index_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_REPEATED_PASS_MANAGER_LOOPED_WITH_RECOMMENDATIONS_H_ diff --git a/source/fuzz/pass_management/repeated_pass_manager_random_with_recommendations.cpp b/source/fuzz/pass_management/repeated_pass_manager_random_with_recommendations.cpp new file mode 100644 index 00000000..48b1e6a1 --- /dev/null +++ b/source/fuzz/pass_management/repeated_pass_manager_random_with_recommendations.cpp @@ -0,0 +1,47 @@ +// 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/pass_management/repeated_pass_manager_random_with_recommendations.h" + +namespace spvtools { +namespace fuzz { + +RepeatedPassManagerRandomWithRecommendations:: + RepeatedPassManagerRandomWithRecommendations( + FuzzerContext* fuzzer_context, RepeatedPassInstances* pass_instances, + RepeatedPassRecommender* pass_recommender) + : RepeatedPassManager(fuzzer_context, pass_instances), + pass_recommender_(pass_recommender) {} + +RepeatedPassManagerRandomWithRecommendations:: + ~RepeatedPassManagerRandomWithRecommendations() = default; + +FuzzerPass* RepeatedPassManagerRandomWithRecommendations::ChoosePass() { + FuzzerPass* result; + if (recommended_passes_.empty() || GetFuzzerContext()->ChooseEven()) { + auto& passes = GetPassInstances()->GetPasses(); + result = passes[GetFuzzerContext()->RandomIndex(passes)].get(); + } else { + result = recommended_passes_.front(); + recommended_passes_.pop_front(); + } + for (auto future_pass : + pass_recommender_->GetFuturePassRecommendations(*result)) { + recommended_passes_.push_back(future_pass); + } + return result; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/pass_management/repeated_pass_manager_random_with_recommendations.h b/source/fuzz/pass_management/repeated_pass_manager_random_with_recommendations.h new file mode 100644 index 00000000..acd207b8 --- /dev/null +++ b/source/fuzz/pass_management/repeated_pass_manager_random_with_recommendations.h @@ -0,0 +1,59 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_REPEATED_PASS_MANAGER_RANDOM_WITH_RECOMMENDATIONS_H_ +#define SOURCE_FUZZ_REPEATED_PASS_MANAGER_RANDOM_WITH_RECOMMENDATIONS_H_ + +#include + +#include "source/fuzz/pass_management/repeated_pass_manager.h" +#include "source/fuzz/pass_management/repeated_pass_recommender.h" + +namespace spvtools { +namespace fuzz { + +// This repeated pass manager uses a pass recommender to recommend future passes +// each time a fuzzer pass is run. It keeps a queue of recommended passes. +// +// Each time a fuzzer pass is requested, the manager either selects an enabled +// fuzzer pass at random, or selects the pass at the front of the recommendation +// queue, removing it from the queue. The decision of which of these pass +// selection methods to use is made randomly each time ChoosePass is called. +// +// Either way, recommended follow-on passes for the chosen pass are added to +// the recommendation queue. +class RepeatedPassManagerRandomWithRecommendations + : public RepeatedPassManager { + public: + RepeatedPassManagerRandomWithRecommendations( + FuzzerContext* fuzzer_context, RepeatedPassInstances* pass_instances, + RepeatedPassRecommender* pass_recommender); + + ~RepeatedPassManagerRandomWithRecommendations() override; + + FuzzerPass* ChoosePass() override; + + private: + // The queue of passes that have been recommended based on previously-chosen + // passes. + std::deque recommended_passes_; + + // Used to recommend future passes. + RepeatedPassRecommender* pass_recommender_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_REPEATED_PASS_MANAGER_RANDOM_WITH_RECOMMENDATIONS_H_ diff --git a/source/fuzz/pass_management/repeated_pass_manager_simple.cpp b/source/fuzz/pass_management/repeated_pass_manager_simple.cpp new file mode 100644 index 00000000..a85a7321 --- /dev/null +++ b/source/fuzz/pass_management/repeated_pass_manager_simple.cpp @@ -0,0 +1,32 @@ +// 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/pass_management/repeated_pass_manager_simple.h" + +namespace spvtools { +namespace fuzz { + +RepeatedPassManagerSimple::RepeatedPassManagerSimple( + FuzzerContext* fuzzer_context, RepeatedPassInstances* pass_instances) + : RepeatedPassManager(fuzzer_context, pass_instances) {} + +RepeatedPassManagerSimple::~RepeatedPassManagerSimple() = default; + +FuzzerPass* RepeatedPassManagerSimple::ChoosePass() { + auto& passes = GetPassInstances()->GetPasses(); + return passes[GetFuzzerContext()->RandomIndex(passes)].get(); +} + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/pass_management/repeated_pass_manager_simple.h b/source/fuzz/pass_management/repeated_pass_manager_simple.h new file mode 100644 index 00000000..548f77b6 --- /dev/null +++ b/source/fuzz/pass_management/repeated_pass_manager_simple.h @@ -0,0 +1,38 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_REPEATED_PASS_MANAGER_SIMPLE_H_ +#define SOURCE_FUZZ_REPEATED_PASS_MANAGER_SIMPLE_H_ + +#include "source/fuzz/pass_management/repeated_pass_manager.h" + +namespace spvtools { +namespace fuzz { + +// Selects the next pass to run uniformly at random from the enabled repeated +// passes. Recommendations are not used. +class RepeatedPassManagerSimple : public RepeatedPassManager { + public: + RepeatedPassManagerSimple(FuzzerContext* fuzzer_context, + RepeatedPassInstances* pass_instances); + + ~RepeatedPassManagerSimple() override; + + FuzzerPass* ChoosePass() override; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_REPEATED_PASS_MANAGER_SIMPLE_H_ diff --git a/source/fuzz/pass_management/repeated_pass_recommender.cpp b/source/fuzz/pass_management/repeated_pass_recommender.cpp new file mode 100644 index 00000000..c7789dc0 --- /dev/null +++ b/source/fuzz/pass_management/repeated_pass_recommender.cpp @@ -0,0 +1,23 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/pass_management/repeated_pass_recommender.h" + +namespace spvtools { +namespace fuzz { + +RepeatedPassRecommender::~RepeatedPassRecommender() = default; + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/pass_management/repeated_pass_recommender.h b/source/fuzz/pass_management/repeated_pass_recommender.h new file mode 100644 index 00000000..a6b13385 --- /dev/null +++ b/source/fuzz/pass_management/repeated_pass_recommender.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 SOURCE_FUZZ_REPEATED_PASS_RECOMMENDER_H_ +#define SOURCE_FUZZ_REPEATED_PASS_RECOMMENDER_H_ + +#include + +#include "source/fuzz/fuzzer_pass.h" + +namespace spvtools { +namespace fuzz { + +// Interface for influencing interactions between repeated fuzzer passes, by +// allowing hints as to which passes are recommended to be run after one +// another. +class RepeatedPassRecommender { + public: + virtual ~RepeatedPassRecommender(); + + // Given a reference to a repeated pass, |pass|, returns a sequence of + // repeated pass instances that might be worth running soon after having + // run |pass|. + virtual std::vector GetFuturePassRecommendations( + const FuzzerPass& pass) = 0; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_REPEATED_PASS_RECOMMENDER_H_ diff --git a/source/fuzz/pass_management/repeated_pass_recommender_standard.cpp b/source/fuzz/pass_management/repeated_pass_recommender_standard.cpp new file mode 100644 index 00000000..253504bb --- /dev/null +++ b/source/fuzz/pass_management/repeated_pass_recommender_standard.cpp @@ -0,0 +1,330 @@ +// 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/pass_management/repeated_pass_recommender_standard.h" + +#include + +namespace spvtools { +namespace fuzz { + +RepeatedPassRecommenderStandard::RepeatedPassRecommenderStandard( + RepeatedPassInstances* pass_instances, FuzzerContext* fuzzer_context) + : pass_instances_(pass_instances), fuzzer_context_(fuzzer_context) {} + +RepeatedPassRecommenderStandard::~RepeatedPassRecommenderStandard() = default; + +std::vector +RepeatedPassRecommenderStandard::GetFuturePassRecommendations( + const FuzzerPass& pass) { + if (&pass == pass_instances_->GetAddAccessChains()) { + // - Adding access chains means there is more scope for loading and storing + // - It could be worth making more access chains from the recently-added + // access chains + return RandomOrderAndNonNull({pass_instances_->GetAddLoads(), + pass_instances_->GetAddStores(), + pass_instances_->GetAddAccessChains()}); + } + if (&pass == pass_instances_->GetAddBitInstructionSynonyms()) { + // - Adding bit instruction synonyms creates opportunities to apply synonyms + return RandomOrderAndNonNull({pass_instances_->GetApplyIdSynonyms()}); + } + if (&pass == pass_instances_->GetAddCompositeInserts()) { + // - Having added inserts we will have more vectors, so there is scope for + // vector shuffling + // - Adding inserts creates synonyms, which we should try to use + // - Vector inserts can be made dynamic + return RandomOrderAndNonNull( + {pass_instances_->GetAddVectorShuffleInstructions(), + pass_instances_->GetApplyIdSynonyms(), + pass_instances_->GetMakeVectorOperationsDynamic()}); + } + if (&pass == pass_instances_->GetAddCompositeTypes()) { + // - More composite types gives more scope for constructing composites + return RandomOrderAndNonNull({pass_instances_->GetConstructComposites()}); + } + if (&pass == pass_instances_->GetAddCopyMemory()) { + // - Recently-added copy memories could be replace with load-store pairs + return RandomOrderAndNonNull( + {pass_instances_->GetReplaceCopyMemoriesWithLoadsStores()}); + } + if (&pass == pass_instances_->GetAddDeadBlocks()) { + // - Dead blocks are great for adding function calls + // - Dead blocks are also great for adding loads and stores + // - The guard associated with a dead block can be obfuscated + return RandomOrderAndNonNull({pass_instances_->GetAddFunctionCalls(), + pass_instances_->GetAddLoads(), + pass_instances_->GetAddStores(), + pass_instances_->GetObfuscateConstants()}); + } + if (&pass == pass_instances_->GetAddDeadBreaks()) { + // - The guard of the dead break is a good candidate for obfuscation + return RandomOrderAndNonNull({pass_instances_->GetObfuscateConstants()}); + } + if (&pass == pass_instances_->GetAddDeadContinues()) { + // - The guard of the dead continue is a good candidate for obfuscation + return RandomOrderAndNonNull({pass_instances_->GetObfuscateConstants()}); + } + if (&pass == pass_instances_->GetAddEquationInstructions()) { + // - Equation instructions can create synonyms, which we can apply + // - Equation instructions collaborate with one another to make synonyms, so + // having added some it is worth adding more + return RandomOrderAndNonNull( + {pass_instances_->GetApplyIdSynonyms(), + pass_instances_->GetAddEquationInstructions()}); + } + if (&pass == pass_instances_->GetAddFunctionCalls()) { + // - Called functions can be inlined + // - Irrelevant ids are created, so they can be replaced + return RandomOrderAndNonNull({pass_instances_->GetInlineFunctions(), + pass_instances_->GetReplaceIrrelevantIds()}); + } + if (&pass == pass_instances_->GetAddGlobalVariables()) { + // - New globals provide new possibilities for making access chains + // - We can load from and store to new globals + return RandomOrderAndNonNull({pass_instances_->GetAddAccessChains(), + pass_instances_->GetAddLoads(), + pass_instances_->GetAddStores()}); + } + if (&pass == pass_instances_->GetAddImageSampleUnusedComponents()) { + // - This introduces an unused component whose id is irrelevant and can be + // replaced + return RandomOrderAndNonNull({pass_instances_->GetReplaceIrrelevantIds()}); + } + if (&pass == pass_instances_->GetAddLoads()) { + // - Loads might end up with corresponding stores, so that pairs can be + // replaced with memory copies + return RandomOrderAndNonNull( + {pass_instances_->GetReplaceLoadsStoresWithCopyMemories()}); + } + if (&pass == pass_instances_->GetAddLocalVariables()) { + // - New locals provide new possibilities for making access chains + // - We can load from and store to new locals + return RandomOrderAndNonNull({pass_instances_->GetAddAccessChains(), + pass_instances_->GetAddLoads(), + pass_instances_->GetAddStores()}); + } + if (&pass == pass_instances_->GetAddLoopPreheaders()) { + // - The loop preheader provides more scope for duplicating regions and + // outlining functions. + return RandomOrderAndNonNull( + {pass_instances_->GetDuplicateRegionsWithSelections(), + pass_instances_->GetOutlineFunctions()}); + } + if (&pass == pass_instances_->GetAddOpPhiSynonyms()) { + // - New synonyms can be applied + // - If OpPhi synonyms are introduced for blocks with dead predecessors, the + // values consumed from dead predecessors can be replaced + return RandomOrderAndNonNull( + {pass_instances_->GetApplyIdSynonyms(), + pass_instances_->GetReplaceOpPhiIdsFromDeadPredecessors()}); + } + if (&pass == pass_instances_->GetAddParameters()) { + // - We might be able to create interesting synonyms of new parameters. + // - This introduces irrelevant ids, which can be replaced + return RandomOrderAndNonNull({pass_instances_->GetAddSynonyms(), + pass_instances_->GetReplaceIrrelevantIds()}); + } + if (&pass == pass_instances_->GetAddRelaxedDecorations()) { + // - No obvious follow-on passes + return {}; + } + if (&pass == pass_instances_->GetAddStores()) { + // - Stores might end up with corresponding loads, so that pairs can be + // replaced with memory copies + return RandomOrderAndNonNull( + {pass_instances_->GetReplaceLoadsStoresWithCopyMemories()}); + } + if (&pass == pass_instances_->GetAddSynonyms()) { + // - New synonyms can be applied + // - Synonym instructions use constants, which can be obfuscated + // - Synonym instructions use irrelevant ids, which can be replaced + // - Synonym instructions introduce addition/subtraction, which can be + // replaced with carrying/extended versions + return RandomOrderAndNonNull( + {pass_instances_->GetApplyIdSynonyms(), + pass_instances_->GetObfuscateConstants(), + pass_instances_->GetReplaceAddsSubsMulsWithCarryingExtended(), + pass_instances_->GetReplaceIrrelevantIds()}); + } + if (&pass == pass_instances_->GetAddVectorShuffleInstructions()) { + // - Vector shuffles create synonyms that can be applied + // - TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3806) Extract + // from composites. + return RandomOrderAndNonNull({pass_instances_->GetApplyIdSynonyms()}); + } + if (&pass == pass_instances_->GetApplyIdSynonyms()) { + // - No obvious follow-on passes + return {}; + } + if (&pass == pass_instances_->GetConstructComposites()) { + // - TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3806): Extract + // from composites. + return RandomOrderAndNonNull({}); + } + if (&pass == pass_instances_->GetCopyObjects()) { + // - Object copies create synonyms that can be applied + // - OpCopyObject can be replaced with a store/load pair + return RandomOrderAndNonNull( + {pass_instances_->GetApplyIdSynonyms(), + pass_instances_->GetReplaceCopyObjectsWithStoresLoads()}); + } + if (&pass == pass_instances_->GetDonateModules()) { + // - New functions in the module can be called + // - Donated dead functions produce irrelevant ids, which can be replaced + return RandomOrderAndNonNull({pass_instances_->GetAddFunctionCalls(), + pass_instances_->GetReplaceIrrelevantIds()}); + } + if (&pass == pass_instances_->GetDuplicateRegionsWithSelections()) { + // - Parts of duplicated regions can be outlined + return RandomOrderAndNonNull({pass_instances_->GetOutlineFunctions()}); + } + if (&pass == pass_instances_->GetFlattenConditionalBranches()) { + // - Parts of flattened selections can be outlined + // - The flattening transformation introduces constants and irrelevant ids + // for enclosing hard-to-flatten operations; these can be obfuscated or + // replaced + return RandomOrderAndNonNull({pass_instances_->GetObfuscateConstants(), + pass_instances_->GetOutlineFunctions(), + pass_instances_->GetReplaceIrrelevantIds()}); + } + if (&pass == pass_instances_->GetInlineFunctions()) { + // - Parts of inlined functions can be outlined again + return RandomOrderAndNonNull({pass_instances_->GetOutlineFunctions()}); + } + if (&pass == pass_instances_->GetInvertComparisonOperators()) { + // - No obvious follow-on passes + return {}; + } + if (&pass == pass_instances_->GetMakeVectorOperationsDynamic()) { + // - No obvious follow-on passes + return {}; + } + if (&pass == pass_instances_->GetMergeBlocks()) { + // - Having merged some blocks it may be interesting to split them in a + // different way + return RandomOrderAndNonNull({pass_instances_->GetSplitBlocks()}); + } + if (&pass == pass_instances_->GetMutatePointers()) { + // - This creates irrelevant ids, which can be replaced + return RandomOrderAndNonNull({pass_instances_->GetReplaceIrrelevantIds()}); + } + if (&pass == pass_instances_->GetObfuscateConstants()) { + // - No obvious follow-on passes + return {}; + } + if (&pass == pass_instances_->GetOutlineFunctions()) { + // - This creates more functions, which can be called + // - Inlining the function for the region that was outlined might also be + // fruitful; it will be inlined in a different form + return RandomOrderAndNonNull({pass_instances_->GetAddFunctionCalls(), + pass_instances_->GetInlineFunctions()}); + } + if (&pass == pass_instances_->GetPermuteBlocks()) { + // No obvious follow-on passes + return {}; + } + if (&pass == pass_instances_->GetPermuteFunctionParameters()) { + // No obvious follow-on passes + return {}; + } + if (&pass == pass_instances_->GetPermuteInstructions()) { + // No obvious follow-on passes + return {}; + } + if (&pass == pass_instances_->GetPropagateInstructionsUp()) { + // No obvious follow-on passes + return {}; + } + if (&pass == pass_instances_->GetPushIdsThroughVariables()) { + // - This pass creates synonyms, so it is worth applying them + return RandomOrderAndNonNull({pass_instances_->GetApplyIdSynonyms()}); + } + if (&pass == pass_instances_->GetReplaceAddsSubsMulsWithCarryingExtended()) { + // No obvious follow-on passes + return {}; + } + if (&pass == pass_instances_->GetReplaceCopyMemoriesWithLoadsStores()) { + // No obvious follow-on passes + return {}; + } + if (&pass == pass_instances_->GetReplaceCopyObjectsWithStoresLoads()) { + // - We may end up with load/store pairs that could be used to create memory + // copies + return RandomOrderAndNonNull( + {pass_instances_->GetReplaceLoadsStoresWithCopyMemories()}); + } + if (&pass == pass_instances_->GetReplaceIrrelevantIds()) { + // No obvious follow-on passes + return {}; + } + if (&pass == pass_instances_->GetReplaceLinearAlgebraInstructions()) { + // No obvious follow-on passes + return {}; + } + if (&pass == pass_instances_->GetReplaceLoadsStoresWithCopyMemories()) { + // No obvious follow-on passes + return {}; + } + if (&pass == pass_instances_->GetReplaceOpPhiIdsFromDeadPredecessors()) { + // No obvious follow-on passes + return {}; + } + if (&pass == pass_instances_->GetReplaceOpSelectsWithConditionalBranches()) { + // No obvious follow-on passes + return {}; + } + if (&pass == pass_instances_->GetReplaceParameterWithGlobal()) { + // No obvious follow-on passes + return {}; + } + if (&pass == pass_instances_->GetReplaceParamsWithStruct()) { + // No obvious follow-on passes + return {}; + } + if (&pass == pass_instances_->GetSplitBlocks()) { + // - More blocks means more chances for adding dead breaks/continues, and + // for adding dead blocks + return RandomOrderAndNonNull({pass_instances_->GetAddDeadBreaks(), + pass_instances_->GetAddDeadContinues(), + pass_instances_->GetAddDeadBlocks()}); + } + if (&pass == pass_instances_->GetSwapBranchConditionalOperands()) { + // No obvious follow-on passes + return {}; + } + assert(false && "Unreachable: every fuzzer pass should be dealt with."); + return {}; +} + +std::vector RepeatedPassRecommenderStandard::RandomOrderAndNonNull( + const std::vector& passes) { + std::vector indices(passes.size()); + std::iota(indices.begin(), indices.end(), 0); + std::vector result; + while (!indices.empty()) { + FuzzerPass* maybe_pass = + passes[fuzzer_context_->RemoveAtRandomIndex(&indices)]; + if (maybe_pass != nullptr && + fuzzer_context_->ChoosePercentage( + fuzzer_context_ + ->GetChanceOfAcceptingRepeatedPassRecommendation())) { + result.push_back(maybe_pass); + } + } + return result; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/pass_management/repeated_pass_recommender_standard.h b/source/fuzz/pass_management/repeated_pass_recommender_standard.h new file mode 100644 index 00000000..293b8e0f --- /dev/null +++ b/source/fuzz/pass_management/repeated_pass_recommender_standard.h @@ -0,0 +1,50 @@ +// 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_REPEATED_PASS_RECOMMENDER_STANDARD_H_ +#define SOURCE_FUZZ_REPEATED_PASS_RECOMMENDER_STANDARD_H_ + +#include "source/fuzz/fuzzer_context.h" +#include "source/fuzz/pass_management/repeated_pass_instances.h" +#include "source/fuzz/pass_management/repeated_pass_recommender.h" + +namespace spvtools { +namespace fuzz { + +// A manually-crafter recommender of repeated passes, designed based on +// knowledge of how the various fuzzer passes work and speculation as to how +// they might interact in interesting ways. +class RepeatedPassRecommenderStandard : public RepeatedPassRecommender { + public: + RepeatedPassRecommenderStandard(RepeatedPassInstances* pass_instances, + FuzzerContext* fuzzer_context); + + ~RepeatedPassRecommenderStandard(); + + std::vector GetFuturePassRecommendations( + const FuzzerPass& pass) override; + + private: + std::vector RandomOrderAndNonNull( + const std::vector& passes); + + RepeatedPassInstances* pass_instances_; + + FuzzerContext* fuzzer_context_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_REPEATED_PASS_RECOMMENDER_STANDARD_H_ diff --git a/source/fuzz/transformation_composite_insert.cpp b/source/fuzz/transformation_composite_insert.cpp index e57ffe8c..79ffd095 100644 --- a/source/fuzz/transformation_composite_insert.cpp +++ b/source/fuzz/transformation_composite_insert.cpp @@ -166,7 +166,7 @@ void TransformationCompositeInsert::Apply( continue; } current_index.push_back(i); - // TODO: (https://github.com/KhronosGroup/SPIRV-Tools/issues/3659) + // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3659): // Google C++ guide restricts the use of r-value references. // https://google.github.io/styleguide/cppguide.html#Rvalue_references // Consider changing the signature of MakeDataDescriptor() diff --git a/source/fuzz/transformation_duplicate_region_with_selection.cpp b/source/fuzz/transformation_duplicate_region_with_selection.cpp index 8c721303..8d099ae1 100644 --- a/source/fuzz/transformation_duplicate_region_with_selection.cpp +++ b/source/fuzz/transformation_duplicate_region_with_selection.cpp @@ -110,7 +110,7 @@ bool TransformationDuplicateRegionWithSelection::IsApplicable( return false; } - // TODO (https://github.com/KhronosGroup/SPIRV-Tools/issues/3785): + // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3785): // The following code has been copied from TransformationOutlineFunction. // Consider refactoring to avoid duplication. auto region_set = GetRegionBlocks(ir_context, entry_block, exit_block); @@ -555,7 +555,7 @@ void TransformationDuplicateRegionWithSelection::Apply( ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone); } -// TODO (https://github.com/KhronosGroup/SPIRV-Tools/issues/3785): +// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3785): // The following method has been copied from // TransformationOutlineFunction. Consider refactoring to avoid // duplication. diff --git a/source/spirv_fuzzer_options.cpp b/source/spirv_fuzzer_options.cpp index 64fefbc2..3f62e0e0 100644 --- a/source/spirv_fuzzer_options.cpp +++ b/source/spirv_fuzzer_options.cpp @@ -25,7 +25,8 @@ spv_fuzzer_options_t::spv_fuzzer_options_t() replay_range(0), replay_validation_enabled(false), shrinker_step_limit(kDefaultStepLimit), - fuzzer_pass_validation_enabled(false) {} + fuzzer_pass_validation_enabled(false), + all_passes_enabled(false) {} SPIRV_TOOLS_EXPORT spv_fuzzer_options spvFuzzerOptionsCreate() { return new spv_fuzzer_options_t(); @@ -60,3 +61,8 @@ SPIRV_TOOLS_EXPORT void spvFuzzerOptionsEnableFuzzerPassValidation( spv_fuzzer_options options) { options->fuzzer_pass_validation_enabled = true; } + +SPIRV_TOOLS_EXPORT void spvFuzzerOptionsEnableAllPasses( + spv_fuzzer_options options) { + options->all_passes_enabled = true; +} diff --git a/source/spirv_fuzzer_options.h b/source/spirv_fuzzer_options.h index 0db16a30..bb8d9103 100644 --- a/source/spirv_fuzzer_options.h +++ b/source/spirv_fuzzer_options.h @@ -40,6 +40,9 @@ struct spv_fuzzer_options_t { // See spvFuzzerOptionsValidateAfterEveryPass. bool fuzzer_pass_validation_enabled; + + // See spvFuzzerOptionsEnableAllPasses. + bool all_passes_enabled; }; #endif // SOURCE_SPIRV_FUZZER_OPTIONS_H_ diff --git a/test/fuzz/fuzzer_replayer_test.cpp b/test/fuzz/fuzzer_replayer_test.cpp index 615f1151..eb2b8179 100644 --- a/test/fuzz/fuzzer_replayer_test.cpp +++ b/test/fuzz/fuzzer_replayer_test.cpp @@ -1639,12 +1639,26 @@ void RunFuzzerAndReplayer(const std::string& shader, }); } + std::vector strategies{ + Fuzzer::RepeatedPassStrategy::kSimple, + Fuzzer::RepeatedPassStrategy::kLoopedWithRecommendations, + Fuzzer::RepeatedPassStrategy::kRandomWithRecommendations}; + uint32_t strategy_index = 0; for (uint32_t seed = initial_seed; seed < initial_seed + num_runs; seed++) { std::vector fuzzer_binary_out; protobufs::TransformationSequence fuzzer_transformation_sequence_out; spvtools::ValidatorOptions validator_options; - Fuzzer fuzzer(env, seed, true, validator_options); + // Every 4th time we run the fuzzer, enable all fuzzer passes. + bool enable_all_passes = (seed % 4) == 0; + Fuzzer fuzzer(env, seed, enable_all_passes, strategies[strategy_index], + true, validator_options); + + // Cycle the repeated pass strategy so that we try a different one next time + // we run the fuzzer. + strategy_index = + (strategy_index + 1) % static_cast(strategies.size()); + fuzzer.SetMessageConsumer(kConsoleMessageConsumer); auto fuzzer_result_status = fuzzer.Run(binary_in, initial_facts, donor_suppliers, diff --git a/test/fuzz/fuzzer_shrinker_test.cpp b/test/fuzz/fuzzer_shrinker_test.cpp index 709e9cef..c9ae42aa 100644 --- a/test/fuzz/fuzzer_shrinker_test.cpp +++ b/test/fuzz/fuzzer_shrinker_test.cpp @@ -1040,7 +1040,23 @@ void RunFuzzerAndShrinker(const std::string& shader, std::vector fuzzer_binary_out; protobufs::TransformationSequence fuzzer_transformation_sequence_out; spvtools::ValidatorOptions validator_options; - Fuzzer fuzzer(env, seed, true, validator_options); + + // Depending on the seed, decide whether to enable all passes and which + // repeated pass manager to use. + bool enable_all_passes = (seed % 4) == 0; + Fuzzer::RepeatedPassStrategy repeated_pass_strategy; + if ((seed % 3) == 0) { + repeated_pass_strategy = Fuzzer::RepeatedPassStrategy::kSimple; + } else if ((seed % 3) == 1) { + repeated_pass_strategy = + Fuzzer::RepeatedPassStrategy::kLoopedWithRecommendations; + } else { + repeated_pass_strategy = + Fuzzer::RepeatedPassStrategy::kRandomWithRecommendations; + } + + Fuzzer fuzzer(env, seed, enable_all_passes, repeated_pass_strategy, true, + validator_options); fuzzer.SetMessageConsumer(kSilentConsumer); auto fuzzer_result_status = fuzzer.Run(binary_in, initial_facts, donor_suppliers, &fuzzer_binary_out, diff --git a/tools/fuzz/fuzz.cpp b/tools/fuzz/fuzz.cpp index 551f3f43..ea7e0b70 100644 --- a/tools/fuzz/fuzz.cpp +++ b/tools/fuzz/fuzz.cpp @@ -16,7 +16,6 @@ #include #include #include -#include #include #include #include @@ -107,6 +106,14 @@ Options (in lexicographical order): provided if the tool is invoked in fuzzing mode; incompatible with replay and shrink modes. The file should be empty if no donors are to be used. + --enable-all-passes + By default, spirv-fuzz follows the philosophy of "swarm testing" + (Groce et al., 2012): only a subset of fuzzer passes are enabled + on any given fuzzer run, with the subset being chosen randomly. + This flag instead forces *all* fuzzer passes to be enabled. When + running spirv-fuzz many times this is likely to produce *less* + diverse fuzzed modules than when swarm testing is used. The + purpose of the flag is to allow that hypothesis to be tested. --force-render-red Transforms the input shader into a shader that writes red to the output buffer, and then captures the original shader as the body @@ -118,6 +125,19 @@ Options (in lexicographical order): Run the validator after applying each fuzzer pass during fuzzing. Aborts fuzzing early if an invalid binary is created. Useful for debugging spirv-fuzz. + --repeated-pass-strategy= + Available strategies are: + - looped (the default): a sequence of fuzzer passes is chosen at + the start of fuzzing, via randomly choosing enabled passes, and + augmenting these choices with fuzzer passes that it is + recommended to run subsequently. Fuzzing then involves + repeatedly applying this fixed sequence of passes. + - random: each time a fuzzer pass is requested, this strategy + either provides one at random from the set of enabled passes, + or provides a pass that has been recommended based on a pass + that was used previously. + - simple: each time a fuzzer pass is requested, one is provided + at random from the set of enabled passes. --replay File from which to read a sequence of transformations to replay (instead of fuzzing) @@ -174,18 +194,23 @@ void FuzzDiagnostic(spv_message_level_t level, const char* /*source*/, fprintf(stderr, "%s\n", message); } -FuzzStatus ParseFlags(int argc, const char** argv, std::string* in_binary_file, - std::string* out_binary_file, std::string* donors_file, - std::string* replay_transformations_file, - std::vector* interestingness_test, - std::string* shrink_transformations_file, - std::string* shrink_temp_file_prefix, - spvtools::FuzzerOptions* fuzzer_options, - spvtools::ValidatorOptions* validator_options) { +FuzzStatus ParseFlags( + int argc, const char** argv, std::string* in_binary_file, + std::string* out_binary_file, std::string* donors_file, + std::string* replay_transformations_file, + std::vector* interestingness_test, + std::string* shrink_transformations_file, + std::string* shrink_temp_file_prefix, + spvtools::fuzz::Fuzzer::RepeatedPassStrategy* repeated_pass_strategy, + spvtools::FuzzerOptions* fuzzer_options, + spvtools::ValidatorOptions* validator_options) { uint32_t positional_arg_index = 0; bool only_positional_arguments_remain = false; bool force_render_red = false; + *repeated_pass_strategy = + spvtools::fuzz::Fuzzer::RepeatedPassStrategy::kLoopedWithRecommendations; + for (int argi = 1; argi < argc; ++argi) { const char* cur_arg = argv[argi]; if ('-' == cur_arg[0] && !only_positional_arguments_remain) { @@ -206,6 +231,9 @@ FuzzStatus ParseFlags(int argc, const char** argv, std::string* in_binary_file, } else if (0 == strncmp(cur_arg, "--donors=", sizeof("--donors=") - 1)) { const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg); *donors_file = std::string(split_flag.second); + } else if (0 == strncmp(cur_arg, "--enable-all-passes", + sizeof("--enable-all-passes") - 1)) { + fuzzer_options->enable_all_passes(); } else if (0 == strncmp(cur_arg, "--force-render-red", sizeof("--force-render-red") - 1)) { force_render_red = true; @@ -215,6 +243,26 @@ FuzzStatus ParseFlags(int argc, const char** argv, std::string* in_binary_file, } else if (0 == strncmp(cur_arg, "--replay=", sizeof("--replay=") - 1)) { const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg); *replay_transformations_file = std::string(split_flag.second); + } else if (0 == strncmp(cur_arg, "--repeated-pass-strategy=", + sizeof("--repeated-pass-strategy=") - 1)) { + std::string strategy = spvtools::utils::SplitFlagArgs(cur_arg).second; + if (strategy == "looped") { + *repeated_pass_strategy = spvtools::fuzz::Fuzzer:: + RepeatedPassStrategy::kLoopedWithRecommendations; + } else if (strategy == "random") { + *repeated_pass_strategy = spvtools::fuzz::Fuzzer:: + RepeatedPassStrategy::kRandomWithRecommendations; + } else if (strategy == "simple") { + *repeated_pass_strategy = + spvtools::fuzz::Fuzzer::RepeatedPassStrategy::kSimple; + } else { + std::stringstream ss; + ss << "Unknown repeated pass strategy '" << strategy << "'" + << std::endl; + ss << "Valid options are 'looped', 'random' and 'simple'."; + spvtools::Error(FuzzDiagnostic, nullptr, {}, ss.str().c_str()); + return {FuzzActions::STOP, 1}; + } } else if (0 == strncmp(cur_arg, "--replay-range=", sizeof("--replay-range=") - 1)) { const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg); @@ -493,7 +541,9 @@ bool Fuzz(const spv_target_env& target_env, spv_validator_options validator_options, const std::vector& binary_in, const spvtools::fuzz::protobufs::FactSequence& initial_facts, - const std::string& donors, std::vector* binary_out, + const std::string& donors, + spvtools::fuzz::Fuzzer::RepeatedPassStrategy repeated_pass_strategy, + std::vector* binary_out, spvtools::fuzz::protobufs::TransformationSequence* transformations_applied) { auto message_consumer = spvtools::utils::CLIMessageConsumer; @@ -526,6 +576,7 @@ bool Fuzz(const spv_target_env& target_env, fuzzer_options->has_random_seed ? fuzzer_options->random_seed : static_cast(std::random_device()()), + fuzzer_options->all_passes_enabled, repeated_pass_strategy, fuzzer_options->fuzzer_pass_validation_enabled, validator_options); fuzzer.SetMessageConsumer(message_consumer); auto fuzz_result_status = @@ -568,6 +619,7 @@ int main(int argc, const char** argv) { std::vector interestingness_test; std::string shrink_transformations_file; std::string shrink_temp_file_prefix = "temp_"; + spvtools::fuzz::Fuzzer::RepeatedPassStrategy repeated_pass_strategy; spvtools::FuzzerOptions fuzzer_options; spvtools::ValidatorOptions validator_options; @@ -576,7 +628,7 @@ int main(int argc, const char** argv) { ParseFlags(argc, argv, &in_binary_file, &out_binary_file, &donors_file, &replay_transformations_file, &interestingness_test, &shrink_transformations_file, &shrink_temp_file_prefix, - &fuzzer_options, &validator_options); + &repeated_pass_strategy, &fuzzer_options, &validator_options); if (status.action == FuzzActions::STOP) { return status.code; @@ -622,7 +674,7 @@ int main(int argc, const char** argv) { break; case FuzzActions::FUZZ: if (!Fuzz(target_env, fuzzer_options, validator_options, binary_in, - initial_facts, donors_file, &binary_out, + initial_facts, donors_file, repeated_pass_strategy, &binary_out, &transformations_applied)) { return 1; }