1 // Copyright (c) 2018 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
15 #include "source/reduce/reducer.h"
16
17 #include "source/opt/build_module.h"
18 #include "source/reduce/operand_to_const_reduction_opportunity_finder.h"
19 #include "source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.h"
20 #include "test/reduce/reduce_test_util.h"
21
22 namespace spvtools {
23 namespace reduce {
24 namespace {
25
26 using opt::BasicBlock;
27 using opt::IRContext;
28
29 const spv_target_env kEnv = SPV_ENV_UNIVERSAL_1_3;
30 const MessageConsumer kMessageConsumer = CLIMessageConsumer;
31
32 // This changes its mind each time IsInteresting is invoked as to whether the
33 // binary is interesting, until some limit is reached after which the binary is
34 // always deemed interesting. This is useful to test that reduction passes
35 // interleave in interesting ways for a while, and then always succeed after
36 // some point; the latter is important to end up with a predictable final
37 // reduced binary for tests.
38 class PingPongInteresting {
39 public:
PingPongInteresting(uint32_t always_interesting_after)40 explicit PingPongInteresting(uint32_t always_interesting_after)
41 : is_interesting_(true),
42 always_interesting_after_(always_interesting_after),
43 count_(0) {}
44
IsInteresting(const std::vector<uint32_t> &)45 bool IsInteresting(const std::vector<uint32_t>&) {
46 bool result;
47 if (count_ > always_interesting_after_) {
48 result = true;
49 } else {
50 result = is_interesting_;
51 is_interesting_ = !is_interesting_;
52 }
53 count_++;
54 return result;
55 }
56
57 private:
58 bool is_interesting_;
59 const uint32_t always_interesting_after_;
60 uint32_t count_;
61 };
62
TEST(ReducerTest,ExprToConstantAndRemoveUnreferenced)63 TEST(ReducerTest, ExprToConstantAndRemoveUnreferenced) {
64 // Check that ExprToConstant and RemoveUnreferenced work together; once some
65 // ID uses have been changed to constants, those IDs can be removed.
66 std::string original = R"(
67 OpCapability Shader
68 %1 = OpExtInstImport "GLSL.std.450"
69 OpMemoryModel Logical GLSL450
70 OpEntryPoint Fragment %4 "main" %60
71 OpExecutionMode %4 OriginUpperLeft
72 OpSource ESSL 310
73 OpName %4 "main"
74 OpName %16 "buf2"
75 OpMemberName %16 0 "i"
76 OpName %18 ""
77 OpName %25 "buf1"
78 OpMemberName %25 0 "f"
79 OpName %27 ""
80 OpName %60 "_GLF_color"
81 OpMemberDecorate %16 0 Offset 0
82 OpDecorate %16 Block
83 OpDecorate %18 DescriptorSet 0
84 OpDecorate %18 Binding 2
85 OpMemberDecorate %25 0 Offset 0
86 OpDecorate %25 Block
87 OpDecorate %27 DescriptorSet 0
88 OpDecorate %27 Binding 1
89 OpDecorate %60 Location 0
90 %2 = OpTypeVoid
91 %3 = OpTypeFunction %2
92 %6 = OpTypeInt 32 1
93 %9 = OpConstant %6 0
94 %16 = OpTypeStruct %6
95 %17 = OpTypePointer Uniform %16
96 %18 = OpVariable %17 Uniform
97 %19 = OpTypePointer Uniform %6
98 %22 = OpTypeBool
99 %100 = OpConstantTrue %22
100 %24 = OpTypeFloat 32
101 %25 = OpTypeStruct %24
102 %26 = OpTypePointer Uniform %25
103 %27 = OpVariable %26 Uniform
104 %28 = OpTypePointer Uniform %24
105 %31 = OpConstant %24 2
106 %56 = OpConstant %6 1
107 %58 = OpTypeVector %24 4
108 %59 = OpTypePointer Output %58
109 %60 = OpVariable %59 Output
110 %72 = OpUndef %24
111 %74 = OpUndef %6
112 %4 = OpFunction %2 None %3
113 %5 = OpLabel
114 OpBranch %10
115 %10 = OpLabel
116 %73 = OpPhi %6 %74 %5 %77 %34
117 %71 = OpPhi %24 %72 %5 %76 %34
118 %70 = OpPhi %6 %9 %5 %57 %34
119 %20 = OpAccessChain %19 %18 %9
120 %21 = OpLoad %6 %20
121 %23 = OpSLessThan %22 %70 %21
122 OpLoopMerge %12 %34 None
123 OpBranchConditional %23 %11 %12
124 %11 = OpLabel
125 %29 = OpAccessChain %28 %27 %9
126 %30 = OpLoad %24 %29
127 %32 = OpFOrdGreaterThan %22 %30 %31
128 OpSelectionMerge %90 None
129 OpBranchConditional %32 %33 %46
130 %33 = OpLabel
131 %40 = OpFAdd %24 %71 %30
132 %45 = OpISub %6 %73 %21
133 OpBranch %90
134 %46 = OpLabel
135 %50 = OpFMul %24 %71 %30
136 %54 = OpSDiv %6 %73 %21
137 OpBranch %90
138 %90 = OpLabel
139 %77 = OpPhi %6 %45 %33 %54 %46
140 %76 = OpPhi %24 %40 %33 %50 %46
141 OpBranch %34
142 %34 = OpLabel
143 %57 = OpIAdd %6 %70 %56
144 OpBranch %10
145 %12 = OpLabel
146 %61 = OpAccessChain %28 %27 %9
147 %62 = OpLoad %24 %61
148 %66 = OpConvertSToF %24 %21
149 %68 = OpConvertSToF %24 %73
150 %69 = OpCompositeConstruct %58 %62 %71 %66 %68
151 OpStore %60 %69
152 OpReturn
153 OpFunctionEnd
154 )";
155
156 std::string expected = R"(
157 OpCapability Shader
158 %1 = OpExtInstImport "GLSL.std.450"
159 OpMemoryModel Logical GLSL450
160 OpEntryPoint Fragment %4 "main" %60
161 OpExecutionMode %4 OriginUpperLeft
162 OpMemberDecorate %16 0 Offset 0
163 OpDecorate %16 Block
164 OpDecorate %18 DescriptorSet 0
165 OpDecorate %18 Binding 2
166 OpMemberDecorate %25 0 Offset 0
167 OpDecorate %25 Block
168 OpDecorate %27 DescriptorSet 0
169 OpDecorate %27 Binding 1
170 OpDecorate %60 Location 0
171 %2 = OpTypeVoid
172 %3 = OpTypeFunction %2
173 %6 = OpTypeInt 32 1
174 %9 = OpConstant %6 0
175 %16 = OpTypeStruct %6
176 %17 = OpTypePointer Uniform %16
177 %18 = OpVariable %17 Uniform
178 %22 = OpTypeBool
179 %100 = OpConstantTrue %22
180 %24 = OpTypeFloat 32
181 %25 = OpTypeStruct %24
182 %26 = OpTypePointer Uniform %25
183 %27 = OpVariable %26 Uniform
184 %31 = OpConstant %24 2
185 %56 = OpConstant %6 1
186 %58 = OpTypeVector %24 4
187 %59 = OpTypePointer Output %58
188 %60 = OpVariable %59 Output
189 %72 = OpUndef %24
190 %74 = OpUndef %6
191 %4 = OpFunction %2 None %3
192 %5 = OpLabel
193 OpBranch %10
194 %10 = OpLabel
195 OpLoopMerge %12 %34 None
196 OpBranchConditional %100 %11 %12
197 %11 = OpLabel
198 OpSelectionMerge %90 None
199 OpBranchConditional %100 %33 %46
200 %33 = OpLabel
201 OpBranch %90
202 %46 = OpLabel
203 OpBranch %90
204 %90 = OpLabel
205 OpBranch %34
206 %34 = OpLabel
207 OpBranch %10
208 %12 = OpLabel
209 OpReturn
210 OpFunctionEnd
211 )";
212
213 Reducer reducer(kEnv);
214 PingPongInteresting ping_pong_interesting(10);
215 reducer.SetMessageConsumer(NopDiagnostic);
216 reducer.SetInterestingnessFunction(
217 [&](const std::vector<uint32_t>& binary, uint32_t) -> bool {
218 return ping_pong_interesting.IsInteresting(binary);
219 });
220 reducer.AddReductionPass(
221 MakeUnique<RemoveUnreferencedInstructionReductionOpportunityFinder>(
222 false));
223 reducer.AddReductionPass(
224 MakeUnique<OperandToConstReductionOpportunityFinder>());
225
226 std::vector<uint32_t> binary_in;
227 SpirvTools t(kEnv);
228
229 ASSERT_TRUE(t.Assemble(original, &binary_in, kReduceAssembleOption));
230 std::vector<uint32_t> binary_out;
231 spvtools::ReducerOptions reducer_options;
232 reducer_options.set_step_limit(500);
233 reducer_options.set_fail_on_validation_error(true);
234 spvtools::ValidatorOptions validator_options;
235
236 Reducer::ReductionResultStatus status = reducer.Run(
237 std::move(binary_in), &binary_out, reducer_options, validator_options);
238
239 ASSERT_EQ(status, Reducer::ReductionResultStatus::kComplete);
240
241 CheckEqual(kEnv, expected, binary_out);
242 }
243
InterestingWhileOpcodeExists(const std::vector<uint32_t> & binary,uint32_t opcode,uint32_t count,bool dump)244 bool InterestingWhileOpcodeExists(const std::vector<uint32_t>& binary,
245 uint32_t opcode, uint32_t count, bool dump) {
246 if (dump) {
247 std::stringstream ss;
248 ss << "temp_" << count << ".spv";
249 DumpShader(binary, ss.str().c_str());
250 }
251
252 std::unique_ptr<IRContext> context =
253 BuildModule(kEnv, kMessageConsumer, binary.data(), binary.size());
254 assert(context);
255 bool interesting = false;
256 for (auto& function : *context->module()) {
257 context->cfg()->ForEachBlockInPostOrder(
258 &*function.begin(), [opcode, &interesting](BasicBlock* block) -> void {
259 for (auto& inst : *block) {
260 if (inst.opcode() == opcode) {
261 interesting = true;
262 break;
263 }
264 }
265 });
266 if (interesting) {
267 break;
268 }
269 }
270 return interesting;
271 }
272
InterestingWhileIMulReachable(const std::vector<uint32_t> & binary,uint32_t count)273 bool InterestingWhileIMulReachable(const std::vector<uint32_t>& binary,
274 uint32_t count) {
275 return InterestingWhileOpcodeExists(binary, SpvOpIMul, count, false);
276 }
277
InterestingWhileSDivReachable(const std::vector<uint32_t> & binary,uint32_t count)278 bool InterestingWhileSDivReachable(const std::vector<uint32_t>& binary,
279 uint32_t count) {
280 return InterestingWhileOpcodeExists(binary, SpvOpSDiv, count, false);
281 }
282
283 // The shader below was derived from the following GLSL, and optimized.
284 // #version 310 es
285 // precision highp float;
286 // layout(location = 0) out vec4 _GLF_color;
287 // int foo() {
288 // int x = 1;
289 // int y;
290 // x = y / x; // SDiv
291 // return x;
292 // }
293 // void main() {
294 // int c;
295 // while (bool(c)) {
296 // do {
297 // if (bool(c)) {
298 // if (bool(c)) {
299 // ++c;
300 // } else {
301 // _GLF_color.x = float(c*c); // IMul
302 // }
303 // return;
304 // }
305 // } while(bool(foo()));
306 // return;
307 // }
308 // }
309 const std::string kShaderWithLoopsDivAndMul = R"(
310 OpCapability Shader
311 %1 = OpExtInstImport "GLSL.std.450"
312 OpMemoryModel Logical GLSL450
313 OpEntryPoint Fragment %4 "main" %49
314 OpExecutionMode %4 OriginUpperLeft
315 OpSource ESSL 310
316 OpName %4 "main"
317 OpName %49 "_GLF_color"
318 OpDecorate %49 Location 0
319 OpDecorate %52 RelaxedPrecision
320 OpDecorate %77 RelaxedPrecision
321 %2 = OpTypeVoid
322 %3 = OpTypeFunction %2
323 %6 = OpTypeInt 32 1
324 %12 = OpConstant %6 1
325 %27 = OpTypeBool
326 %28 = OpTypeInt 32 0
327 %29 = OpConstant %28 0
328 %46 = OpTypeFloat 32
329 %47 = OpTypeVector %46 4
330 %48 = OpTypePointer Output %47
331 %49 = OpVariable %48 Output
332 %54 = OpTypePointer Output %46
333 %64 = OpConstantFalse %27
334 %67 = OpConstantTrue %27
335 %81 = OpUndef %6
336 %4 = OpFunction %2 None %3
337 %5 = OpLabel
338 OpBranch %61
339 %61 = OpLabel
340 OpLoopMerge %60 %63 None
341 OpBranch %20
342 %20 = OpLabel
343 %30 = OpINotEqual %27 %81 %29
344 OpLoopMerge %22 %23 None
345 OpBranchConditional %30 %21 %22
346 %21 = OpLabel
347 OpBranch %31
348 %31 = OpLabel
349 OpLoopMerge %33 %38 None
350 OpBranch %32
351 %32 = OpLabel
352 OpBranchConditional %30 %37 %38
353 %37 = OpLabel
354 OpSelectionMerge %42 None
355 OpBranchConditional %30 %41 %45
356 %41 = OpLabel
357 OpBranch %42
358 %45 = OpLabel
359 %52 = OpIMul %6 %81 %81
360 %53 = OpConvertSToF %46 %52
361 %55 = OpAccessChain %54 %49 %29
362 OpStore %55 %53
363 OpBranch %42
364 %42 = OpLabel
365 OpBranch %33
366 %38 = OpLabel
367 %77 = OpSDiv %6 %81 %12
368 %58 = OpINotEqual %27 %77 %29
369 OpBranchConditional %58 %31 %33
370 %33 = OpLabel
371 %86 = OpPhi %27 %67 %42 %64 %38
372 OpSelectionMerge %68 None
373 OpBranchConditional %86 %22 %68
374 %68 = OpLabel
375 OpBranch %22
376 %23 = OpLabel
377 OpBranch %20
378 %22 = OpLabel
379 %90 = OpPhi %27 %64 %20 %86 %33 %67 %68
380 OpSelectionMerge %70 None
381 OpBranchConditional %90 %60 %70
382 %70 = OpLabel
383 OpBranch %60
384 %63 = OpLabel
385 OpBranch %61
386 %60 = OpLabel
387 OpReturn
388 OpFunctionEnd
389 )";
390
TEST(ReducerTest,ShaderReduceWhileMulReachable)391 TEST(ReducerTest, ShaderReduceWhileMulReachable) {
392 Reducer reducer(kEnv);
393
394 reducer.SetInterestingnessFunction(InterestingWhileIMulReachable);
395 reducer.AddDefaultReductionPasses();
396 reducer.SetMessageConsumer(kMessageConsumer);
397
398 std::vector<uint32_t> binary_in;
399 SpirvTools t(kEnv);
400
401 ASSERT_TRUE(
402 t.Assemble(kShaderWithLoopsDivAndMul, &binary_in, kReduceAssembleOption));
403 std::vector<uint32_t> binary_out;
404 spvtools::ReducerOptions reducer_options;
405 reducer_options.set_step_limit(500);
406 reducer_options.set_fail_on_validation_error(true);
407 spvtools::ValidatorOptions validator_options;
408
409 Reducer::ReductionResultStatus status = reducer.Run(
410 std::move(binary_in), &binary_out, reducer_options, validator_options);
411
412 ASSERT_EQ(status, Reducer::ReductionResultStatus::kComplete);
413 }
414
TEST(ReducerTest,ShaderReduceWhileDivReachable)415 TEST(ReducerTest, ShaderReduceWhileDivReachable) {
416 Reducer reducer(kEnv);
417
418 reducer.SetInterestingnessFunction(InterestingWhileSDivReachable);
419 reducer.AddDefaultReductionPasses();
420 reducer.SetMessageConsumer(kMessageConsumer);
421
422 std::vector<uint32_t> binary_in;
423 SpirvTools t(kEnv);
424
425 ASSERT_TRUE(
426 t.Assemble(kShaderWithLoopsDivAndMul, &binary_in, kReduceAssembleOption));
427 std::vector<uint32_t> binary_out;
428 spvtools::ReducerOptions reducer_options;
429 reducer_options.set_step_limit(500);
430 reducer_options.set_fail_on_validation_error(true);
431 spvtools::ValidatorOptions validator_options;
432
433 Reducer::ReductionResultStatus status = reducer.Run(
434 std::move(binary_in), &binary_out, reducer_options, validator_options);
435
436 ASSERT_EQ(status, Reducer::ReductionResultStatus::kComplete);
437 }
438
439 } // namespace
440 } // namespace reduce
441 } // namespace spvtools
442