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 #include "source/reduce/reduction_opportunity.h"
17 #include "source/reduce/remove_instruction_reduction_opportunity.h"
18 #include "test/reduce/reduce_test_util.h"
19 
20 namespace spvtools {
21 namespace reduce {
22 namespace {
23 
24 using opt::Function;
25 using opt::IRContext;
26 using opt::Instruction;
27 
28 // A reduction opportunity finder that finds opportunities to remove global
29 // values regardless of whether they are referenced. This is very likely to make
30 // the resulting module invalid.  We use this to test the reducer's behavior in
31 // the scenario where a bad reduction pass leads to an invalid module.
32 class BlindlyRemoveGlobalValuesReductionOpportunityFinder
33     : public ReductionOpportunityFinder {
34  public:
35   BlindlyRemoveGlobalValuesReductionOpportunityFinder() = default;
36 
37   ~BlindlyRemoveGlobalValuesReductionOpportunityFinder() override = default;
38 
39   // The name of this pass.
GetName() const40   std::string GetName() const final { return "BlindlyRemoveGlobalValuesPass"; }
41 
42   // Finds opportunities to remove all global values.  Assuming they are all
43   // referenced (directly or indirectly) from elsewhere in the module, each such
44   // opportunity will make the module invalid.
GetAvailableOpportunities(IRContext * context,uint32_t) const45   std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
46       IRContext* context, uint32_t /*unused*/) const final {
47     std::vector<std::unique_ptr<ReductionOpportunity>> result;
48     for (auto& inst : context->module()->types_values()) {
49       if (inst.HasResultId()) {
50         result.push_back(
51             MakeUnique<RemoveInstructionReductionOpportunity>(&inst));
52       }
53     }
54     return result;
55   }
56 };
57 
58 // A reduction opportunity that exists at the start of every function whose
59 // first instruction is an OpVariable instruction. When applied, the OpVariable
60 // instruction is duplicated (with a fresh result id). This allows each
61 // reduction step to increase the number of variables to check if the validator
62 // limits are enforced.
63 class OpVariableDuplicatorReductionOpportunity : public ReductionOpportunity {
64  public:
OpVariableDuplicatorReductionOpportunity(Function * function)65   OpVariableDuplicatorReductionOpportunity(Function* function)
66       : function_(function) {}
67 
PreconditionHolds()68   bool PreconditionHolds() override {
69     Instruction* first_instruction = &*function_->begin()[0].begin();
70     return first_instruction->opcode() == SpvOpVariable;
71   }
72 
73  protected:
Apply()74   void Apply() override {
75     // Duplicate the first OpVariable instruction.
76 
77     Instruction* first_instruction = &*function_->begin()[0].begin();
78     assert(first_instruction->opcode() == SpvOpVariable &&
79            "Expected first instruction to be OpVariable");
80     IRContext* context = first_instruction->context();
81     Instruction* cloned_instruction = first_instruction->Clone(context);
82     cloned_instruction->SetResultId(context->TakeNextId());
83     cloned_instruction->InsertBefore(first_instruction);
84   }
85 
86  private:
87   Function* function_;
88 };
89 
90 // A reduction opportunity finder that finds
91 // OpVariableDuplicatorReductionOpportunity.
92 class OpVariableDuplicatorReductionOpportunityFinder
93     : public ReductionOpportunityFinder {
94  public:
95   OpVariableDuplicatorReductionOpportunityFinder() = default;
96 
97   ~OpVariableDuplicatorReductionOpportunityFinder() override = default;
98 
GetName() const99   std::string GetName() const final {
100     return "LocalVariableAdderReductionOpportunityFinder";
101   }
102 
GetAvailableOpportunities(IRContext * context,uint32_t) const103   std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
104       IRContext* context, uint32_t /*unused*/) const final {
105     std::vector<std::unique_ptr<ReductionOpportunity>> result;
106     for (auto& function : *context->module()) {
107       Instruction* first_instruction = &*function.begin()[0].begin();
108       if (first_instruction->opcode() == SpvOpVariable) {
109         result.push_back(
110             MakeUnique<OpVariableDuplicatorReductionOpportunity>(&function));
111       }
112     }
113     return result;
114   }
115 };
116 
TEST(ValidationDuringReductionTest,CheckInvalidPassMakesNoProgress)117 TEST(ValidationDuringReductionTest, CheckInvalidPassMakesNoProgress) {
118   // A module whose global values are all referenced, so that any application of
119   // MakeModuleInvalidPass will make the module invalid. Check that the reducer
120   // makes no progress, as every step will be invalid and treated as
121   // uninteresting.
122   std::string original = R"(
123                OpCapability Shader
124           %1 = OpExtInstImport "GLSL.std.450"
125                OpMemoryModel Logical GLSL450
126                OpEntryPoint Fragment %4 "main" %60
127                OpExecutionMode %4 OriginUpperLeft
128                OpSource ESSL 310
129                OpName %4 "main"
130                OpName %16 "buf2"
131                OpMemberName %16 0 "i"
132                OpName %18 ""
133                OpName %25 "buf1"
134                OpMemberName %25 0 "f"
135                OpName %27 ""
136                OpName %60 "_GLF_color"
137                OpMemberDecorate %16 0 Offset 0
138                OpDecorate %16 Block
139                OpDecorate %18 DescriptorSet 0
140                OpDecorate %18 Binding 2
141                OpMemberDecorate %25 0 Offset 0
142                OpDecorate %25 Block
143                OpDecorate %27 DescriptorSet 0
144                OpDecorate %27 Binding 1
145                OpDecorate %60 Location 0
146           %2 = OpTypeVoid
147           %3 = OpTypeFunction %2
148           %6 = OpTypeInt 32 1
149           %9 = OpConstant %6 0
150          %16 = OpTypeStruct %6
151          %17 = OpTypePointer Uniform %16
152          %18 = OpVariable %17 Uniform
153          %19 = OpTypePointer Uniform %6
154          %22 = OpTypeBool
155          %24 = OpTypeFloat 32
156          %25 = OpTypeStruct %24
157          %26 = OpTypePointer Uniform %25
158          %27 = OpVariable %26 Uniform
159          %28 = OpTypePointer Uniform %24
160          %31 = OpConstant %24 2
161          %56 = OpConstant %6 1
162          %58 = OpTypeVector %24 4
163          %59 = OpTypePointer Output %58
164          %60 = OpVariable %59 Output
165          %72 = OpUndef %24
166          %74 = OpUndef %6
167           %4 = OpFunction %2 None %3
168           %5 = OpLabel
169                OpBranch %10
170          %10 = OpLabel
171          %73 = OpPhi %6 %74 %5 %77 %34
172          %71 = OpPhi %24 %72 %5 %76 %34
173          %70 = OpPhi %6 %9 %5 %57 %34
174          %20 = OpAccessChain %19 %18 %9
175          %21 = OpLoad %6 %20
176          %23 = OpSLessThan %22 %70 %21
177                OpLoopMerge %12 %34 None
178                OpBranchConditional %23 %11 %12
179          %11 = OpLabel
180          %29 = OpAccessChain %28 %27 %9
181          %30 = OpLoad %24 %29
182          %32 = OpFOrdGreaterThan %22 %30 %31
183                OpSelectionMerge %90 None
184                OpBranchConditional %32 %33 %46
185          %33 = OpLabel
186          %40 = OpFAdd %24 %71 %30
187          %45 = OpISub %6 %73 %21
188                OpBranch %90
189          %46 = OpLabel
190          %50 = OpFMul %24 %71 %30
191          %54 = OpSDiv %6 %73 %21
192                OpBranch %90
193          %90 = OpLabel
194          %77 = OpPhi %6 %45 %33 %54 %46
195          %76 = OpPhi %24 %40 %33 %50 %46
196                OpBranch %34
197          %34 = OpLabel
198          %57 = OpIAdd %6 %70 %56
199                OpBranch %10
200          %12 = OpLabel
201          %61 = OpAccessChain %28 %27 %9
202          %62 = OpLoad %24 %61
203          %66 = OpConvertSToF %24 %21
204          %68 = OpConvertSToF %24 %73
205          %69 = OpCompositeConstruct %58 %62 %71 %66 %68
206                OpStore %60 %69
207                OpReturn
208                OpFunctionEnd
209   )";
210 
211   spv_target_env env = SPV_ENV_UNIVERSAL_1_3;
212   Reducer reducer(env);
213   reducer.SetMessageConsumer(NopDiagnostic);
214 
215   // Say that every module is interesting.
216   reducer.SetInterestingnessFunction(
217       [](const std::vector<uint32_t>&, uint32_t) -> bool { return true; });
218 
219   reducer.AddReductionPass(
220       MakeUnique<BlindlyRemoveGlobalValuesReductionOpportunityFinder>());
221 
222   std::vector<uint32_t> binary_in;
223   SpirvTools t(env);
224 
225   ASSERT_TRUE(t.Assemble(original, &binary_in, kReduceAssembleOption));
226   std::vector<uint32_t> binary_out;
227   spvtools::ReducerOptions reducer_options;
228   reducer_options.set_step_limit(500);
229   // Don't fail on a validation error; just treat it as uninteresting.
230   reducer_options.set_fail_on_validation_error(false);
231   spvtools::ValidatorOptions validator_options;
232 
233   Reducer::ReductionResultStatus status = reducer.Run(
234       std::move(binary_in), &binary_out, reducer_options, validator_options);
235 
236   ASSERT_EQ(status, Reducer::ReductionResultStatus::kComplete);
237 
238   // The reducer should have no impact.
239   CheckEqual(env, original, binary_out);
240 }
241 
TEST(ValidationDuringReductionTest,CheckNotAlwaysInvalidCanMakeProgress)242 TEST(ValidationDuringReductionTest, CheckNotAlwaysInvalidCanMakeProgress) {
243   // A module with just one unreferenced global value.  All but one application
244   // of MakeModuleInvalidPass will make the module invalid.
245   std::string original = R"(
246                OpCapability Shader
247           %1 = OpExtInstImport "GLSL.std.450"
248                OpMemoryModel Logical GLSL450
249                OpEntryPoint Fragment %4 "main" %60
250                OpExecutionMode %4 OriginUpperLeft
251                OpSource ESSL 310
252                OpName %4 "main"
253                OpName %16 "buf2"
254                OpMemberName %16 0 "i"
255                OpName %18 ""
256                OpName %25 "buf1"
257                OpMemberName %25 0 "f"
258                OpName %27 ""
259                OpName %60 "_GLF_color"
260                OpMemberDecorate %16 0 Offset 0
261                OpDecorate %16 Block
262                OpDecorate %18 DescriptorSet 0
263                OpDecorate %18 Binding 2
264                OpMemberDecorate %25 0 Offset 0
265                OpDecorate %25 Block
266                OpDecorate %27 DescriptorSet 0
267                OpDecorate %27 Binding 1
268                OpDecorate %60 Location 0
269           %2 = OpTypeVoid
270           %3 = OpTypeFunction %2
271           %6 = OpTypeInt 32 1
272           %9 = OpConstant %6 0
273          %16 = OpTypeStruct %6
274          %17 = OpTypePointer Uniform %16
275          %18 = OpVariable %17 Uniform
276          %19 = OpTypePointer Uniform %6
277          %22 = OpTypeBool
278          %24 = OpTypeFloat 32
279          %25 = OpTypeStruct %24
280          %26 = OpTypePointer Uniform %25
281          %27 = OpVariable %26 Uniform
282          %28 = OpTypePointer Uniform %24
283          %31 = OpConstant %24 2
284          %56 = OpConstant %6 1
285        %1000 = OpConstant %6 1000 ; It should be possible to remove this instruction without making the module invalid.
286          %58 = OpTypeVector %24 4
287          %59 = OpTypePointer Output %58
288          %60 = OpVariable %59 Output
289          %72 = OpUndef %24
290          %74 = OpUndef %6
291           %4 = OpFunction %2 None %3
292           %5 = OpLabel
293                OpBranch %10
294          %10 = OpLabel
295          %73 = OpPhi %6 %74 %5 %77 %34
296          %71 = OpPhi %24 %72 %5 %76 %34
297          %70 = OpPhi %6 %9 %5 %57 %34
298          %20 = OpAccessChain %19 %18 %9
299          %21 = OpLoad %6 %20
300          %23 = OpSLessThan %22 %70 %21
301                OpLoopMerge %12 %34 None
302                OpBranchConditional %23 %11 %12
303          %11 = OpLabel
304          %29 = OpAccessChain %28 %27 %9
305          %30 = OpLoad %24 %29
306          %32 = OpFOrdGreaterThan %22 %30 %31
307                OpSelectionMerge %90 None
308                OpBranchConditional %32 %33 %46
309          %33 = OpLabel
310          %40 = OpFAdd %24 %71 %30
311          %45 = OpISub %6 %73 %21
312                OpBranch %90
313          %46 = OpLabel
314          %50 = OpFMul %24 %71 %30
315          %54 = OpSDiv %6 %73 %21
316                OpBranch %90
317          %90 = OpLabel
318          %77 = OpPhi %6 %45 %33 %54 %46
319          %76 = OpPhi %24 %40 %33 %50 %46
320                OpBranch %34
321          %34 = OpLabel
322          %57 = OpIAdd %6 %70 %56
323                OpBranch %10
324          %12 = OpLabel
325          %61 = OpAccessChain %28 %27 %9
326          %62 = OpLoad %24 %61
327          %66 = OpConvertSToF %24 %21
328          %68 = OpConvertSToF %24 %73
329          %69 = OpCompositeConstruct %58 %62 %71 %66 %68
330                OpStore %60 %69
331                OpReturn
332                OpFunctionEnd
333   )";
334 
335   // This is the same as the original, except that the constant declaration of
336   // 1000 is gone.
337   std::string expected = R"(
338                OpCapability Shader
339           %1 = OpExtInstImport "GLSL.std.450"
340                OpMemoryModel Logical GLSL450
341                OpEntryPoint Fragment %4 "main" %60
342                OpExecutionMode %4 OriginUpperLeft
343                OpSource ESSL 310
344                OpName %4 "main"
345                OpName %16 "buf2"
346                OpMemberName %16 0 "i"
347                OpName %18 ""
348                OpName %25 "buf1"
349                OpMemberName %25 0 "f"
350                OpName %27 ""
351                OpName %60 "_GLF_color"
352                OpMemberDecorate %16 0 Offset 0
353                OpDecorate %16 Block
354                OpDecorate %18 DescriptorSet 0
355                OpDecorate %18 Binding 2
356                OpMemberDecorate %25 0 Offset 0
357                OpDecorate %25 Block
358                OpDecorate %27 DescriptorSet 0
359                OpDecorate %27 Binding 1
360                OpDecorate %60 Location 0
361           %2 = OpTypeVoid
362           %3 = OpTypeFunction %2
363           %6 = OpTypeInt 32 1
364           %9 = OpConstant %6 0
365          %16 = OpTypeStruct %6
366          %17 = OpTypePointer Uniform %16
367          %18 = OpVariable %17 Uniform
368          %19 = OpTypePointer Uniform %6
369          %22 = OpTypeBool
370          %24 = OpTypeFloat 32
371          %25 = OpTypeStruct %24
372          %26 = OpTypePointer Uniform %25
373          %27 = OpVariable %26 Uniform
374          %28 = OpTypePointer Uniform %24
375          %31 = OpConstant %24 2
376          %56 = OpConstant %6 1
377          %58 = OpTypeVector %24 4
378          %59 = OpTypePointer Output %58
379          %60 = OpVariable %59 Output
380          %72 = OpUndef %24
381          %74 = OpUndef %6
382           %4 = OpFunction %2 None %3
383           %5 = OpLabel
384                OpBranch %10
385          %10 = OpLabel
386          %73 = OpPhi %6 %74 %5 %77 %34
387          %71 = OpPhi %24 %72 %5 %76 %34
388          %70 = OpPhi %6 %9 %5 %57 %34
389          %20 = OpAccessChain %19 %18 %9
390          %21 = OpLoad %6 %20
391          %23 = OpSLessThan %22 %70 %21
392                OpLoopMerge %12 %34 None
393                OpBranchConditional %23 %11 %12
394          %11 = OpLabel
395          %29 = OpAccessChain %28 %27 %9
396          %30 = OpLoad %24 %29
397          %32 = OpFOrdGreaterThan %22 %30 %31
398                OpSelectionMerge %90 None
399                OpBranchConditional %32 %33 %46
400          %33 = OpLabel
401          %40 = OpFAdd %24 %71 %30
402          %45 = OpISub %6 %73 %21
403                OpBranch %90
404          %46 = OpLabel
405          %50 = OpFMul %24 %71 %30
406          %54 = OpSDiv %6 %73 %21
407                OpBranch %90
408          %90 = OpLabel
409          %77 = OpPhi %6 %45 %33 %54 %46
410          %76 = OpPhi %24 %40 %33 %50 %46
411                OpBranch %34
412          %34 = OpLabel
413          %57 = OpIAdd %6 %70 %56
414                OpBranch %10
415          %12 = OpLabel
416          %61 = OpAccessChain %28 %27 %9
417          %62 = OpLoad %24 %61
418          %66 = OpConvertSToF %24 %21
419          %68 = OpConvertSToF %24 %73
420          %69 = OpCompositeConstruct %58 %62 %71 %66 %68
421                OpStore %60 %69
422                OpReturn
423                OpFunctionEnd
424   )";
425 
426   spv_target_env env = SPV_ENV_UNIVERSAL_1_3;
427   Reducer reducer(env);
428   reducer.SetMessageConsumer(NopDiagnostic);
429 
430   // Say that every module is interesting.
431   reducer.SetInterestingnessFunction(
432       [](const std::vector<uint32_t>&, uint32_t) -> bool { return true; });
433 
434   reducer.AddReductionPass(
435       MakeUnique<BlindlyRemoveGlobalValuesReductionOpportunityFinder>());
436 
437   std::vector<uint32_t> binary_in;
438   SpirvTools t(env);
439 
440   ASSERT_TRUE(t.Assemble(original, &binary_in, kReduceAssembleOption));
441   std::vector<uint32_t> binary_out;
442   spvtools::ReducerOptions reducer_options;
443   reducer_options.set_step_limit(500);
444   // Don't fail on a validation error; just treat it as uninteresting.
445   reducer_options.set_fail_on_validation_error(false);
446   spvtools::ValidatorOptions validator_options;
447 
448   Reducer::ReductionResultStatus status = reducer.Run(
449       std::move(binary_in), &binary_out, reducer_options, validator_options);
450 
451   ASSERT_EQ(status, Reducer::ReductionResultStatus::kComplete);
452 
453   CheckEqual(env, expected, binary_out);
454 }
455 
456 // Sets up a Reducer for use in the CheckValidationOptions test; avoids
457 // repetition.
SetupReducerForCheckValidationOptions(Reducer * reducer)458 void SetupReducerForCheckValidationOptions(Reducer* reducer) {
459   reducer->SetMessageConsumer(NopDiagnostic);
460 
461   // Say that every module is interesting.
462   reducer->SetInterestingnessFunction(
463       [](const std::vector<uint32_t>&, uint32_t) -> bool { return true; });
464 
465   // Each "reduction" step will duplicate the first OpVariable instruction in
466   // the function.
467   reducer->AddReductionPass(
468       MakeUnique<OpVariableDuplicatorReductionOpportunityFinder>());
469 }
470 
TEST(ValidationDuringReductionTest,CheckValidationOptions)471 TEST(ValidationDuringReductionTest, CheckValidationOptions) {
472   // A module that only validates when the "skip-block-layout" validator option
473   // is used. Also, the entry point's first instruction creates a local
474   // variable; this instruction will be duplicated on each reduction step.
475   std::string original = R"(
476                OpCapability Shader
477           %1 = OpExtInstImport "GLSL.std.450"
478                OpMemoryModel Logical GLSL450
479                OpEntryPoint Vertex %2 "Main" %3
480                OpSource HLSL 600
481                OpDecorate %3 BuiltIn Position
482                OpDecorate %4 DescriptorSet 0
483                OpDecorate %4 Binding 99
484                OpDecorate %5 ArrayStride 16
485                OpMemberDecorate %6 0 Offset 0
486                OpMemberDecorate %6 1 Offset 32
487                OpMemberDecorate %6 1 MatrixStride 16
488                OpMemberDecorate %6 1 ColMajor
489                OpMemberDecorate %6 2 Offset 96
490                OpMemberDecorate %6 3 Offset 100
491                OpMemberDecorate %6 4 Offset 112
492                OpMemberDecorate %6 4 MatrixStride 16
493                OpMemberDecorate %6 4 ColMajor
494                OpMemberDecorate %6 5 Offset 176
495                OpDecorate %6 Block
496           %7 = OpTypeFloat 32
497           %8 = OpTypeVector %7 4
498           %9 = OpTypeMatrix %8 4
499          %10 = OpTypeVector %7 2
500          %11 = OpTypeInt 32 1
501          %12 = OpTypeInt 32 0
502          %13 = OpConstant %12 2
503          %14 = OpConstant %11 1
504          %15 = OpConstant %11 5
505           %5 = OpTypeArray %8 %13
506           %6 = OpTypeStruct %5 %9 %12 %10 %9 %7
507          %16 = OpTypePointer Uniform %6
508          %17 = OpTypePointer Output %8
509          %18 = OpTypeVoid
510          %19 = OpTypeFunction %18
511          %20 = OpTypePointer Uniform %7
512           %4 = OpVariable %16 Uniform
513           %3 = OpVariable %17 Output
514          %21 = OpTypePointer Function %11
515           %2 = OpFunction %18 None %19
516          %22 = OpLabel
517          %23 = OpVariable %21 Function
518          %24 = OpAccessChain %20 %4 %15
519          %25 = OpLoad %7 %24
520          %26 = OpCompositeConstruct %8 %25 %25 %25 %25
521                OpStore %3 %26
522                OpReturn
523                OpFunctionEnd
524   )";
525 
526   spv_target_env env = SPV_ENV_UNIVERSAL_1_3;
527   std::vector<uint32_t> binary_in;
528   SpirvTools t(env);
529 
530   ASSERT_TRUE(t.Assemble(original, &binary_in, kReduceAssembleOption));
531   std::vector<uint32_t> binary_out;
532   spvtools::ReducerOptions reducer_options;
533   spvtools::ValidatorOptions validator_options;
534 
535   reducer_options.set_step_limit(3);
536   reducer_options.set_fail_on_validation_error(true);
537 
538   // Reduction should fail because the initial state is invalid without the
539   // "skip-block-layout" validator option. Note that the interestingness test
540   // always returns true.
541   {
542     Reducer reducer(env);
543     SetupReducerForCheckValidationOptions(&reducer);
544 
545     Reducer::ReductionResultStatus status =
546         reducer.Run(std::vector<uint32_t>(binary_in), &binary_out,
547                     reducer_options, validator_options);
548 
549     ASSERT_EQ(status, Reducer::ReductionResultStatus::kInitialStateInvalid);
550   }
551 
552   // Try again with validator option.
553   validator_options.SetSkipBlockLayout(true);
554 
555   // Reduction should hit step limit; module is seen as valid, interestingness
556   // test always succeeds, and the finder yields infinite opportunities.
557   {
558     Reducer reducer(env);
559     SetupReducerForCheckValidationOptions(&reducer);
560 
561     Reducer::ReductionResultStatus status =
562         reducer.Run(std::vector<uint32_t>(binary_in), &binary_out,
563                     reducer_options, validator_options);
564 
565     ASSERT_EQ(status, Reducer::ReductionResultStatus::kReachedStepLimit);
566   }
567 
568   // Now set a limit on the number of local variables.
569   validator_options.SetUniversalLimit(spv_validator_limit_max_local_variables,
570                                       2);
571 
572   // Reduction should now fail due to reaching an invalid state; after one step,
573   // a local variable is added and the module becomes "invalid" given the
574   // validator limits.
575   {
576     Reducer reducer(env);
577     SetupReducerForCheckValidationOptions(&reducer);
578 
579     Reducer::ReductionResultStatus status =
580         reducer.Run(std::vector<uint32_t>(binary_in), &binary_out,
581                     reducer_options, validator_options);
582 
583     ASSERT_EQ(status, Reducer::ReductionResultStatus::kStateInvalid);
584   }
585 }
586 
587 }  // namespace
588 }  // namespace reduce
589 }  // namespace spvtools
590