| // 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/shrinker.h" | |
| #include "gtest/gtest.h" | |
| #include "source/fuzz/fact_manager/fact_manager.h" | |
| #include "source/fuzz/fuzzer_context.h" | |
| #include "source/fuzz/fuzzer_pass_donate_modules.h" | |
| #include "source/fuzz/fuzzer_util.h" | |
| #include "source/fuzz/pseudo_random_generator.h" | |
| #include "source/fuzz/transformation_context.h" | |
| #include "source/opt/ir_context.h" | |
| #include "source/util/make_unique.h" | |
| #include "test/fuzz/fuzz_test_util.h" | |
| namespace spvtools { | |
| namespace fuzz { | |
| namespace { | |
| TEST(ShrinkerTest, ReduceAddedFunctions) { | |
| const std::string kReferenceModule = 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 | |
| %6 = OpTypeInt 32 1 | |
| %7 = OpTypePointer Private %6 | |
| %8 = OpVariable %7 Private | |
| %9 = OpConstant %6 2 | |
| %10 = OpTypePointer Function %6 | |
| %4 = OpFunction %2 None %3 | |
| %5 = OpLabel | |
| %11 = OpVariable %10 Function | |
| OpStore %8 %9 | |
| %12 = OpLoad %6 %8 | |
| OpStore %11 %12 | |
| OpReturn | |
| OpFunctionEnd | |
| )"; | |
| const std::string kDonorModule = 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 | |
| %6 = OpTypeInt 32 1 | |
| %7 = OpTypePointer Function %6 | |
| %8 = OpTypeFunction %6 %7 | |
| %12 = OpTypeFunction %2 %7 | |
| %17 = OpConstant %6 0 | |
| %26 = OpTypeBool | |
| %32 = OpConstant %6 1 | |
| %46 = OpTypePointer Private %6 | |
| %47 = OpVariable %46 Private | |
| %48 = OpConstant %6 3 | |
| %4 = OpFunction %2 None %3 | |
| %5 = OpLabel | |
| %49 = OpVariable %7 Function | |
| %50 = OpVariable %7 Function | |
| %51 = OpLoad %6 %49 | |
| OpStore %50 %51 | |
| %52 = OpFunctionCall %2 %14 %50 | |
| OpReturn | |
| OpFunctionEnd | |
| %10 = OpFunction %6 None %8 | |
| %9 = OpFunctionParameter %7 | |
| %11 = OpLabel | |
| %16 = OpVariable %7 Function | |
| %18 = OpVariable %7 Function | |
| OpStore %16 %17 | |
| OpStore %18 %17 | |
| OpBranch %19 | |
| %19 = OpLabel | |
| OpLoopMerge %21 %22 None | |
| OpBranch %23 | |
| %23 = OpLabel | |
| %24 = OpLoad %6 %18 | |
| %25 = OpLoad %6 %9 | |
| %27 = OpSLessThan %26 %24 %25 | |
| OpBranchConditional %27 %20 %21 | |
| %20 = OpLabel | |
| %28 = OpLoad %6 %9 | |
| %29 = OpLoad %6 %16 | |
| %30 = OpIAdd %6 %29 %28 | |
| OpStore %16 %30 | |
| OpBranch %22 | |
| %22 = OpLabel | |
| %31 = OpLoad %6 %18 | |
| %33 = OpIAdd %6 %31 %32 | |
| OpStore %18 %33 | |
| OpBranch %19 | |
| %21 = OpLabel | |
| %34 = OpLoad %6 %16 | |
| %35 = OpNot %6 %34 | |
| OpReturnValue %35 | |
| OpFunctionEnd | |
| %14 = OpFunction %2 None %12 | |
| %13 = OpFunctionParameter %7 | |
| %15 = OpLabel | |
| %37 = OpVariable %7 Function | |
| %38 = OpVariable %7 Function | |
| %39 = OpLoad %6 %13 | |
| OpStore %38 %39 | |
| %40 = OpFunctionCall %6 %10 %38 | |
| OpStore %37 %40 | |
| %41 = OpLoad %6 %37 | |
| %42 = OpLoad %6 %13 | |
| %43 = OpSGreaterThan %26 %41 %42 | |
| OpSelectionMerge %45 None | |
| OpBranchConditional %43 %44 %45 | |
| %44 = OpLabel | |
| OpStore %47 %48 | |
| OpBranch %45 | |
| %45 = OpLabel | |
| OpReturn | |
| OpFunctionEnd | |
| )"; | |
| // Note: |env| should ideally be declared const. However, due to a known | |
| // issue with older versions of MSVC we would have to mark |env| as being | |
| // captured due to its used in a lambda below, and other compilers would warn | |
| // that such capturing is not necessary. Not declaring |env| as const means | |
| // that it needs to be captured to be used in the lambda, and thus all | |
| // compilers are kept happy. See: | |
| // https://developercommunity.visualstudio.com/content/problem/367326/problems-with-capturing-constexpr-in-lambda.html | |
| spv_target_env env = SPV_ENV_UNIVERSAL_1_3; | |
| const auto consumer = fuzzerutil::kSilentMessageConsumer; | |
| SpirvTools tools(env); | |
| std::vector<uint32_t> reference_binary; | |
| ASSERT_TRUE( | |
| tools.Assemble(kReferenceModule, &reference_binary, kFuzzAssembleOption)); | |
| spvtools::ValidatorOptions validator_options; | |
| const auto variant_ir_context = | |
| BuildModule(env, consumer, kReferenceModule, kFuzzAssembleOption); | |
| ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed( | |
| variant_ir_context.get(), validator_options, kConsoleMessageConsumer)); | |
| const auto donor_ir_context = | |
| BuildModule(env, consumer, kDonorModule, kFuzzAssembleOption); | |
| ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed( | |
| donor_ir_context.get(), validator_options, kConsoleMessageConsumer)); | |
| FuzzerContext fuzzer_context(MakeUnique<PseudoRandomGenerator>(0), 100, | |
| false); | |
| TransformationContext transformation_context( | |
| MakeUnique<FactManager>(variant_ir_context.get()), validator_options); | |
| protobufs::TransformationSequence transformations; | |
| FuzzerPassDonateModules pass(variant_ir_context.get(), | |
| &transformation_context, &fuzzer_context, | |
| &transformations, false, {}); | |
| pass.DonateSingleModule(donor_ir_context.get(), true); | |
| protobufs::FactSequence no_facts; | |
| Shrinker::InterestingnessFunction interestingness_function = | |
| [consumer, env](const std::vector<uint32_t>& binary, | |
| uint32_t /*unused*/) -> bool { | |
| bool found_op_not = false; | |
| uint32_t op_call_count = 0; | |
| auto temp_ir_context = | |
| BuildModule(env, consumer, binary.data(), binary.size()); | |
| for (auto& function : *temp_ir_context->module()) { | |
| for (auto& block : function) { | |
| for (auto& inst : block) { | |
| if (inst.opcode() == spv::Op::OpNot) { | |
| found_op_not = true; | |
| } else if (inst.opcode() == spv::Op::OpFunctionCall) { | |
| op_call_count++; | |
| } | |
| } | |
| } | |
| } | |
| return found_op_not && op_call_count >= 2; | |
| }; | |
| auto shrinker_result = | |
| Shrinker(env, consumer, reference_binary, no_facts, transformations, | |
| interestingness_function, 1000, true, validator_options) | |
| .Run(); | |
| ASSERT_EQ(Shrinker::ShrinkerResultStatus::kComplete, shrinker_result.status); | |
| // We now check that the module after shrinking looks right. | |
| // The entry point should be identical to what it looked like in the | |
| // reference, while the other functions should be absolutely minimal, | |
| // containing only what is needed to satisfy the interestingness function. | |
| auto ir_context_after_shrinking = | |
| BuildModule(env, consumer, shrinker_result.transformed_binary.data(), | |
| shrinker_result.transformed_binary.size()); | |
| bool first_function = true; | |
| for (auto& function : *ir_context_after_shrinking->module()) { | |
| if (first_function) { | |
| first_function = false; | |
| bool first_block = true; | |
| for (auto& block : function) { | |
| ASSERT_TRUE(first_block); | |
| uint32_t counter = 0; | |
| for (auto& inst : block) { | |
| switch (counter) { | |
| case 0: | |
| ASSERT_EQ(spv::Op::OpVariable, inst.opcode()); | |
| ASSERT_EQ(11, inst.result_id()); | |
| break; | |
| case 1: | |
| ASSERT_EQ(spv::Op::OpStore, inst.opcode()); | |
| break; | |
| case 2: | |
| ASSERT_EQ(spv::Op::OpLoad, inst.opcode()); | |
| ASSERT_EQ(12, inst.result_id()); | |
| break; | |
| case 3: | |
| ASSERT_EQ(spv::Op::OpStore, inst.opcode()); | |
| break; | |
| case 4: | |
| ASSERT_EQ(spv::Op::OpReturn, inst.opcode()); | |
| break; | |
| default: | |
| FAIL(); | |
| } | |
| counter++; | |
| } | |
| } | |
| } else { | |
| bool first_block = true; | |
| for (auto& block : function) { | |
| ASSERT_TRUE(first_block); | |
| first_block = false; | |
| for (auto& inst : block) { | |
| switch (inst.opcode()) { | |
| case spv::Op::OpVariable: | |
| case spv::Op::OpNot: | |
| case spv::Op::OpReturn: | |
| case spv::Op::OpReturnValue: | |
| case spv::Op::OpFunctionCall: | |
| // These are the only instructions we expect to see. | |
| break; | |
| default: | |
| FAIL(); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| TEST(ShrinkerTest, HitStepLimitWhenReducingAddedFunctions) { | |
| const std::string kReferenceModule = 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 | |
| %6 = OpTypeInt 32 1 | |
| %7 = OpTypePointer Private %6 | |
| %8 = OpVariable %7 Private | |
| %9 = OpConstant %6 2 | |
| %10 = OpTypePointer Function %6 | |
| %4 = OpFunction %2 None %3 | |
| %5 = OpLabel | |
| %11 = OpVariable %10 Function | |
| OpStore %8 %9 | |
| %12 = OpLoad %6 %8 | |
| OpStore %11 %12 | |
| OpReturn | |
| OpFunctionEnd | |
| )"; | |
| const std::string kDonorModule = 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 | |
| %6 = OpTypeInt 32 1 | |
| %48 = OpConstant %6 3 | |
| %4 = OpFunction %2 None %3 | |
| %5 = OpLabel | |
| %52 = OpCopyObject %6 %48 | |
| %53 = OpCopyObject %6 %52 | |
| %54 = OpCopyObject %6 %53 | |
| %55 = OpCopyObject %6 %54 | |
| %56 = OpCopyObject %6 %55 | |
| %57 = OpCopyObject %6 %56 | |
| %58 = OpCopyObject %6 %48 | |
| %59 = OpCopyObject %6 %58 | |
| %60 = OpCopyObject %6 %59 | |
| %61 = OpCopyObject %6 %60 | |
| %62 = OpCopyObject %6 %61 | |
| %63 = OpCopyObject %6 %62 | |
| %64 = OpCopyObject %6 %48 | |
| OpReturn | |
| OpFunctionEnd | |
| )"; | |
| spv_target_env env = SPV_ENV_UNIVERSAL_1_3; | |
| const auto consumer = fuzzerutil::kSilentMessageConsumer; | |
| SpirvTools tools(env); | |
| std::vector<uint32_t> reference_binary; | |
| ASSERT_TRUE( | |
| tools.Assemble(kReferenceModule, &reference_binary, kFuzzAssembleOption)); | |
| spvtools::ValidatorOptions validator_options; | |
| const auto variant_ir_context = | |
| BuildModule(env, consumer, kReferenceModule, kFuzzAssembleOption); | |
| ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed( | |
| variant_ir_context.get(), validator_options, kConsoleMessageConsumer)); | |
| const auto donor_ir_context = | |
| BuildModule(env, consumer, kDonorModule, kFuzzAssembleOption); | |
| ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed( | |
| donor_ir_context.get(), validator_options, kConsoleMessageConsumer)); | |
| FuzzerContext fuzzer_context(MakeUnique<PseudoRandomGenerator>(0), 100, | |
| false); | |
| TransformationContext transformation_context( | |
| MakeUnique<FactManager>(variant_ir_context.get()), validator_options); | |
| protobufs::TransformationSequence transformations; | |
| FuzzerPassDonateModules pass(variant_ir_context.get(), | |
| &transformation_context, &fuzzer_context, | |
| &transformations, false, {}); | |
| pass.DonateSingleModule(donor_ir_context.get(), true); | |
| protobufs::FactSequence no_facts; | |
| Shrinker::InterestingnessFunction interestingness_function = | |
| [consumer, env](const std::vector<uint32_t>& binary, | |
| uint32_t /*unused*/) -> bool { | |
| auto temp_ir_context = | |
| BuildModule(env, consumer, binary.data(), binary.size()); | |
| uint32_t copy_object_count = 0; | |
| temp_ir_context->module()->ForEachInst( | |
| [©_object_count](opt::Instruction* inst) { | |
| if (inst->opcode() == spv::Op::OpCopyObject) { | |
| copy_object_count++; | |
| } | |
| }); | |
| return copy_object_count >= 8; | |
| }; | |
| auto shrinker_result = | |
| Shrinker(env, consumer, reference_binary, no_facts, transformations, | |
| interestingness_function, 30, true, validator_options) | |
| .Run(); | |
| ASSERT_EQ(Shrinker::ShrinkerResultStatus::kStepLimitReached, | |
| shrinker_result.status); | |
| } | |
| } // namespace | |
| } // namespace fuzz | |
| } // namespace spvtools |