strength_reduction_test.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433
  1. // Copyright (c) 2017 Google Inc.
  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 <string>
  15. #include <unordered_set>
  16. #include <vector>
  17. #include "gmock/gmock.h"
  18. #include "test/opt/pass_fixture.h"
  19. #include "test/opt/pass_utils.h"
  20. namespace spvtools {
  21. namespace opt {
  22. namespace {
  23. using ::testing::HasSubstr;
  24. using ::testing::MatchesRegex;
  25. using StrengthReductionBasicTest = PassTest<::testing::Test>;
  26. // Test to make sure we replace 5*8.
  27. TEST_F(StrengthReductionBasicTest, BasicReplaceMulBy8) {
  28. const std::vector<const char*> text = {
  29. // clang-format off
  30. "OpCapability Shader",
  31. "%1 = OpExtInstImport \"GLSL.std.450\"",
  32. "OpMemoryModel Logical GLSL450",
  33. "OpEntryPoint Vertex %main \"main\"",
  34. "OpName %main \"main\"",
  35. "%void = OpTypeVoid",
  36. "%4 = OpTypeFunction %void",
  37. "%uint = OpTypeInt 32 0",
  38. "%uint_5 = OpConstant %uint 5",
  39. "%uint_8 = OpConstant %uint 8",
  40. "%main = OpFunction %void None %4",
  41. "%8 = OpLabel",
  42. "%9 = OpIMul %uint %uint_5 %uint_8",
  43. "OpReturn",
  44. "OpFunctionEnd"
  45. // clang-format on
  46. };
  47. auto result = SinglePassRunAndDisassemble<StrengthReductionPass>(
  48. JoinAllInsts(text), /* skip_nop = */ true, /* do_validation = */ false);
  49. EXPECT_EQ(Pass::Status::SuccessWithChange, std::get<1>(result));
  50. const std::string& output = std::get<0>(result);
  51. EXPECT_THAT(output, Not(HasSubstr("OpIMul")));
  52. EXPECT_THAT(output, HasSubstr("OpShiftLeftLogical %uint %uint_5 %uint_3"));
  53. }
  54. // TODO(dneto): Add Effcee as required dependency, and make this unconditional.
  55. // Test to make sure we replace 16*5
  56. // Also demonstrate use of Effcee matching.
  57. TEST_F(StrengthReductionBasicTest, BasicReplaceMulBy16) {
  58. const std::string text = R"(
  59. OpCapability Shader
  60. %1 = OpExtInstImport "GLSL.std.450"
  61. OpMemoryModel Logical GLSL450
  62. OpEntryPoint Vertex %main "main"
  63. OpName %main "main"
  64. %void = OpTypeVoid
  65. %4 = OpTypeFunction %void
  66. ; We know disassembly will produce %uint here, but
  67. ; CHECK: %uint = OpTypeInt 32 0
  68. ; CHECK-DAG: [[five:%[a-zA-Z_\d]+]] = OpConstant %uint 5
  69. ; We have RE2 regular expressions, so \w matches [_a-zA-Z0-9].
  70. ; This shows the preferred pattern for matching SPIR-V identifiers.
  71. ; (We could have cheated in this case since we know the disassembler will
  72. ; generate the 'nice' name of "%uint_4".
  73. ; CHECK-DAG: [[four:%\w+]] = OpConstant %uint 4
  74. %uint = OpTypeInt 32 0
  75. %uint_5 = OpConstant %uint 5
  76. %uint_16 = OpConstant %uint 16
  77. %main = OpFunction %void None %4
  78. ; CHECK: OpLabel
  79. %8 = OpLabel
  80. ; CHECK-NEXT: OpShiftLeftLogical %uint [[five]] [[four]]
  81. ; The multiplication disappears.
  82. ; CHECK-NOT: OpIMul
  83. %9 = OpIMul %uint %uint_16 %uint_5
  84. OpReturn
  85. ; CHECK: OpFunctionEnd
  86. OpFunctionEnd)";
  87. SinglePassRunAndMatch<StrengthReductionPass>(text, false);
  88. }
  89. // Test to make sure we replace a multiple of 32 and 4.
  90. TEST_F(StrengthReductionBasicTest, BasicTwoPowersOf2) {
  91. // In this case, we have two powers of 2. Need to make sure we replace only
  92. // one of them for the bit shift.
  93. // clang-format off
  94. const std::string text = R"(
  95. OpCapability Shader
  96. %1 = OpExtInstImport "GLSL.std.450"
  97. OpMemoryModel Logical GLSL450
  98. OpEntryPoint Vertex %main "main"
  99. OpName %main "main"
  100. %void = OpTypeVoid
  101. %4 = OpTypeFunction %void
  102. %int = OpTypeInt 32 1
  103. %int_32 = OpConstant %int 32
  104. %int_4 = OpConstant %int 4
  105. %main = OpFunction %void None %4
  106. %8 = OpLabel
  107. %9 = OpIMul %int %int_32 %int_4
  108. OpReturn
  109. OpFunctionEnd
  110. )";
  111. // clang-format on
  112. auto result = SinglePassRunAndDisassemble<StrengthReductionPass>(
  113. text, /* skip_nop = */ true, /* do_validation = */ false);
  114. EXPECT_EQ(Pass::Status::SuccessWithChange, std::get<1>(result));
  115. const std::string& output = std::get<0>(result);
  116. EXPECT_THAT(output, Not(HasSubstr("OpIMul")));
  117. EXPECT_THAT(output, HasSubstr("OpShiftLeftLogical %int %int_4 %uint_5"));
  118. }
  119. // Test to make sure we don't replace 0*5.
  120. TEST_F(StrengthReductionBasicTest, BasicDontReplace0) {
  121. const std::vector<const char*> text = {
  122. // clang-format off
  123. "OpCapability Shader",
  124. "%1 = OpExtInstImport \"GLSL.std.450\"",
  125. "OpMemoryModel Logical GLSL450",
  126. "OpEntryPoint Vertex %main \"main\"",
  127. "OpName %main \"main\"",
  128. "%void = OpTypeVoid",
  129. "%4 = OpTypeFunction %void",
  130. "%int = OpTypeInt 32 1",
  131. "%int_0 = OpConstant %int 0",
  132. "%int_5 = OpConstant %int 5",
  133. "%main = OpFunction %void None %4",
  134. "%8 = OpLabel",
  135. "%9 = OpIMul %int %int_0 %int_5",
  136. "OpReturn",
  137. "OpFunctionEnd"
  138. // clang-format on
  139. };
  140. auto result = SinglePassRunAndDisassemble<StrengthReductionPass>(
  141. JoinAllInsts(text), /* skip_nop = */ true, /* do_validation = */ false);
  142. EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
  143. }
  144. // Test to make sure we do not replace a multiple of 5 and 7.
  145. TEST_F(StrengthReductionBasicTest, BasicNoChange) {
  146. const std::vector<const char*> text = {
  147. // clang-format off
  148. "OpCapability Shader",
  149. "%1 = OpExtInstImport \"GLSL.std.450\"",
  150. "OpMemoryModel Logical GLSL450",
  151. "OpEntryPoint Vertex %2 \"main\"",
  152. "OpName %2 \"main\"",
  153. "%3 = OpTypeVoid",
  154. "%4 = OpTypeFunction %3",
  155. "%5 = OpTypeInt 32 1",
  156. "%6 = OpTypeInt 32 0",
  157. "%7 = OpConstant %5 5",
  158. "%8 = OpConstant %5 7",
  159. "%2 = OpFunction %3 None %4",
  160. "%9 = OpLabel",
  161. "%10 = OpIMul %5 %7 %8",
  162. "OpReturn",
  163. "OpFunctionEnd",
  164. // clang-format on
  165. };
  166. auto result = SinglePassRunAndDisassemble<StrengthReductionPass>(
  167. JoinAllInsts(text), /* skip_nop = */ true, /* do_validation = */ false);
  168. EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
  169. }
  170. // Test to make sure constants and types are reused and not duplicated.
  171. TEST_F(StrengthReductionBasicTest, NoDuplicateConstantsAndTypes) {
  172. const std::vector<const char*> text = {
  173. // clang-format off
  174. "OpCapability Shader",
  175. "%1 = OpExtInstImport \"GLSL.std.450\"",
  176. "OpMemoryModel Logical GLSL450",
  177. "OpEntryPoint Vertex %main \"main\"",
  178. "OpName %main \"main\"",
  179. "%void = OpTypeVoid",
  180. "%4 = OpTypeFunction %void",
  181. "%uint = OpTypeInt 32 0",
  182. "%uint_8 = OpConstant %uint 8",
  183. "%uint_3 = OpConstant %uint 3",
  184. "%main = OpFunction %void None %4",
  185. "%8 = OpLabel",
  186. "%9 = OpIMul %uint %uint_8 %uint_3",
  187. "OpReturn",
  188. "OpFunctionEnd",
  189. // clang-format on
  190. };
  191. auto result = SinglePassRunAndDisassemble<StrengthReductionPass>(
  192. JoinAllInsts(text), /* skip_nop = */ true, /* do_validation = */ false);
  193. EXPECT_EQ(Pass::Status::SuccessWithChange, std::get<1>(result));
  194. const std::string& output = std::get<0>(result);
  195. EXPECT_THAT(output,
  196. Not(MatchesRegex(".*OpConstant %uint 3.*OpConstant %uint 3.*")));
  197. EXPECT_THAT(output, Not(MatchesRegex(".*OpTypeInt 32 0.*OpTypeInt 32 0.*")));
  198. }
  199. // Test to make sure we generate the constants only once
  200. TEST_F(StrengthReductionBasicTest, BasicCreateOneConst) {
  201. const std::vector<const char*> text = {
  202. // clang-format off
  203. "OpCapability Shader",
  204. "%1 = OpExtInstImport \"GLSL.std.450\"",
  205. "OpMemoryModel Logical GLSL450",
  206. "OpEntryPoint Vertex %main \"main\"",
  207. "OpName %main \"main\"",
  208. "%void = OpTypeVoid",
  209. "%4 = OpTypeFunction %void",
  210. "%uint = OpTypeInt 32 0",
  211. "%uint_5 = OpConstant %uint 5",
  212. "%uint_9 = OpConstant %uint 9",
  213. "%uint_128 = OpConstant %uint 128",
  214. "%main = OpFunction %void None %4",
  215. "%8 = OpLabel",
  216. "%9 = OpIMul %uint %uint_5 %uint_128",
  217. "%10 = OpIMul %uint %uint_9 %uint_128",
  218. "OpReturn",
  219. "OpFunctionEnd"
  220. // clang-format on
  221. };
  222. auto result = SinglePassRunAndDisassemble<StrengthReductionPass>(
  223. JoinAllInsts(text), /* skip_nop = */ true, /* do_validation = */ false);
  224. EXPECT_EQ(Pass::Status::SuccessWithChange, std::get<1>(result));
  225. const std::string& output = std::get<0>(result);
  226. EXPECT_THAT(output, Not(HasSubstr("OpIMul")));
  227. EXPECT_THAT(output, HasSubstr("OpShiftLeftLogical %uint %uint_5 %uint_7"));
  228. EXPECT_THAT(output, HasSubstr("OpShiftLeftLogical %uint %uint_9 %uint_7"));
  229. }
  230. // Test to make sure we generate the instructions in the correct position and
  231. // that the uses get replaced as well. Here we check that the use in the return
  232. // is replaced, we also check that we can replace two OpIMuls when one feeds the
  233. // other.
  234. TEST_F(StrengthReductionBasicTest, BasicCheckPositionAndReplacement) {
  235. // This is just the preamble to set up the test.
  236. const std::vector<const char*> common_text = {
  237. // clang-format off
  238. "OpCapability Shader",
  239. "%1 = OpExtInstImport \"GLSL.std.450\"",
  240. "OpMemoryModel Logical GLSL450",
  241. "OpEntryPoint Fragment %main \"main\" %gl_FragColor",
  242. "OpExecutionMode %main OriginUpperLeft",
  243. "OpName %main \"main\"",
  244. "OpName %foo_i1_ \"foo(i1;\"",
  245. "OpName %n \"n\"",
  246. "OpName %gl_FragColor \"gl_FragColor\"",
  247. "OpName %param \"param\"",
  248. "OpDecorate %gl_FragColor Location 0",
  249. "%void = OpTypeVoid",
  250. "%3 = OpTypeFunction %void",
  251. "%int = OpTypeInt 32 1",
  252. "%_ptr_Function_int = OpTypePointer Function %int",
  253. "%8 = OpTypeFunction %int %_ptr_Function_int",
  254. "%int_256 = OpConstant %int 256",
  255. "%int_2 = OpConstant %int 2",
  256. "%float = OpTypeFloat 32",
  257. "%v4float = OpTypeVector %float 4",
  258. "%_ptr_Output_v4float = OpTypePointer Output %v4float",
  259. "%gl_FragColor = OpVariable %_ptr_Output_v4float Output",
  260. "%float_1 = OpConstant %float 1",
  261. "%int_10 = OpConstant %int 10",
  262. "%float_0_375 = OpConstant %float 0.375",
  263. "%float_0_75 = OpConstant %float 0.75",
  264. "%uint = OpTypeInt 32 0",
  265. "%uint_8 = OpConstant %uint 8",
  266. "%uint_1 = OpConstant %uint 1",
  267. "%main = OpFunction %void None %3",
  268. "%5 = OpLabel",
  269. "%param = OpVariable %_ptr_Function_int Function",
  270. "OpStore %param %int_10",
  271. "%26 = OpFunctionCall %int %foo_i1_ %param",
  272. "%27 = OpConvertSToF %float %26",
  273. "%28 = OpFDiv %float %float_1 %27",
  274. "%31 = OpCompositeConstruct %v4float %28 %float_0_375 %float_0_75 %float_1",
  275. "OpStore %gl_FragColor %31",
  276. "OpReturn",
  277. "OpFunctionEnd"
  278. // clang-format on
  279. };
  280. // This is the real test. The two OpIMul should be replaced. The expected
  281. // output is in |foo_after|.
  282. const std::vector<const char*> foo_before = {
  283. // clang-format off
  284. "%foo_i1_ = OpFunction %int None %8",
  285. "%n = OpFunctionParameter %_ptr_Function_int",
  286. "%11 = OpLabel",
  287. "%12 = OpLoad %int %n",
  288. "%14 = OpIMul %int %12 %int_256",
  289. "%16 = OpIMul %int %14 %int_2",
  290. "OpReturnValue %16",
  291. "OpFunctionEnd",
  292. // clang-format on
  293. };
  294. const std::vector<const char*> foo_after = {
  295. // clang-format off
  296. "%foo_i1_ = OpFunction %int None %8",
  297. "%n = OpFunctionParameter %_ptr_Function_int",
  298. "%11 = OpLabel",
  299. "%12 = OpLoad %int %n",
  300. "%33 = OpShiftLeftLogical %int %12 %uint_8",
  301. "%34 = OpShiftLeftLogical %int %33 %uint_1",
  302. "OpReturnValue %34",
  303. "OpFunctionEnd",
  304. // clang-format on
  305. };
  306. SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
  307. SinglePassRunAndCheck<StrengthReductionPass>(
  308. JoinAllInsts(Concat(common_text, foo_before)),
  309. JoinAllInsts(Concat(common_text, foo_after)),
  310. /* skip_nop = */ true, /* do_validate = */ true);
  311. }
  312. // Test that, when the result of an OpIMul instruction has more than 1 use, and
  313. // the instruction is replaced, all of the uses of the results are replace with
  314. // the new result.
  315. TEST_F(StrengthReductionBasicTest, BasicTestMultipleReplacements) {
  316. // This is just the preamble to set up the test.
  317. const std::vector<const char*> common_text = {
  318. // clang-format off
  319. "OpCapability Shader",
  320. "%1 = OpExtInstImport \"GLSL.std.450\"",
  321. "OpMemoryModel Logical GLSL450",
  322. "OpEntryPoint Fragment %main \"main\" %gl_FragColor",
  323. "OpExecutionMode %main OriginUpperLeft",
  324. "OpName %main \"main\"",
  325. "OpName %foo_i1_ \"foo(i1;\"",
  326. "OpName %n \"n\"",
  327. "OpName %gl_FragColor \"gl_FragColor\"",
  328. "OpName %param \"param\"",
  329. "OpDecorate %gl_FragColor Location 0",
  330. "%void = OpTypeVoid",
  331. "%3 = OpTypeFunction %void",
  332. "%int = OpTypeInt 32 1",
  333. "%_ptr_Function_int = OpTypePointer Function %int",
  334. "%8 = OpTypeFunction %int %_ptr_Function_int",
  335. "%int_256 = OpConstant %int 256",
  336. "%int_2 = OpConstant %int 2",
  337. "%float = OpTypeFloat 32",
  338. "%v4float = OpTypeVector %float 4",
  339. "%_ptr_Output_v4float = OpTypePointer Output %v4float",
  340. "%gl_FragColor = OpVariable %_ptr_Output_v4float Output",
  341. "%float_1 = OpConstant %float 1",
  342. "%int_10 = OpConstant %int 10",
  343. "%float_0_375 = OpConstant %float 0.375",
  344. "%float_0_75 = OpConstant %float 0.75",
  345. "%uint = OpTypeInt 32 0",
  346. "%uint_8 = OpConstant %uint 8",
  347. "%uint_1 = OpConstant %uint 1",
  348. "%main = OpFunction %void None %3",
  349. "%5 = OpLabel",
  350. "%param = OpVariable %_ptr_Function_int Function",
  351. "OpStore %param %int_10",
  352. "%26 = OpFunctionCall %int %foo_i1_ %param",
  353. "%27 = OpConvertSToF %float %26",
  354. "%28 = OpFDiv %float %float_1 %27",
  355. "%31 = OpCompositeConstruct %v4float %28 %float_0_375 %float_0_75 %float_1",
  356. "OpStore %gl_FragColor %31",
  357. "OpReturn",
  358. "OpFunctionEnd"
  359. // clang-format on
  360. };
  361. // This is the real test. The two OpIMul instructions should be replaced. In
  362. // particular, we want to be sure that both uses of %16 are changed to use the
  363. // new result.
  364. const std::vector<const char*> foo_before = {
  365. // clang-format off
  366. "%foo_i1_ = OpFunction %int None %8",
  367. "%n = OpFunctionParameter %_ptr_Function_int",
  368. "%11 = OpLabel",
  369. "%12 = OpLoad %int %n",
  370. "%14 = OpIMul %int %12 %int_256",
  371. "%16 = OpIMul %int %14 %int_2",
  372. "%17 = OpIAdd %int %14 %16",
  373. "OpReturnValue %17",
  374. "OpFunctionEnd",
  375. // clang-format on
  376. };
  377. const std::vector<const char*> foo_after = {
  378. // clang-format off
  379. "%foo_i1_ = OpFunction %int None %8",
  380. "%n = OpFunctionParameter %_ptr_Function_int",
  381. "%11 = OpLabel",
  382. "%12 = OpLoad %int %n",
  383. "%34 = OpShiftLeftLogical %int %12 %uint_8",
  384. "%35 = OpShiftLeftLogical %int %34 %uint_1",
  385. "%17 = OpIAdd %int %34 %35",
  386. "OpReturnValue %17",
  387. "OpFunctionEnd",
  388. // clang-format on
  389. };
  390. SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
  391. SinglePassRunAndCheck<StrengthReductionPass>(
  392. JoinAllInsts(Concat(common_text, foo_before)),
  393. JoinAllInsts(Concat(common_text, foo_after)),
  394. /* skip_nop = */ true, /* do_validate = */ true);
  395. }
  396. } // namespace
  397. } // namespace opt
  398. } // namespace spvtools