diff --git a/source/fuzz/replayer.cpp b/source/fuzz/replayer.cpp index 380e58b0e..a5f311fb2 100644 --- a/source/fuzz/replayer.cpp +++ b/source/fuzz/replayer.cpp @@ -59,7 +59,7 @@ Replayer::ReplayerResult Replayer::Run() { "The number of transformations to be replayed must not " "exceed the size of the transformation sequence."); return {Replayer::ReplayerResultStatus::kTooManyTransformationsRequested, - std::vector(), protobufs::TransformationSequence()}; + nullptr, nullptr, protobufs::TransformationSequence()}; } spvtools::SpirvTools tools(target_env_); @@ -67,15 +67,15 @@ Replayer::ReplayerResult Replayer::Run() { consumer_(SPV_MSG_ERROR, nullptr, {}, "Failed to create SPIRV-Tools interface; stopping."); return {Replayer::ReplayerResultStatus::kFailedToCreateSpirvToolsInterface, - std::vector(), protobufs::TransformationSequence()}; + nullptr, nullptr, protobufs::TransformationSequence()}; } // Initial binary should be valid. if (!tools.Validate(&binary_in_[0], binary_in_.size(), validator_options_)) { consumer_(SPV_MSG_INFO, nullptr, {}, "Initial binary is invalid; stopping."); - return {Replayer::ReplayerResultStatus::kInitialBinaryInvalid, - std::vector(), protobufs::TransformationSequence()}; + return {Replayer::ReplayerResultStatus::kInitialBinaryInvalid, nullptr, + nullptr, protobufs::TransformationSequence()}; } // Build the module from the input binary. @@ -140,7 +140,7 @@ Replayer::ReplayerResult Replayer::Run() { "Binary became invalid during replay (set a " "breakpoint to inspect); stopping."); return {Replayer::ReplayerResultStatus::kReplayValidationFailure, - std::vector(), protobufs::TransformationSequence()}; + nullptr, nullptr, protobufs::TransformationSequence()}; } // The binary was valid, so it becomes the latest valid binary. @@ -149,10 +149,8 @@ Replayer::ReplayerResult Replayer::Run() { } } - // Write out the module as a binary. - std::vector binary_out; - ir_context->module()->ToBinary(&binary_out, false); - return {Replayer::ReplayerResultStatus::kComplete, std::move(binary_out), + return {Replayer::ReplayerResultStatus::kComplete, std::move(ir_context), + std::move(transformation_context), std::move(transformation_sequence_out)}; } diff --git a/source/fuzz/replayer.h b/source/fuzz/replayer.h index 5bc62d9d0..8e0303e76 100644 --- a/source/fuzz/replayer.h +++ b/source/fuzz/replayer.h @@ -19,6 +19,8 @@ #include #include "source/fuzz/protobufs/spirvfuzz_protobufs.h" +#include "source/fuzz/transformation_context.h" +#include "source/opt/ir_context.h" #include "spirv-tools/libspirv.hpp" namespace spvtools { @@ -39,7 +41,8 @@ class Replayer { struct ReplayerResult { ReplayerResultStatus status; - std::vector transformed_binary; + std::unique_ptr transformed_module; + std::unique_ptr transformation_context; protobufs::TransformationSequence applied_transformations; }; @@ -70,9 +73,10 @@ class Replayer { // ids will be available during replay starting from this value. // // On success, returns a successful result status together with the - // transformations that were successfully applied and the binary resulting - // from applying them. Otherwise, returns an appropriate result status - // together with an empty binary and empty transformation sequence. + // transformations that were applied, the IR for the transformed module, and + // the transformation context that arises from applying these transformations. + // Otherwise, returns an appropriate result status, an empty transformation + // sequence, and null pointers for the IR context and transformation context. ReplayerResult Run(); private: diff --git a/source/fuzz/shrinker.cpp b/source/fuzz/shrinker.cpp index a88a1eaf2..ef6a99009 100644 --- a/source/fuzz/shrinker.cpp +++ b/source/fuzz/shrinker.cpp @@ -70,7 +70,7 @@ Shrinker::Shrinker( uint32_t step_limit, bool validate_during_replay, spv_validator_options validator_options) : target_env_(target_env), - consumer_(consumer), + consumer_(std::move(consumer)), binary_in_(binary_in), initial_facts_(initial_facts), transformation_sequence_in_(transformation_sequence_in), @@ -120,8 +120,9 @@ Shrinker::ShrinkerResult Shrinker::Run() { // Get the binary that results from running these transformations, and the // subsequence of the initial transformations that actually apply (in // principle this could be a strict subsequence). - std::vector current_best_binary = - std::move(initial_replay_result.transformed_binary); + std::vector current_best_binary; + initial_replay_result.transformed_module->module()->ToBinary( + ¤t_best_binary, false); protobufs::TransformationSequence current_best_transformations = std::move(initial_replay_result.applied_transformations); @@ -215,12 +216,14 @@ Shrinker::ShrinkerResult Shrinker::Run() { "Removing this chunk of transformations should not have an effect " "on earlier chunks."); - if (interestingness_function_(replay_result.transformed_binary, - attempt)) { + std::vector transformed_binary; + replay_result.transformed_module->module()->ToBinary(&transformed_binary, + false); + if (interestingness_function_(transformed_binary, attempt)) { // If the binary arising from the smaller transformation sequence is // interesting, this becomes our current best binary and transformation // sequence. - current_best_binary = std::move(replay_result.transformed_binary); + current_best_binary = std::move(transformed_binary); current_best_transformations = std::move(replay_result.applied_transformations); progress_this_round = true; diff --git a/test/fuzz/fuzz_test_util.cpp b/test/fuzz/fuzz_test_util.cpp index c717961aa..c874f7ab8 100644 --- a/test/fuzz/fuzz_test_util.cpp +++ b/test/fuzz/fuzz_test_util.cpp @@ -72,6 +72,13 @@ bool IsEqual(const spv_target_env env, const opt::IRContext* ir_1, return IsEqual(env, binary_1, binary_2); } +bool IsEqual(const spv_target_env env, const std::vector& binary_1, + const opt::IRContext* ir_2) { + std::vector binary_2; + ir_2->module()->ToBinary(&binary_2, false); + return IsEqual(env, binary_1, binary_2); +} + bool IsValid(spv_target_env env, const opt::IRContext* ir) { std::vector binary; ir->module()->ToBinary(&binary, false); diff --git a/test/fuzz/fuzz_test_util.h b/test/fuzz/fuzz_test_util.h index 19d19182c..1126de111 100644 --- a/test/fuzz/fuzz_test_util.h +++ b/test/fuzz/fuzz_test_util.h @@ -45,6 +45,11 @@ bool IsEqual(spv_target_env env, const std::string& expected_text, bool IsEqual(spv_target_env env, const opt::IRContext* ir_1, const opt::IRContext* ir_2); +// Turns |ir_2| into a binary, then returns true if and only if the resulting +// binary is bit-wise equal to |binary_1|. +bool IsEqual(spv_target_env env, const std::vector& binary_1, + const opt::IRContext* ir_2); + // Assembles the given IR context and returns true if and only if // the resulting binary is valid. bool IsValid(spv_target_env env, const opt::IRContext* ir); diff --git a/test/fuzz/fuzzer_replayer_test.cpp b/test/fuzz/fuzzer_replayer_test.cpp index bfcf4ea9d..fc81713e4 100644 --- a/test/fuzz/fuzzer_replayer_test.cpp +++ b/test/fuzz/fuzzer_replayer_test.cpp @@ -1684,8 +1684,8 @@ void RunFuzzerAndReplayer(const std::string& shader, replayer_result.applied_transformations.SerializeToString( &replayer_transformations_string); ASSERT_EQ(fuzzer_transformations_string, replayer_transformations_string); - ASSERT_EQ(fuzzer_result.transformed_binary, - replayer_result.transformed_binary); + ASSERT_TRUE(IsEqual(env, fuzzer_result.transformed_binary, + replayer_result.transformed_module.get())); } } diff --git a/test/fuzz/replayer_test.cpp b/test/fuzz/replayer_test.cpp index 2444e9f8d..6a956b0cf 100644 --- a/test/fuzz/replayer_test.cpp +++ b/test/fuzz/replayer_test.cpp @@ -14,7 +14,12 @@ #include "source/fuzz/replayer.h" +#include "source/fuzz/data_descriptor.h" #include "source/fuzz/instruction_descriptor.h" +#include "source/fuzz/transformation_add_constant_scalar.h" +#include "source/fuzz/transformation_add_global_variable.h" +#include "source/fuzz/transformation_add_parameter.h" +#include "source/fuzz/transformation_add_synonym.h" #include "source/fuzz/transformation_split_block.h" #include "test/fuzz/fuzz_test_util.h" @@ -169,8 +174,8 @@ TEST(ReplayerTest, PartialReplay) { OpReturn OpFunctionEnd )"; - ASSERT_TRUE( - IsEqual(env, kFullySplitShader, replayer_result.transformed_binary)); + ASSERT_TRUE(IsEqual(env, kFullySplitShader, + replayer_result.transformed_module.get())); } { @@ -247,8 +252,8 @@ TEST(ReplayerTest, PartialReplay) { OpReturn OpFunctionEnd )"; - ASSERT_TRUE( - IsEqual(env, kHalfSplitShader, replayer_result.transformed_binary)); + ASSERT_TRUE(IsEqual(env, kHalfSplitShader, + replayer_result.transformed_module.get())); } { @@ -263,7 +268,8 @@ TEST(ReplayerTest, PartialReplay) { replayer_result.status); // No transformations should be applied ASSERT_EQ(0, replayer_result.applied_transformations.transformation_size()); - ASSERT_TRUE(IsEqual(env, kTestShader, replayer_result.transformed_binary)); + ASSERT_TRUE( + IsEqual(env, kTestShader, replayer_result.transformed_module.get())); } { @@ -282,10 +288,122 @@ TEST(ReplayerTest, PartialReplay) { // No transformations should be applied ASSERT_EQ(0, replayer_result.applied_transformations.transformation_size()); // The output binary should be empty - ASSERT_TRUE(replayer_result.transformed_binary.empty()); + ASSERT_EQ(nullptr, replayer_result.transformed_module); } } +TEST(ReplayerTest, CheckFactsAfterReplay) { + const std::string kTestShader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 320 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %8 = OpTypeInt 32 1 + %9 = OpTypePointer Function %8 + %50 = OpTypePointer Private %8 + %11 = OpConstant %8 1 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %10 = OpVariable %9 Function + OpStore %10 %11 + %12 = OpFunctionCall %2 %6 + OpReturn + OpFunctionEnd + %6 = OpFunction %2 None %3 + %7 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + spvtools::ValidatorOptions validator_options; + + std::vector binary_in; + SpirvTools t(env); + t.SetMessageConsumer(kSilentConsumer); + ASSERT_TRUE(t.Assemble(kTestShader, &binary_in, kFuzzAssembleOption)); + ASSERT_TRUE(t.Validate(binary_in)); + + protobufs::TransformationSequence transformations; + *transformations.add_transformation() = + TransformationAddConstantScalar(100, 8, {42}, true).ToMessage(); + *transformations.add_transformation() = + TransformationAddGlobalVariable(101, 50, SpvStorageClassPrivate, 100, + true) + .ToMessage(); + *transformations.add_transformation() = + TransformationAddParameter(6, 102, 8, {{12, 100}}, 103).ToMessage(); + *transformations.add_transformation() = + TransformationAddSynonym( + 11, + protobufs::TransformationAddSynonym::SynonymType:: + TransformationAddSynonym_SynonymType_COPY_OBJECT, + 104, MakeInstructionDescriptor(12, SpvOpFunctionCall, 0)) + .ToMessage(); + + // Full replay + protobufs::FactSequence empty_facts; + auto replayer_result = + Replayer(env, kSilentConsumer, binary_in, empty_facts, transformations, + transformations.transformation_size(), 0, true, + validator_options) + .Run(); + // Replay should succeed. + ASSERT_EQ(Replayer::ReplayerResultStatus::kComplete, replayer_result.status); + // All transformations should be applied. + ASSERT_TRUE(google::protobuf::util::MessageDifferencer::Equals( + transformations, replayer_result.applied_transformations)); + + const std::string kExpected = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 320 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %8 = OpTypeInt 32 1 + %9 = OpTypePointer Function %8 + %50 = OpTypePointer Private %8 + %11 = OpConstant %8 1 + %100 = OpConstant %8 42 + %101 = OpVariable %50 Private %100 + %103 = OpTypeFunction %2 %8 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %10 = OpVariable %9 Function + OpStore %10 %11 + %104 = OpCopyObject %8 %11 + %12 = OpFunctionCall %2 %6 %100 + OpReturn + OpFunctionEnd + %6 = OpFunction %2 None %103 + %102 = OpFunctionParameter %8 + %7 = OpLabel + OpReturn + OpFunctionEnd + )"; + ASSERT_TRUE( + IsEqual(env, kExpected, replayer_result.transformed_module.get())); + + ASSERT_TRUE( + replayer_result.transformation_context->GetFactManager()->IdIsIrrelevant( + 100)); + ASSERT_TRUE(replayer_result.transformation_context->GetFactManager() + ->PointeeValueIsIrrelevant(101)); + ASSERT_TRUE( + replayer_result.transformation_context->GetFactManager()->IdIsIrrelevant( + 102)); + ASSERT_TRUE( + replayer_result.transformation_context->GetFactManager()->IsSynonymous( + MakeDataDescriptor(11, {}), MakeDataDescriptor(104, {}))); +} + } // namespace } // namespace fuzz } // namespace spvtools diff --git a/tools/fuzz/fuzz.cpp b/tools/fuzz/fuzz.cpp index 80ac9f514..d19904b42 100644 --- a/tools/fuzz/fuzz.cpp +++ b/tools/fuzz/fuzz.cpp @@ -480,8 +480,7 @@ bool Replay(const spv_target_env& target_env, initial_facts, transformation_sequence, num_transformations_to_apply, 0, fuzzer_options->replay_validation_enabled, validator_options) .Run(); - - *binary_out = std::move(replay_result.transformed_binary); + replay_result.transformed_module->module()->ToBinary(binary_out, false); *transformations_applied = std::move(replay_result.applied_transformations); return replay_result.status == spvtools::fuzz::Replayer::ReplayerResultStatus::kComplete;