shrinker_test.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  1. // Copyright (c) 2020 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. #include "source/fuzz/shrinker.h"
  15. #include "gtest/gtest.h"
  16. #include "source/fuzz/fact_manager/fact_manager.h"
  17. #include "source/fuzz/fuzzer_context.h"
  18. #include "source/fuzz/fuzzer_pass_donate_modules.h"
  19. #include "source/fuzz/fuzzer_util.h"
  20. #include "source/fuzz/pseudo_random_generator.h"
  21. #include "source/fuzz/transformation_context.h"
  22. #include "source/opt/ir_context.h"
  23. #include "source/util/make_unique.h"
  24. #include "test/fuzz/fuzz_test_util.h"
  25. namespace spvtools {
  26. namespace fuzz {
  27. namespace {
  28. TEST(ShrinkerTest, ReduceAddedFunctions) {
  29. const std::string kReferenceModule = R"(
  30. OpCapability Shader
  31. %1 = OpExtInstImport "GLSL.std.450"
  32. OpMemoryModel Logical GLSL450
  33. OpEntryPoint Fragment %4 "main"
  34. OpExecutionMode %4 OriginUpperLeft
  35. OpSource ESSL 320
  36. %2 = OpTypeVoid
  37. %3 = OpTypeFunction %2
  38. %6 = OpTypeInt 32 1
  39. %7 = OpTypePointer Private %6
  40. %8 = OpVariable %7 Private
  41. %9 = OpConstant %6 2
  42. %10 = OpTypePointer Function %6
  43. %4 = OpFunction %2 None %3
  44. %5 = OpLabel
  45. %11 = OpVariable %10 Function
  46. OpStore %8 %9
  47. %12 = OpLoad %6 %8
  48. OpStore %11 %12
  49. OpReturn
  50. OpFunctionEnd
  51. )";
  52. const std::string kDonorModule = R"(
  53. OpCapability Shader
  54. %1 = OpExtInstImport "GLSL.std.450"
  55. OpMemoryModel Logical GLSL450
  56. OpEntryPoint Fragment %4 "main"
  57. OpExecutionMode %4 OriginUpperLeft
  58. OpSource ESSL 320
  59. %2 = OpTypeVoid
  60. %3 = OpTypeFunction %2
  61. %6 = OpTypeInt 32 1
  62. %7 = OpTypePointer Function %6
  63. %8 = OpTypeFunction %6 %7
  64. %12 = OpTypeFunction %2 %7
  65. %17 = OpConstant %6 0
  66. %26 = OpTypeBool
  67. %32 = OpConstant %6 1
  68. %46 = OpTypePointer Private %6
  69. %47 = OpVariable %46 Private
  70. %48 = OpConstant %6 3
  71. %4 = OpFunction %2 None %3
  72. %5 = OpLabel
  73. %49 = OpVariable %7 Function
  74. %50 = OpVariable %7 Function
  75. %51 = OpLoad %6 %49
  76. OpStore %50 %51
  77. %52 = OpFunctionCall %2 %14 %50
  78. OpReturn
  79. OpFunctionEnd
  80. %10 = OpFunction %6 None %8
  81. %9 = OpFunctionParameter %7
  82. %11 = OpLabel
  83. %16 = OpVariable %7 Function
  84. %18 = OpVariable %7 Function
  85. OpStore %16 %17
  86. OpStore %18 %17
  87. OpBranch %19
  88. %19 = OpLabel
  89. OpLoopMerge %21 %22 None
  90. OpBranch %23
  91. %23 = OpLabel
  92. %24 = OpLoad %6 %18
  93. %25 = OpLoad %6 %9
  94. %27 = OpSLessThan %26 %24 %25
  95. OpBranchConditional %27 %20 %21
  96. %20 = OpLabel
  97. %28 = OpLoad %6 %9
  98. %29 = OpLoad %6 %16
  99. %30 = OpIAdd %6 %29 %28
  100. OpStore %16 %30
  101. OpBranch %22
  102. %22 = OpLabel
  103. %31 = OpLoad %6 %18
  104. %33 = OpIAdd %6 %31 %32
  105. OpStore %18 %33
  106. OpBranch %19
  107. %21 = OpLabel
  108. %34 = OpLoad %6 %16
  109. %35 = OpNot %6 %34
  110. OpReturnValue %35
  111. OpFunctionEnd
  112. %14 = OpFunction %2 None %12
  113. %13 = OpFunctionParameter %7
  114. %15 = OpLabel
  115. %37 = OpVariable %7 Function
  116. %38 = OpVariable %7 Function
  117. %39 = OpLoad %6 %13
  118. OpStore %38 %39
  119. %40 = OpFunctionCall %6 %10 %38
  120. OpStore %37 %40
  121. %41 = OpLoad %6 %37
  122. %42 = OpLoad %6 %13
  123. %43 = OpSGreaterThan %26 %41 %42
  124. OpSelectionMerge %45 None
  125. OpBranchConditional %43 %44 %45
  126. %44 = OpLabel
  127. OpStore %47 %48
  128. OpBranch %45
  129. %45 = OpLabel
  130. OpReturn
  131. OpFunctionEnd
  132. )";
  133. // Note: |env| should ideally be declared const. However, due to a known
  134. // issue with older versions of MSVC we would have to mark |env| as being
  135. // captured due to its used in a lambda below, and other compilers would warn
  136. // that such capturing is not necessary. Not declaring |env| as const means
  137. // that it needs to be captured to be used in the lambda, and thus all
  138. // compilers are kept happy. See:
  139. // https://developercommunity.visualstudio.com/content/problem/367326/problems-with-capturing-constexpr-in-lambda.html
  140. spv_target_env env = SPV_ENV_UNIVERSAL_1_3;
  141. const auto consumer = fuzzerutil::kSilentMessageConsumer;
  142. SpirvTools tools(env);
  143. std::vector<uint32_t> reference_binary;
  144. ASSERT_TRUE(
  145. tools.Assemble(kReferenceModule, &reference_binary, kFuzzAssembleOption));
  146. spvtools::ValidatorOptions validator_options;
  147. const auto variant_ir_context =
  148. BuildModule(env, consumer, kReferenceModule, kFuzzAssembleOption);
  149. ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
  150. variant_ir_context.get(), validator_options, kConsoleMessageConsumer));
  151. const auto donor_ir_context =
  152. BuildModule(env, consumer, kDonorModule, kFuzzAssembleOption);
  153. ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
  154. donor_ir_context.get(), validator_options, kConsoleMessageConsumer));
  155. FuzzerContext fuzzer_context(MakeUnique<PseudoRandomGenerator>(0), 100,
  156. false);
  157. TransformationContext transformation_context(
  158. MakeUnique<FactManager>(variant_ir_context.get()), validator_options);
  159. protobufs::TransformationSequence transformations;
  160. FuzzerPassDonateModules pass(variant_ir_context.get(),
  161. &transformation_context, &fuzzer_context,
  162. &transformations, false, {});
  163. pass.DonateSingleModule(donor_ir_context.get(), true);
  164. protobufs::FactSequence no_facts;
  165. Shrinker::InterestingnessFunction interestingness_function =
  166. [consumer, env](const std::vector<uint32_t>& binary,
  167. uint32_t /*unused*/) -> bool {
  168. bool found_op_not = false;
  169. uint32_t op_call_count = 0;
  170. auto temp_ir_context =
  171. BuildModule(env, consumer, binary.data(), binary.size());
  172. for (auto& function : *temp_ir_context->module()) {
  173. for (auto& block : function) {
  174. for (auto& inst : block) {
  175. if (inst.opcode() == spv::Op::OpNot) {
  176. found_op_not = true;
  177. } else if (inst.opcode() == spv::Op::OpFunctionCall) {
  178. op_call_count++;
  179. }
  180. }
  181. }
  182. }
  183. return found_op_not && op_call_count >= 2;
  184. };
  185. auto shrinker_result =
  186. Shrinker(env, consumer, reference_binary, no_facts, transformations,
  187. interestingness_function, 1000, true, validator_options)
  188. .Run();
  189. ASSERT_EQ(Shrinker::ShrinkerResultStatus::kComplete, shrinker_result.status);
  190. // We now check that the module after shrinking looks right.
  191. // The entry point should be identical to what it looked like in the
  192. // reference, while the other functions should be absolutely minimal,
  193. // containing only what is needed to satisfy the interestingness function.
  194. auto ir_context_after_shrinking =
  195. BuildModule(env, consumer, shrinker_result.transformed_binary.data(),
  196. shrinker_result.transformed_binary.size());
  197. bool first_function = true;
  198. for (auto& function : *ir_context_after_shrinking->module()) {
  199. if (first_function) {
  200. first_function = false;
  201. bool first_block = true;
  202. for (auto& block : function) {
  203. ASSERT_TRUE(first_block);
  204. uint32_t counter = 0;
  205. for (auto& inst : block) {
  206. switch (counter) {
  207. case 0:
  208. ASSERT_EQ(spv::Op::OpVariable, inst.opcode());
  209. ASSERT_EQ(11, inst.result_id());
  210. break;
  211. case 1:
  212. ASSERT_EQ(spv::Op::OpStore, inst.opcode());
  213. break;
  214. case 2:
  215. ASSERT_EQ(spv::Op::OpLoad, inst.opcode());
  216. ASSERT_EQ(12, inst.result_id());
  217. break;
  218. case 3:
  219. ASSERT_EQ(spv::Op::OpStore, inst.opcode());
  220. break;
  221. case 4:
  222. ASSERT_EQ(spv::Op::OpReturn, inst.opcode());
  223. break;
  224. default:
  225. FAIL();
  226. }
  227. counter++;
  228. }
  229. }
  230. } else {
  231. bool first_block = true;
  232. for (auto& block : function) {
  233. ASSERT_TRUE(first_block);
  234. first_block = false;
  235. for (auto& inst : block) {
  236. switch (inst.opcode()) {
  237. case spv::Op::OpVariable:
  238. case spv::Op::OpNot:
  239. case spv::Op::OpReturn:
  240. case spv::Op::OpReturnValue:
  241. case spv::Op::OpFunctionCall:
  242. // These are the only instructions we expect to see.
  243. break;
  244. default:
  245. FAIL();
  246. }
  247. }
  248. }
  249. }
  250. }
  251. }
  252. TEST(ShrinkerTest, HitStepLimitWhenReducingAddedFunctions) {
  253. const std::string kReferenceModule = R"(
  254. OpCapability Shader
  255. %1 = OpExtInstImport "GLSL.std.450"
  256. OpMemoryModel Logical GLSL450
  257. OpEntryPoint Fragment %4 "main"
  258. OpExecutionMode %4 OriginUpperLeft
  259. OpSource ESSL 320
  260. %2 = OpTypeVoid
  261. %3 = OpTypeFunction %2
  262. %6 = OpTypeInt 32 1
  263. %7 = OpTypePointer Private %6
  264. %8 = OpVariable %7 Private
  265. %9 = OpConstant %6 2
  266. %10 = OpTypePointer Function %6
  267. %4 = OpFunction %2 None %3
  268. %5 = OpLabel
  269. %11 = OpVariable %10 Function
  270. OpStore %8 %9
  271. %12 = OpLoad %6 %8
  272. OpStore %11 %12
  273. OpReturn
  274. OpFunctionEnd
  275. )";
  276. const std::string kDonorModule = R"(
  277. OpCapability Shader
  278. %1 = OpExtInstImport "GLSL.std.450"
  279. OpMemoryModel Logical GLSL450
  280. OpEntryPoint Fragment %4 "main"
  281. OpExecutionMode %4 OriginUpperLeft
  282. OpSource ESSL 320
  283. %2 = OpTypeVoid
  284. %3 = OpTypeFunction %2
  285. %6 = OpTypeInt 32 1
  286. %48 = OpConstant %6 3
  287. %4 = OpFunction %2 None %3
  288. %5 = OpLabel
  289. %52 = OpCopyObject %6 %48
  290. %53 = OpCopyObject %6 %52
  291. %54 = OpCopyObject %6 %53
  292. %55 = OpCopyObject %6 %54
  293. %56 = OpCopyObject %6 %55
  294. %57 = OpCopyObject %6 %56
  295. %58 = OpCopyObject %6 %48
  296. %59 = OpCopyObject %6 %58
  297. %60 = OpCopyObject %6 %59
  298. %61 = OpCopyObject %6 %60
  299. %62 = OpCopyObject %6 %61
  300. %63 = OpCopyObject %6 %62
  301. %64 = OpCopyObject %6 %48
  302. OpReturn
  303. OpFunctionEnd
  304. )";
  305. spv_target_env env = SPV_ENV_UNIVERSAL_1_3;
  306. const auto consumer = fuzzerutil::kSilentMessageConsumer;
  307. SpirvTools tools(env);
  308. std::vector<uint32_t> reference_binary;
  309. ASSERT_TRUE(
  310. tools.Assemble(kReferenceModule, &reference_binary, kFuzzAssembleOption));
  311. spvtools::ValidatorOptions validator_options;
  312. const auto variant_ir_context =
  313. BuildModule(env, consumer, kReferenceModule, kFuzzAssembleOption);
  314. ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
  315. variant_ir_context.get(), validator_options, kConsoleMessageConsumer));
  316. const auto donor_ir_context =
  317. BuildModule(env, consumer, kDonorModule, kFuzzAssembleOption);
  318. ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
  319. donor_ir_context.get(), validator_options, kConsoleMessageConsumer));
  320. FuzzerContext fuzzer_context(MakeUnique<PseudoRandomGenerator>(0), 100,
  321. false);
  322. TransformationContext transformation_context(
  323. MakeUnique<FactManager>(variant_ir_context.get()), validator_options);
  324. protobufs::TransformationSequence transformations;
  325. FuzzerPassDonateModules pass(variant_ir_context.get(),
  326. &transformation_context, &fuzzer_context,
  327. &transformations, false, {});
  328. pass.DonateSingleModule(donor_ir_context.get(), true);
  329. protobufs::FactSequence no_facts;
  330. Shrinker::InterestingnessFunction interestingness_function =
  331. [consumer, env](const std::vector<uint32_t>& binary,
  332. uint32_t /*unused*/) -> bool {
  333. auto temp_ir_context =
  334. BuildModule(env, consumer, binary.data(), binary.size());
  335. uint32_t copy_object_count = 0;
  336. temp_ir_context->module()->ForEachInst(
  337. [&copy_object_count](opt::Instruction* inst) {
  338. if (inst->opcode() == spv::Op::OpCopyObject) {
  339. copy_object_count++;
  340. }
  341. });
  342. return copy_object_count >= 8;
  343. };
  344. auto shrinker_result =
  345. Shrinker(env, consumer, reference_binary, no_facts, transformations,
  346. interestingness_function, 30, true, validator_options)
  347. .Run();
  348. ASSERT_EQ(Shrinker::ShrinkerResultStatus::kStepLimitReached,
  349. shrinker_result.status);
  350. }
  351. } // namespace
  352. } // namespace fuzz
  353. } // namespace spvtools