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
15 #include <string>
16 #include <vector>
17
18 #include "gmock/gmock.h"
19 #include "spirv-tools/libspirv.hpp"
20 #include "spirv-tools/optimizer.hpp"
21 #include "test/opt/pass_fixture.h"
22
23 namespace spvtools {
24 namespace opt {
25 namespace {
26
27 using ::testing::Eq;
28
29 // Return a string that contains the minimum instructions needed to form
30 // a valid module. Other instructions can be appended to this string.
Header()31 std::string Header() {
32 return R"(OpCapability Shader
33 OpCapability Linkage
34 OpMemoryModel Logical GLSL450
35 )";
36 }
37
TEST(Optimizer,CanRunNullPassWithDistinctInputOutputVectors)38 TEST(Optimizer, CanRunNullPassWithDistinctInputOutputVectors) {
39 SpirvTools tools(SPV_ENV_UNIVERSAL_1_0);
40 std::vector<uint32_t> binary_in;
41 tools.Assemble(Header() + "OpName %foo \"foo\"\n%foo = OpTypeVoid",
42 &binary_in);
43
44 Optimizer opt(SPV_ENV_UNIVERSAL_1_0);
45 opt.RegisterPass(CreateNullPass());
46 std::vector<uint32_t> binary_out;
47 opt.Run(binary_in.data(), binary_in.size(), &binary_out);
48
49 std::string disassembly;
50 tools.Disassemble(binary_out.data(), binary_out.size(), &disassembly);
51 EXPECT_THAT(disassembly,
52 Eq(Header() + "OpName %foo \"foo\"\n%foo = OpTypeVoid\n"));
53 }
54
TEST(Optimizer,CanRunTransformingPassWithDistinctInputOutputVectors)55 TEST(Optimizer, CanRunTransformingPassWithDistinctInputOutputVectors) {
56 SpirvTools tools(SPV_ENV_UNIVERSAL_1_0);
57 std::vector<uint32_t> binary_in;
58 tools.Assemble(Header() + "OpName %foo \"foo\"\n%foo = OpTypeVoid",
59 &binary_in);
60
61 Optimizer opt(SPV_ENV_UNIVERSAL_1_0);
62 opt.RegisterPass(CreateStripDebugInfoPass());
63 std::vector<uint32_t> binary_out;
64 opt.Run(binary_in.data(), binary_in.size(), &binary_out);
65
66 std::string disassembly;
67 tools.Disassemble(binary_out.data(), binary_out.size(), &disassembly);
68 EXPECT_THAT(disassembly, Eq(Header() + "%void = OpTypeVoid\n"));
69 }
70
TEST(Optimizer,CanRunNullPassWithAliasedVectors)71 TEST(Optimizer, CanRunNullPassWithAliasedVectors) {
72 SpirvTools tools(SPV_ENV_UNIVERSAL_1_0);
73 std::vector<uint32_t> binary;
74 tools.Assemble("OpName %foo \"foo\"\n%foo = OpTypeVoid", &binary);
75
76 Optimizer opt(SPV_ENV_UNIVERSAL_1_0);
77 opt.RegisterPass(CreateNullPass());
78 opt.Run(binary.data(), binary.size(), &binary); // This is the key.
79
80 std::string disassembly;
81 tools.Disassemble(binary.data(), binary.size(), &disassembly);
82 EXPECT_THAT(disassembly, Eq("OpName %foo \"foo\"\n%foo = OpTypeVoid\n"));
83 }
84
TEST(Optimizer,CanRunNullPassWithAliasedVectorDataButDifferentSize)85 TEST(Optimizer, CanRunNullPassWithAliasedVectorDataButDifferentSize) {
86 SpirvTools tools(SPV_ENV_UNIVERSAL_1_0);
87 std::vector<uint32_t> binary;
88 tools.Assemble(Header() + "OpName %foo \"foo\"\n%foo = OpTypeVoid", &binary);
89
90 Optimizer opt(SPV_ENV_UNIVERSAL_1_0);
91 opt.RegisterPass(CreateNullPass());
92 auto orig_size = binary.size();
93 // Now change the size. Add a word that will be ignored
94 // by the optimizer.
95 binary.push_back(42);
96 EXPECT_THAT(orig_size + 1, Eq(binary.size()));
97 opt.Run(binary.data(), orig_size, &binary); // This is the key.
98 // The binary vector should have been rewritten.
99 EXPECT_THAT(binary.size(), Eq(orig_size));
100
101 std::string disassembly;
102 tools.Disassemble(binary.data(), binary.size(), &disassembly);
103 EXPECT_THAT(disassembly,
104 Eq(Header() + "OpName %foo \"foo\"\n%foo = OpTypeVoid\n"));
105 }
106
TEST(Optimizer,CanRunTransformingPassWithAliasedVectors)107 TEST(Optimizer, CanRunTransformingPassWithAliasedVectors) {
108 SpirvTools tools(SPV_ENV_UNIVERSAL_1_0);
109 std::vector<uint32_t> binary;
110 tools.Assemble(Header() + "OpName %foo \"foo\"\n%foo = OpTypeVoid", &binary);
111
112 Optimizer opt(SPV_ENV_UNIVERSAL_1_0);
113 opt.RegisterPass(CreateStripDebugInfoPass());
114 opt.Run(binary.data(), binary.size(), &binary); // This is the key
115
116 std::string disassembly;
117 tools.Disassemble(binary.data(), binary.size(), &disassembly);
118 EXPECT_THAT(disassembly, Eq(Header() + "%void = OpTypeVoid\n"));
119 }
120
TEST(Optimizer,CanValidateFlags)121 TEST(Optimizer, CanValidateFlags) {
122 Optimizer opt(SPV_ENV_UNIVERSAL_1_0);
123 EXPECT_FALSE(opt.FlagHasValidForm("bad-flag"));
124 EXPECT_TRUE(opt.FlagHasValidForm("-O"));
125 EXPECT_TRUE(opt.FlagHasValidForm("-Os"));
126 EXPECT_FALSE(opt.FlagHasValidForm("-O2"));
127 EXPECT_TRUE(opt.FlagHasValidForm("--this_flag"));
128 }
129
TEST(Optimizer,CanRegisterPassesFromFlags)130 TEST(Optimizer, CanRegisterPassesFromFlags) {
131 SpirvTools tools(SPV_ENV_UNIVERSAL_1_0);
132 Optimizer opt(SPV_ENV_UNIVERSAL_1_0);
133
134 spv_message_level_t msg_level;
135 const char* msg_fname;
136 spv_position_t msg_position;
137 const char* msg;
138 auto examine_message = [&msg_level, &msg_fname, &msg_position, &msg](
139 spv_message_level_t ml, const char* f,
140 const spv_position_t& p, const char* m) {
141 msg_level = ml;
142 msg_fname = f;
143 msg_position = p;
144 msg = m;
145 };
146 opt.SetMessageConsumer(examine_message);
147
148 std::vector<std::string> pass_flags = {
149 "--strip-debug",
150 "--strip-nonsemantic",
151 "--set-spec-const-default-value=23:42 21:12",
152 "--if-conversion",
153 "--freeze-spec-const",
154 "--inline-entry-points-exhaustive",
155 "--inline-entry-points-opaque",
156 "--convert-local-access-chains",
157 "--eliminate-dead-code-aggressive",
158 "--eliminate-insert-extract",
159 "--eliminate-local-single-block",
160 "--eliminate-local-single-store",
161 "--merge-blocks",
162 "--merge-return",
163 "--eliminate-dead-branches",
164 "--eliminate-dead-functions",
165 "--eliminate-local-multi-store",
166 "--eliminate-dead-const",
167 "--eliminate-dead-inserts",
168 "--eliminate-dead-variables",
169 "--fold-spec-const-op-composite",
170 "--loop-unswitch",
171 "--scalar-replacement=300",
172 "--scalar-replacement",
173 "--strength-reduction",
174 "--unify-const",
175 "--flatten-decorations",
176 "--compact-ids",
177 "--cfg-cleanup",
178 "--local-redundancy-elimination",
179 "--loop-invariant-code-motion",
180 "--reduce-load-size",
181 "--redundancy-elimination",
182 "--private-to-local",
183 "--remove-duplicates",
184 "--workaround-1209",
185 "--replace-invalid-opcode",
186 "--simplify-instructions",
187 "--ssa-rewrite",
188 "--copy-propagate-arrays",
189 "--loop-fission=20",
190 "--loop-fusion=2",
191 "--loop-unroll",
192 "--vector-dce",
193 "--loop-unroll-partial=3",
194 "--loop-peeling",
195 "--ccp",
196 "-O",
197 "-Os",
198 "--legalize-hlsl"};
199 EXPECT_TRUE(opt.RegisterPassesFromFlags(pass_flags));
200
201 // Test some invalid flags.
202 EXPECT_FALSE(opt.RegisterPassFromFlag("-O2"));
203 EXPECT_EQ(msg_level, SPV_MSG_ERROR);
204
205 EXPECT_FALSE(opt.RegisterPassFromFlag("-loop-unroll"));
206 EXPECT_EQ(msg_level, SPV_MSG_ERROR);
207
208 EXPECT_FALSE(opt.RegisterPassFromFlag("--set-spec-const-default-value"));
209 EXPECT_EQ(msg_level, SPV_MSG_ERROR);
210
211 EXPECT_FALSE(opt.RegisterPassFromFlag("--scalar-replacement=s"));
212 EXPECT_EQ(msg_level, SPV_MSG_ERROR);
213
214 EXPECT_FALSE(opt.RegisterPassFromFlag("--loop-fission=-4"));
215 EXPECT_EQ(msg_level, SPV_MSG_ERROR);
216
217 EXPECT_FALSE(opt.RegisterPassFromFlag("--loop-fusion=xx"));
218 EXPECT_EQ(msg_level, SPV_MSG_ERROR);
219
220 EXPECT_FALSE(opt.RegisterPassFromFlag("--loop-unroll-partial"));
221 EXPECT_EQ(msg_level, SPV_MSG_ERROR);
222 }
223
224
TEST(Optimizer,RemoveNop)225 TEST(Optimizer, RemoveNop) {
226 // Test that OpNops are removed even if no optimizations are run.
227 const std::string before = R"(OpCapability Shader
228 OpCapability Linkage
229 OpMemoryModel Logical GLSL450
230 %void = OpTypeVoid
231 %2 = OpTypeFunction %void
232 %3 = OpFunction %void None %2
233 %4 = OpLabel
234 OpNop
235 OpReturn
236 OpFunctionEnd
237 )";
238
239 const std::string after = R"(OpCapability Shader
240 OpCapability Linkage
241 OpMemoryModel Logical GLSL450
242 %void = OpTypeVoid
243 %2 = OpTypeFunction %void
244 %3 = OpFunction %void None %2
245 %4 = OpLabel
246 OpReturn
247 OpFunctionEnd
248 )";
249
250 std::vector<uint32_t> binary;
251 {
252 SpirvTools tools(SPV_ENV_VULKAN_1_1);
253 tools.Assemble(before, &binary);
254 }
255
256 Optimizer opt(SPV_ENV_VULKAN_1_1);
257
258 std::vector<uint32_t> optimized;
259 class ValidatorOptions validator_options;
260 ASSERT_TRUE(opt.Run(binary.data(), binary.size(), &optimized,
261 validator_options, true))
262 << before << "\n";
263 std::string disassembly;
264 {
265 SpirvTools tools(SPV_ENV_VULKAN_1_1);
266 tools.Disassemble(optimized.data(), optimized.size(), &disassembly);
267 }
268
269 EXPECT_EQ(after, disassembly)
270 << "Was expecting the OpNop to have been removed.";
271 }
272
TEST(Optimizer,AvoidIntegrityCheckForExtraLineInfo)273 TEST(Optimizer, AvoidIntegrityCheckForExtraLineInfo) {
274 // Test that it avoids the integrity check when no optimizations are run and
275 // OpLines are propagated.
276 const std::string before = R"(OpCapability Shader
277 OpCapability Linkage
278 OpMemoryModel Logical GLSL450
279 %1 = OpString "Test"
280 %void = OpTypeVoid
281 %3 = OpTypeFunction %void
282 %uint = OpTypeInt 32 0
283 %_ptr_Function_uint = OpTypePointer Function %uint
284 %6 = OpFunction %void None %3
285 %7 = OpLabel
286 OpLine %1 10 0
287 %8 = OpVariable %_ptr_Function_uint Function
288 OpLine %1 10 0
289 %9 = OpVariable %_ptr_Function_uint Function
290 OpLine %1 20 0
291 OpReturn
292 OpFunctionEnd
293 )";
294
295 const std::string after = R"(OpCapability Shader
296 OpCapability Linkage
297 OpMemoryModel Logical GLSL450
298 %1 = OpString "Test"
299 %void = OpTypeVoid
300 %3 = OpTypeFunction %void
301 %uint = OpTypeInt 32 0
302 %_ptr_Function_uint = OpTypePointer Function %uint
303 %6 = OpFunction %void None %3
304 %7 = OpLabel
305 OpLine %1 10 0
306 %8 = OpVariable %_ptr_Function_uint Function
307 %9 = OpVariable %_ptr_Function_uint Function
308 OpLine %1 20 0
309 OpReturn
310 OpFunctionEnd
311 )";
312
313 std::vector<uint32_t> binary;
314 SpirvTools tools(SPV_ENV_VULKAN_1_1);
315 tools.Assemble(before, &binary);
316
317 Optimizer opt(SPV_ENV_VULKAN_1_1);
318
319 std::vector<uint32_t> optimized;
320 class ValidatorOptions validator_options;
321 ASSERT_TRUE(opt.Run(binary.data(), binary.size(), &optimized,
322 validator_options, true))
323 << before << "\n";
324
325 std::string disassembly;
326 tools.Disassemble(optimized.data(), optimized.size(), &disassembly);
327
328 EXPECT_EQ(after, disassembly)
329 << "Was expecting the OpLine to have been propagated.";
330 }
331
TEST(Optimizer,AvoidIntegrityCheckForDebugScope)332 TEST(Optimizer, AvoidIntegrityCheckForDebugScope) {
333 // Test that it avoids the integrity check when the code contains DebugScope.
334 const std::string before = R"(OpCapability Shader
335 %1 = OpExtInstImport "OpenCL.DebugInfo.100"
336 OpMemoryModel Logical GLSL450
337 OpEntryPoint Fragment %main "main"
338 OpExecutionMode %main OriginUpperLeft
339 %3 = OpString "simple_vs.hlsl"
340 OpSource HLSL 600 %3
341 OpName %main "main"
342 %void = OpTypeVoid
343 %5 = OpTypeFunction %void
344 %6 = OpExtInst %void %1 DebugSource %3
345 %7 = OpExtInst %void %1 DebugCompilationUnit 2 4 %6 HLSL
346 %main = OpFunction %void None %5
347 %14 = OpLabel
348 %26 = OpExtInst %void %1 DebugScope %7
349 OpReturn
350 %27 = OpExtInst %void %1 DebugNoScope
351 OpFunctionEnd
352 )";
353
354 const std::string after = R"(OpCapability Shader
355 %1 = OpExtInstImport "OpenCL.DebugInfo.100"
356 OpMemoryModel Logical GLSL450
357 OpEntryPoint Fragment %main "main"
358 OpExecutionMode %main OriginUpperLeft
359 %3 = OpString "simple_vs.hlsl"
360 OpSource HLSL 600 %3
361 OpName %main "main"
362 %void = OpTypeVoid
363 %5 = OpTypeFunction %void
364 %6 = OpExtInst %void %1 DebugSource %3
365 %7 = OpExtInst %void %1 DebugCompilationUnit 2 4 %6 HLSL
366 %main = OpFunction %void None %5
367 %8 = OpLabel
368 %11 = OpExtInst %void %1 DebugScope %7
369 OpReturn
370 %12 = OpExtInst %void %1 DebugNoScope
371 OpFunctionEnd
372 )";
373
374 std::vector<uint32_t> binary;
375 SpirvTools tools(SPV_ENV_VULKAN_1_1);
376 tools.Assemble(before, &binary);
377
378 Optimizer opt(SPV_ENV_VULKAN_1_1);
379
380 std::vector<uint32_t> optimized;
381 ASSERT_TRUE(opt.Run(binary.data(), binary.size(), &optimized))
382 << before << "\n";
383
384 std::string disassembly;
385 tools.Disassemble(optimized.data(), optimized.size(), &disassembly);
386
387 EXPECT_EQ(after, disassembly)
388 << "Was expecting the result id of DebugScope to have been changed.";
389 }
390
TEST(Optimizer,CheckDefaultPerformancePassesLargeStructScalarization)391 TEST(Optimizer, CheckDefaultPerformancePassesLargeStructScalarization) {
392 std::string start = R"(OpCapability Shader
393 %1 = OpExtInstImport "GLSL.std.450"
394 OpMemoryModel Logical GLSL450
395 OpEntryPoint Vertex %4 "main" %46 %48
396 OpSource GLSL 430
397 OpName %4 "main"
398 OpDecorate %44 Block
399 OpMemberDecorate %44 0 BuiltIn Position
400 OpMemberDecorate %44 1 BuiltIn PointSize
401 OpMemberDecorate %44 2 BuiltIn ClipDistance
402 OpDecorate %48 Location 0
403 %2 = OpTypeVoid
404 %3 = OpTypeFunction %2
405 %6 = OpTypeFloat 32
406 %7 = OpTypeVector %6 4
407 %8 = OpTypePointer Function %7
408 %9 = OpTypeStruct %7)";
409
410 // add 200 float members to the struct
411 for (int i = 0; i < 200; i++) {
412 start += " %6";
413 }
414
415 start += R"(
416 %10 = OpTypeFunction %9 %8
417 %14 = OpTypeFunction %6 %9
418 %18 = OpTypePointer Function %9
419 %20 = OpTypeInt 32 1
420 %21 = OpConstant %20 0
421 %24 = OpConstant %20 1
422 %25 = OpTypeInt 32 0
423 %26 = OpConstant %25 1
424 %27 = OpTypePointer Function %6
425 %43 = OpTypeArray %6 %26
426 %44 = OpTypeStruct %7 %6 %43
427 %45 = OpTypePointer Output %44
428 %46 = OpVariable %45 Output
429 %47 = OpTypePointer Input %7
430 %48 = OpVariable %47 Input
431 %54 = OpTypePointer Output %7
432 %4 = OpFunction %2 None %3
433 %5 = OpLabel
434 %49 = OpVariable %8 Function
435 %50 = OpLoad %7 %48
436 OpStore %49 %50
437 %51 = OpFunctionCall %9 %12 %49
438 %52 = OpFunctionCall %6 %16 %51
439 %53 = OpCompositeConstruct %7 %52 %52 %52 %52
440 %55 = OpAccessChain %54 %46 %21
441 OpStore %55 %53
442 OpReturn
443 OpFunctionEnd
444 %12 = OpFunction %9 None %10
445 %11 = OpFunctionParameter %8
446 %13 = OpLabel
447 %19 = OpVariable %18 Function
448 %22 = OpLoad %7 %11
449 %23 = OpAccessChain %8 %19 %21
450 OpStore %23 %22
451 %28 = OpAccessChain %27 %11 %26
452 %29 = OpLoad %6 %28
453 %30 = OpConvertFToS %20 %29
454 %31 = OpAccessChain %27 %19 %21 %30
455 %32 = OpLoad %6 %31
456 %33 = OpAccessChain %27 %19 %24
457 OpStore %33 %32
458 %34 = OpLoad %9 %19
459 OpReturnValue %34
460 OpFunctionEnd
461 %16 = OpFunction %6 None %14
462 %15 = OpFunctionParameter %9
463 %17 = OpLabel
464 %37 = OpCompositeExtract %6 %15 1
465 %38 = OpConvertFToS %20 %37
466 %39 = OpCompositeExtract %7 %15 0
467 %40 = OpVectorExtractDynamic %6 %39 %38
468 OpReturnValue %40
469 OpFunctionEnd)";
470
471 std::vector<uint32_t> binary;
472 SpirvTools tools(SPV_ENV_VULKAN_1_3);
473 tools.Assemble(start, &binary,
474 SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
475
476 std::string test_disassembly;
477 std::string default_disassembly;
478
479 {
480 Optimizer opt(SPV_ENV_VULKAN_1_3);
481 opt.RegisterPerformancePasses();
482
483 std::vector<uint32_t> optimized;
484 ASSERT_TRUE(opt.Run(binary.data(), binary.size(), &optimized))
485 << start << "\n";
486
487 tools.Disassemble(optimized.data(), optimized.size(), &default_disassembly,
488 SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
489 }
490
491 {
492 // default passes should not benefit from additional scalar replacement
493 Optimizer opt(SPV_ENV_VULKAN_1_3);
494 opt.RegisterPerformancePasses()
495 .RegisterPass(CreateScalarReplacementPass(201))
496 .RegisterPass(CreateAggressiveDCEPass());
497
498 std::vector<uint32_t> optimized;
499 ASSERT_TRUE(opt.Run(binary.data(), binary.size(), &optimized))
500 << start << "\n";
501
502 tools.Disassemble(optimized.data(), optimized.size(), &test_disassembly,
503 SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
504 }
505
506 EXPECT_EQ(test_disassembly, default_disassembly);
507 }
508
509 } // namespace
510 } // namespace opt
511 } // namespace spvtools
512