// Copyright (c) 2019 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "source/fuzz/transformation_add_dead_break.h" #include "gtest/gtest.h" #include "source/fuzz/fuzzer_util.h" #include "test/fuzz/fuzz_test_util.h" namespace spvtools { namespace fuzz { namespace { TEST(TransformationAddDeadBreakTest, BreaksOutOfSimpleIf) { // For a simple if-then-else, checks that some dead break scenarios are // possible, and that some invalid scenarios are indeed not allowed. // The SPIR-V for this test is adapted from the following GLSL, by separating // some assignments into their own basic blocks, and adding constants for true // and false: // // void main() { // int x; // int y; // x = 1; // if (x < y) { // x = 2; // x = 3; // } else { // y = 2; // y = 3; // } // x = y; // } std::string shader = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource ESSL 310 OpName %4 "main" OpName %8 "x" OpName %11 "y" %2 = OpTypeVoid %3 = OpTypeFunction %2 %6 = OpTypeInt 32 1 %7 = OpTypePointer Function %6 %9 = OpConstant %6 1 %13 = OpTypeBool %17 = OpConstant %6 2 %18 = OpConstant %6 3 %25 = OpConstantTrue %13 %26 = OpConstantFalse %13 %4 = OpFunction %2 None %3 %5 = OpLabel %8 = OpVariable %7 Function %11 = OpVariable %7 Function OpStore %8 %9 %10 = OpLoad %6 %8 %12 = OpLoad %6 %11 %14 = OpSLessThan %13 %10 %12 OpSelectionMerge %16 None OpBranchConditional %14 %15 %19 %15 = OpLabel OpStore %8 %17 OpBranch %21 %21 = OpLabel OpStore %8 %18 OpBranch %22 %22 = OpLabel OpBranch %16 %19 = OpLabel OpStore %11 %17 OpBranch %23 %23 = OpLabel OpStore %11 %18 OpBranch %24 %24 = OpLabel OpBranch %16 %16 = OpLabel %20 = OpLoad %6 %11 OpStore %8 %20 OpReturn OpFunctionEnd )"; const auto env = SPV_ENV_UNIVERSAL_1_3; const auto consumer = nullptr; const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); spvtools::ValidatorOptions validator_options; ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); TransformationContext transformation_context( MakeUnique(context.get()), validator_options); const uint32_t merge_block = 16; // These are all possibilities. ASSERT_TRUE(TransformationAddDeadBreak(15, merge_block, true, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_TRUE(TransformationAddDeadBreak(15, merge_block, false, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_TRUE(TransformationAddDeadBreak(21, merge_block, true, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_TRUE(TransformationAddDeadBreak(21, merge_block, false, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_TRUE(TransformationAddDeadBreak(22, merge_block, true, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_TRUE(TransformationAddDeadBreak(22, merge_block, false, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_TRUE(TransformationAddDeadBreak(19, merge_block, true, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_TRUE(TransformationAddDeadBreak(19, merge_block, false, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_TRUE(TransformationAddDeadBreak(23, merge_block, true, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_TRUE(TransformationAddDeadBreak(23, merge_block, false, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_TRUE(TransformationAddDeadBreak(24, merge_block, true, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_TRUE(TransformationAddDeadBreak(24, merge_block, false, {}) .IsApplicable(context.get(), transformation_context)); // Inapplicable: 100 is not a block id. ASSERT_FALSE(TransformationAddDeadBreak(100, merge_block, true, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_FALSE(TransformationAddDeadBreak(15, 100, true, {}) .IsApplicable(context.get(), transformation_context)); // Inapplicable: 24 is not a merge block. ASSERT_FALSE(TransformationAddDeadBreak(15, 24, true, {}) .IsApplicable(context.get(), transformation_context)); // These are the transformations we will apply. auto transformation1 = TransformationAddDeadBreak(15, merge_block, true, {}); auto transformation2 = TransformationAddDeadBreak(21, merge_block, false, {}); auto transformation3 = TransformationAddDeadBreak(22, merge_block, true, {}); auto transformation4 = TransformationAddDeadBreak(19, merge_block, false, {}); auto transformation5 = TransformationAddDeadBreak(23, merge_block, true, {}); auto transformation6 = TransformationAddDeadBreak(24, merge_block, false, {}); ASSERT_TRUE( transformation1.IsApplicable(context.get(), transformation_context)); ApplyAndCheckFreshIds(transformation1, context.get(), &transformation_context); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); ASSERT_TRUE( transformation2.IsApplicable(context.get(), transformation_context)); ApplyAndCheckFreshIds(transformation2, context.get(), &transformation_context); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); ASSERT_TRUE( transformation3.IsApplicable(context.get(), transformation_context)); ApplyAndCheckFreshIds(transformation3, context.get(), &transformation_context); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); ASSERT_TRUE( transformation4.IsApplicable(context.get(), transformation_context)); ApplyAndCheckFreshIds(transformation4, context.get(), &transformation_context); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); ASSERT_TRUE( transformation5.IsApplicable(context.get(), transformation_context)); ApplyAndCheckFreshIds(transformation5, context.get(), &transformation_context); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); ASSERT_TRUE( transformation6.IsApplicable(context.get(), transformation_context)); ApplyAndCheckFreshIds(transformation6, context.get(), &transformation_context); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); std::string after_transformation = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource ESSL 310 OpName %4 "main" OpName %8 "x" OpName %11 "y" %2 = OpTypeVoid %3 = OpTypeFunction %2 %6 = OpTypeInt 32 1 %7 = OpTypePointer Function %6 %9 = OpConstant %6 1 %13 = OpTypeBool %17 = OpConstant %6 2 %18 = OpConstant %6 3 %25 = OpConstantTrue %13 %26 = OpConstantFalse %13 %4 = OpFunction %2 None %3 %5 = OpLabel %8 = OpVariable %7 Function %11 = OpVariable %7 Function OpStore %8 %9 %10 = OpLoad %6 %8 %12 = OpLoad %6 %11 %14 = OpSLessThan %13 %10 %12 OpSelectionMerge %16 None OpBranchConditional %14 %15 %19 %15 = OpLabel OpStore %8 %17 OpBranchConditional %25 %21 %16 %21 = OpLabel OpStore %8 %18 OpBranchConditional %26 %16 %22 %22 = OpLabel OpBranchConditional %25 %16 %16 %19 = OpLabel OpStore %11 %17 OpBranchConditional %26 %16 %23 %23 = OpLabel OpStore %11 %18 OpBranchConditional %25 %24 %16 %24 = OpLabel OpBranchConditional %26 %16 %16 %16 = OpLabel %20 = OpLoad %6 %11 OpStore %8 %20 OpReturn OpFunctionEnd )"; ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); } TEST(TransformationAddDeadBreakTest, BreakOutOfNestedIfs) { // Checks some allowed and disallowed scenarios for nests of ifs. // The SPIR-V for this test is adapted from the following GLSL: // // void main() { // int x; // int y; // x = 1; // if (x < y) { // x = 2; // x = 3; // if (x == y) { // y = 3; // } // } else { // y = 2; // y = 3; // } // if (x == y) { // x = 2; // } // x = y; // } std::string shader = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource ESSL 310 OpName %4 "main" OpName %8 "x" OpName %11 "y" %2 = OpTypeVoid %3 = OpTypeFunction %2 %6 = OpTypeInt 32 1 %7 = OpTypePointer Function %6 %9 = OpConstant %6 1 %13 = OpTypeBool %17 = OpConstant %6 2 %18 = OpConstant %6 3 %31 = OpConstantTrue %13 %32 = OpConstantFalse %13 %4 = OpFunction %2 None %3 %5 = OpLabel %8 = OpVariable %7 Function %11 = OpVariable %7 Function OpStore %8 %9 %10 = OpLoad %6 %8 %12 = OpLoad %6 %11 %14 = OpSLessThan %13 %10 %12 OpSelectionMerge %16 None OpBranchConditional %14 %15 %24 %15 = OpLabel OpStore %8 %17 OpBranch %33 %33 = OpLabel OpStore %8 %18 %19 = OpLoad %6 %8 OpBranch %34 %34 = OpLabel %20 = OpLoad %6 %11 %21 = OpIEqual %13 %19 %20 OpSelectionMerge %23 None OpBranchConditional %21 %22 %23 %22 = OpLabel OpStore %11 %18 OpBranch %35 %35 = OpLabel OpBranch %23 %23 = OpLabel OpBranch %16 %24 = OpLabel OpStore %11 %17 OpBranch %36 %36 = OpLabel OpStore %11 %18 OpBranch %16 %16 = OpLabel %25 = OpLoad %6 %8 OpBranch %37 %37 = OpLabel %26 = OpLoad %6 %11 %27 = OpIEqual %13 %25 %26 OpSelectionMerge %29 None OpBranchConditional %27 %28 %29 %28 = OpLabel OpStore %8 %17 OpBranch %38 %38 = OpLabel OpBranch %29 %29 = OpLabel %30 = OpLoad %6 %11 OpStore %8 %30 OpReturn OpFunctionEnd )"; const auto env = SPV_ENV_UNIVERSAL_1_3; const auto consumer = nullptr; const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); spvtools::ValidatorOptions validator_options; ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); TransformationContext transformation_context( MakeUnique(context.get()), validator_options); // The header and merge blocks const uint32_t header_inner = 34; const uint32_t merge_inner = 23; const uint32_t header_outer = 5; const uint32_t merge_outer = 16; const uint32_t header_after = 37; const uint32_t merge_after = 29; // The non-merge-nor-header blocks in each construct const uint32_t inner_block_1 = 22; const uint32_t inner_block_2 = 35; const uint32_t outer_block_1 = 15; const uint32_t outer_block_2 = 33; const uint32_t outer_block_3 = 24; const uint32_t outer_block_4 = 36; const uint32_t after_block_1 = 28; const uint32_t after_block_2 = 38; // Fine to break from a construct to its merge ASSERT_TRUE(TransformationAddDeadBreak(inner_block_1, merge_inner, true, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_TRUE(TransformationAddDeadBreak(inner_block_2, merge_inner, false, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_TRUE(TransformationAddDeadBreak(outer_block_1, merge_outer, true, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_TRUE(TransformationAddDeadBreak(outer_block_2, merge_outer, false, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_TRUE(TransformationAddDeadBreak(outer_block_3, merge_outer, true, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_TRUE(TransformationAddDeadBreak(outer_block_4, merge_outer, false, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_TRUE(TransformationAddDeadBreak(after_block_1, merge_after, true, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_TRUE(TransformationAddDeadBreak(after_block_2, merge_after, false, {}) .IsApplicable(context.get(), transformation_context)); // Not OK to break to the wrong merge (whether enclosing or not) ASSERT_FALSE(TransformationAddDeadBreak(inner_block_1, merge_outer, true, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_FALSE(TransformationAddDeadBreak(inner_block_2, merge_after, false, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_FALSE(TransformationAddDeadBreak(outer_block_1, merge_inner, true, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_FALSE(TransformationAddDeadBreak(outer_block_2, merge_after, false, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_FALSE(TransformationAddDeadBreak(after_block_1, merge_inner, true, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_FALSE(TransformationAddDeadBreak(after_block_2, merge_outer, false, {}) .IsApplicable(context.get(), transformation_context)); // Not OK to break from header (as it does not branch unconditionally) ASSERT_FALSE(TransformationAddDeadBreak(header_inner, merge_inner, true, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_FALSE(TransformationAddDeadBreak(header_outer, merge_outer, false, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_FALSE(TransformationAddDeadBreak(header_after, merge_after, true, {}) .IsApplicable(context.get(), transformation_context)); // Not OK to break to non-merge ASSERT_FALSE( TransformationAddDeadBreak(inner_block_1, inner_block_2, true, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_FALSE( TransformationAddDeadBreak(outer_block_2, after_block_1, false, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_FALSE(TransformationAddDeadBreak(outer_block_1, header_after, true, {}) .IsApplicable(context.get(), transformation_context)); auto transformation1 = TransformationAddDeadBreak(inner_block_1, merge_inner, true, {}); auto transformation2 = TransformationAddDeadBreak(inner_block_2, merge_inner, false, {}); auto transformation3 = TransformationAddDeadBreak(outer_block_1, merge_outer, true, {}); auto transformation4 = TransformationAddDeadBreak(outer_block_2, merge_outer, false, {}); auto transformation5 = TransformationAddDeadBreak(outer_block_3, merge_outer, true, {}); auto transformation6 = TransformationAddDeadBreak(outer_block_4, merge_outer, false, {}); auto transformation7 = TransformationAddDeadBreak(after_block_1, merge_after, true, {}); auto transformation8 = TransformationAddDeadBreak(after_block_2, merge_after, false, {}); ASSERT_TRUE( transformation1.IsApplicable(context.get(), transformation_context)); ApplyAndCheckFreshIds(transformation1, context.get(), &transformation_context); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); ASSERT_TRUE( transformation2.IsApplicable(context.get(), transformation_context)); ApplyAndCheckFreshIds(transformation2, context.get(), &transformation_context); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); ASSERT_TRUE( transformation3.IsApplicable(context.get(), transformation_context)); ApplyAndCheckFreshIds(transformation3, context.get(), &transformation_context); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); ASSERT_TRUE( transformation4.IsApplicable(context.get(), transformation_context)); ApplyAndCheckFreshIds(transformation4, context.get(), &transformation_context); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); ASSERT_TRUE( transformation5.IsApplicable(context.get(), transformation_context)); ApplyAndCheckFreshIds(transformation5, context.get(), &transformation_context); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); ASSERT_TRUE( transformation6.IsApplicable(context.get(), transformation_context)); ApplyAndCheckFreshIds(transformation6, context.get(), &transformation_context); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); ASSERT_TRUE( transformation7.IsApplicable(context.get(), transformation_context)); ApplyAndCheckFreshIds(transformation7, context.get(), &transformation_context); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); ASSERT_TRUE( transformation8.IsApplicable(context.get(), transformation_context)); ApplyAndCheckFreshIds(transformation8, context.get(), &transformation_context); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); std::string after_transformation = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource ESSL 310 OpName %4 "main" OpName %8 "x" OpName %11 "y" %2 = OpTypeVoid %3 = OpTypeFunction %2 %6 = OpTypeInt 32 1 %7 = OpTypePointer Function %6 %9 = OpConstant %6 1 %13 = OpTypeBool %17 = OpConstant %6 2 %18 = OpConstant %6 3 %31 = OpConstantTrue %13 %32 = OpConstantFalse %13 %4 = OpFunction %2 None %3 %5 = OpLabel %8 = OpVariable %7 Function %11 = OpVariable %7 Function OpStore %8 %9 %10 = OpLoad %6 %8 %12 = OpLoad %6 %11 %14 = OpSLessThan %13 %10 %12 OpSelectionMerge %16 None OpBranchConditional %14 %15 %24 %15 = OpLabel OpStore %8 %17 OpBranchConditional %31 %33 %16 %33 = OpLabel OpStore %8 %18 %19 = OpLoad %6 %8 OpBranchConditional %32 %16 %34 %34 = OpLabel %20 = OpLoad %6 %11 %21 = OpIEqual %13 %19 %20 OpSelectionMerge %23 None OpBranchConditional %21 %22 %23 %22 = OpLabel OpStore %11 %18 OpBranchConditional %31 %35 %23 %35 = OpLabel OpBranchConditional %32 %23 %23 %23 = OpLabel OpBranch %16 %24 = OpLabel OpStore %11 %17 OpBranchConditional %31 %36 %16 %36 = OpLabel OpStore %11 %18 OpBranchConditional %32 %16 %16 %16 = OpLabel %25 = OpLoad %6 %8 OpBranch %37 %37 = OpLabel %26 = OpLoad %6 %11 %27 = OpIEqual %13 %25 %26 OpSelectionMerge %29 None OpBranchConditional %27 %28 %29 %28 = OpLabel OpStore %8 %17 OpBranchConditional %31 %38 %29 %38 = OpLabel OpBranchConditional %32 %29 %29 %29 = OpLabel %30 = OpLoad %6 %11 OpStore %8 %30 OpReturn OpFunctionEnd )"; ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); } TEST(TransformationAddDeadBreakTest, BreakOutOfNestedSwitches) { // Checks some allowed and disallowed scenarios for nests of switches. // The SPIR-V for this test is adapted from the following GLSL: // // void main() { // int x; // int y; // x = 1; // if (x < y) { // switch (x) { // case 0: // case 1: // if (x == y) { // } // x = 2; // break; // case 3: // if (y == 4) { // y = 2; // x = 3; // } // case 10: // break; // default: // switch (y) { // case 1: // break; // case 2: // x = 4; // y = 2; // default: // x = 3; // break; // } // } // } else { // switch (y) { // case 1: // x = 4; // case 2: // y = 3; // default: // x = y; // break; // } // } // } std::string shader = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource ESSL 310 OpName %4 "main" OpName %8 "x" OpName %11 "y" %2 = OpTypeVoid %3 = OpTypeFunction %2 %6 = OpTypeInt 32 1 %7 = OpTypePointer Function %6 %9 = OpConstant %6 1 %13 = OpTypeBool %29 = OpConstant %6 2 %32 = OpConstant %6 4 %36 = OpConstant %6 3 %60 = OpConstantTrue %13 %61 = OpConstantFalse %13 %4 = OpFunction %2 None %3 %5 = OpLabel %8 = OpVariable %7 Function %11 = OpVariable %7 Function OpStore %8 %9 %10 = OpLoad %6 %8 %12 = OpLoad %6 %11 %14 = OpSLessThan %13 %10 %12 OpSelectionMerge %16 None OpBranchConditional %14 %15 %47 %15 = OpLabel %17 = OpLoad %6 %8 OpSelectionMerge %22 None OpSwitch %17 %21 0 %18 1 %18 3 %19 10 %20 %21 = OpLabel %38 = OpLoad %6 %11 OpSelectionMerge %42 None OpSwitch %38 %41 1 %39 2 %40 %41 = OpLabel OpStore %8 %36 OpBranch %42 %39 = OpLabel OpBranch %42 %40 = OpLabel OpStore %8 %32 OpStore %11 %29 OpBranch %41 %42 = OpLabel OpBranch %22 %18 = OpLabel %23 = OpLoad %6 %8 OpBranch %63 %63 = OpLabel %24 = OpLoad %6 %11 %25 = OpIEqual %13 %23 %24 OpSelectionMerge %27 None OpBranchConditional %25 %26 %27 %26 = OpLabel OpBranch %27 %27 = OpLabel OpStore %8 %29 OpBranch %22 %19 = OpLabel %31 = OpLoad %6 %11 %33 = OpIEqual %13 %31 %32 OpSelectionMerge %35 None OpBranchConditional %33 %34 %35 %34 = OpLabel OpStore %11 %29 OpBranch %62 %62 = OpLabel OpStore %8 %36 OpBranch %35 %35 = OpLabel OpBranch %20 %20 = OpLabel OpBranch %22 %22 = OpLabel OpBranch %16 %47 = OpLabel %48 = OpLoad %6 %11 OpSelectionMerge %52 None OpSwitch %48 %51 1 %49 2 %50 %51 = OpLabel %53 = OpLoad %6 %11 OpStore %8 %53 OpBranch %52 %49 = OpLabel OpStore %8 %32 OpBranch %50 %50 = OpLabel OpStore %11 %36 OpBranch %51 %52 = OpLabel OpBranch %16 %16 = OpLabel OpReturn OpFunctionEnd )"; const auto env = SPV_ENV_UNIVERSAL_1_3; const auto consumer = nullptr; const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); spvtools::ValidatorOptions validator_options; ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); TransformationContext transformation_context( MakeUnique(context.get()), validator_options); // The header and merge blocks const uint32_t header_outer_if = 5; const uint32_t merge_outer_if = 16; const uint32_t header_then_outer_switch = 15; const uint32_t merge_then_outer_switch = 22; const uint32_t header_then_inner_switch = 21; const uint32_t merge_then_inner_switch = 42; const uint32_t header_else_switch = 47; const uint32_t merge_else_switch = 52; const uint32_t header_inner_if_1 = 19; const uint32_t merge_inner_if_1 = 35; const uint32_t header_inner_if_2 = 63; const uint32_t merge_inner_if_2 = 27; // The non-merge-nor-header blocks in each construct const uint32_t then_outer_switch_block_1 = 18; const uint32_t then_inner_switch_block_1 = 39; const uint32_t then_inner_switch_block_2 = 40; const uint32_t then_inner_switch_block_3 = 41; const uint32_t else_switch_block_1 = 49; const uint32_t else_switch_block_2 = 50; const uint32_t else_switch_block_3 = 51; const uint32_t inner_if_1_block_1 = 34; const uint32_t inner_if_1_block_2 = 62; const uint32_t inner_if_2_block_1 = 26; // Fine to branch straight to direct merge block for a construct ASSERT_TRUE(TransformationAddDeadBreak(then_outer_switch_block_1, merge_then_outer_switch, true, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_TRUE(TransformationAddDeadBreak(then_inner_switch_block_1, merge_then_inner_switch, false, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_TRUE(TransformationAddDeadBreak(then_inner_switch_block_2, merge_then_inner_switch, true, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_TRUE(TransformationAddDeadBreak(then_inner_switch_block_3, merge_then_inner_switch, true, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_TRUE(TransformationAddDeadBreak(else_switch_block_1, merge_else_switch, false, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_TRUE(TransformationAddDeadBreak(else_switch_block_2, merge_else_switch, true, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_TRUE(TransformationAddDeadBreak(else_switch_block_3, merge_else_switch, false, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_TRUE( TransformationAddDeadBreak(inner_if_1_block_1, merge_inner_if_1, true, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_TRUE(TransformationAddDeadBreak(inner_if_1_block_2, merge_inner_if_1, false, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_TRUE( TransformationAddDeadBreak(inner_if_2_block_1, merge_inner_if_2, true, {}) .IsApplicable(context.get(), transformation_context)); // Not OK to break out of a switch from a selection construct inside the // switch. ASSERT_FALSE(TransformationAddDeadBreak(inner_if_1_block_1, merge_then_outer_switch, true, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_FALSE(TransformationAddDeadBreak(inner_if_1_block_2, merge_then_outer_switch, false, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_FALSE(TransformationAddDeadBreak(inner_if_2_block_1, merge_then_outer_switch, true, {}) .IsApplicable(context.get(), transformation_context)); // Some miscellaneous inapplicable cases. ASSERT_FALSE( TransformationAddDeadBreak(header_outer_if, merge_outer_if, true, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_FALSE(TransformationAddDeadBreak(header_inner_if_1, inner_if_1_block_2, false, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_FALSE(TransformationAddDeadBreak(header_then_inner_switch, header_then_outer_switch, false, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_FALSE(TransformationAddDeadBreak(header_else_switch, then_inner_switch_block_3, false, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_FALSE(TransformationAddDeadBreak(header_inner_if_2, header_inner_if_2, false, {}) .IsApplicable(context.get(), transformation_context)); auto transformation1 = TransformationAddDeadBreak( then_outer_switch_block_1, merge_then_outer_switch, true, {}); auto transformation2 = TransformationAddDeadBreak( then_inner_switch_block_1, merge_then_inner_switch, false, {}); auto transformation3 = TransformationAddDeadBreak( then_inner_switch_block_2, merge_then_inner_switch, true, {}); auto transformation4 = TransformationAddDeadBreak( then_inner_switch_block_3, merge_then_inner_switch, true, {}); auto transformation5 = TransformationAddDeadBreak( else_switch_block_1, merge_else_switch, false, {}); auto transformation6 = TransformationAddDeadBreak( else_switch_block_2, merge_else_switch, true, {}); auto transformation7 = TransformationAddDeadBreak( else_switch_block_3, merge_else_switch, false, {}); auto transformation8 = TransformationAddDeadBreak(inner_if_1_block_1, merge_inner_if_1, true, {}); auto transformation9 = TransformationAddDeadBreak( inner_if_1_block_2, merge_inner_if_1, false, {}); auto transformation10 = TransformationAddDeadBreak( inner_if_2_block_1, merge_inner_if_2, true, {}); ASSERT_TRUE( transformation1.IsApplicable(context.get(), transformation_context)); ApplyAndCheckFreshIds(transformation1, context.get(), &transformation_context); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); ASSERT_TRUE( transformation2.IsApplicable(context.get(), transformation_context)); ApplyAndCheckFreshIds(transformation2, context.get(), &transformation_context); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); ASSERT_TRUE( transformation3.IsApplicable(context.get(), transformation_context)); ApplyAndCheckFreshIds(transformation3, context.get(), &transformation_context); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); ASSERT_TRUE( transformation4.IsApplicable(context.get(), transformation_context)); ApplyAndCheckFreshIds(transformation4, context.get(), &transformation_context); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); ASSERT_TRUE( transformation5.IsApplicable(context.get(), transformation_context)); ApplyAndCheckFreshIds(transformation5, context.get(), &transformation_context); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); ASSERT_TRUE( transformation6.IsApplicable(context.get(), transformation_context)); ApplyAndCheckFreshIds(transformation6, context.get(), &transformation_context); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); ASSERT_TRUE( transformation7.IsApplicable(context.get(), transformation_context)); ApplyAndCheckFreshIds(transformation7, context.get(), &transformation_context); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); ASSERT_TRUE( transformation8.IsApplicable(context.get(), transformation_context)); ApplyAndCheckFreshIds(transformation8, context.get(), &transformation_context); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); ASSERT_TRUE( transformation9.IsApplicable(context.get(), transformation_context)); ApplyAndCheckFreshIds(transformation9, context.get(), &transformation_context); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); ASSERT_TRUE( transformation10.IsApplicable(context.get(), transformation_context)); ApplyAndCheckFreshIds(transformation10, context.get(), &transformation_context); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); std::string after_transformation = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource ESSL 310 OpName %4 "main" OpName %8 "x" OpName %11 "y" %2 = OpTypeVoid %3 = OpTypeFunction %2 %6 = OpTypeInt 32 1 %7 = OpTypePointer Function %6 %9 = OpConstant %6 1 %13 = OpTypeBool %29 = OpConstant %6 2 %32 = OpConstant %6 4 %36 = OpConstant %6 3 %60 = OpConstantTrue %13 %61 = OpConstantFalse %13 %4 = OpFunction %2 None %3 %5 = OpLabel %8 = OpVariable %7 Function %11 = OpVariable %7 Function OpStore %8 %9 %10 = OpLoad %6 %8 %12 = OpLoad %6 %11 %14 = OpSLessThan %13 %10 %12 OpSelectionMerge %16 None OpBranchConditional %14 %15 %47 %15 = OpLabel %17 = OpLoad %6 %8 OpSelectionMerge %22 None OpSwitch %17 %21 0 %18 1 %18 3 %19 10 %20 %21 = OpLabel %38 = OpLoad %6 %11 OpSelectionMerge %42 None OpSwitch %38 %41 1 %39 2 %40 %41 = OpLabel OpStore %8 %36 OpBranchConditional %60 %42 %42 %39 = OpLabel OpBranchConditional %61 %42 %42 %40 = OpLabel OpStore %8 %32 OpStore %11 %29 OpBranchConditional %60 %41 %42 %42 = OpLabel OpBranch %22 %18 = OpLabel %23 = OpLoad %6 %8 OpBranchConditional %60 %63 %22 %63 = OpLabel %24 = OpLoad %6 %11 %25 = OpIEqual %13 %23 %24 OpSelectionMerge %27 None OpBranchConditional %25 %26 %27 %26 = OpLabel OpBranchConditional %60 %27 %27 %27 = OpLabel OpStore %8 %29 OpBranch %22 %19 = OpLabel %31 = OpLoad %6 %11 %33 = OpIEqual %13 %31 %32 OpSelectionMerge %35 None OpBranchConditional %33 %34 %35 %34 = OpLabel OpStore %11 %29 OpBranchConditional %60 %62 %35 %62 = OpLabel OpStore %8 %36 OpBranchConditional %61 %35 %35 %35 = OpLabel OpBranch %20 %20 = OpLabel OpBranch %22 %22 = OpLabel OpBranch %16 %47 = OpLabel %48 = OpLoad %6 %11 OpSelectionMerge %52 None OpSwitch %48 %51 1 %49 2 %50 %51 = OpLabel %53 = OpLoad %6 %11 OpStore %8 %53 OpBranchConditional %61 %52 %52 %49 = OpLabel OpStore %8 %32 OpBranchConditional %61 %52 %50 %50 = OpLabel OpStore %11 %36 OpBranchConditional %60 %51 %52 %52 = OpLabel OpBranch %16 %16 = OpLabel OpReturn OpFunctionEnd )"; ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); } TEST(TransformationAddDeadBreakTest, BreakOutOfLoopNest) { // Checks some allowed and disallowed scenarios for a nest of loops, including // breaking from an if or switch right out of a loop. // The SPIR-V for this test is adapted from the following GLSL: // // void main() { // int x, y; // do { // x++; // for (int j = 0; j < 100; j++) { // y++; // if (x == y) { // x++; // if (x == 2) { // y++; // } // switch (x) { // case 0: // x = 2; // default: // break; // } // } // } // } while (x > y); // // for (int i = 0; i < 100; i++) { // x++; // } // } std::string shader = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource ESSL 310 OpName %4 "main" OpName %12 "x" OpName %16 "j" OpName %27 "y" OpName %55 "i" %2 = OpTypeVoid %3 = OpTypeFunction %2 %10 = OpTypeInt 32 1 %11 = OpTypePointer Function %10 %14 = OpConstant %10 1 %17 = OpConstant %10 0 %24 = OpConstant %10 100 %25 = OpTypeBool %38 = OpConstant %10 2 %67 = OpConstantTrue %25 %68 = OpConstantFalse %25 %4 = OpFunction %2 None %3 %5 = OpLabel %12 = OpVariable %11 Function %16 = OpVariable %11 Function %27 = OpVariable %11 Function %55 = OpVariable %11 Function OpBranch %6 %6 = OpLabel OpLoopMerge %8 %9 None OpBranch %7 %7 = OpLabel %13 = OpLoad %10 %12 %15 = OpIAdd %10 %13 %14 OpStore %12 %15 OpStore %16 %17 OpBranch %18 %18 = OpLabel OpLoopMerge %20 %21 None OpBranch %22 %22 = OpLabel %23 = OpLoad %10 %16 %26 = OpSLessThan %25 %23 %24 OpBranchConditional %26 %19 %20 %19 = OpLabel %28 = OpLoad %10 %27 %29 = OpIAdd %10 %28 %14 OpStore %27 %29 %30 = OpLoad %10 %12 %31 = OpLoad %10 %27 %32 = OpIEqual %25 %30 %31 OpSelectionMerge %34 None OpBranchConditional %32 %33 %34 %33 = OpLabel %35 = OpLoad %10 %12 %36 = OpIAdd %10 %35 %14 OpStore %12 %36 %37 = OpLoad %10 %12 %39 = OpIEqual %25 %37 %38 OpSelectionMerge %41 None OpBranchConditional %39 %40 %41 %40 = OpLabel %42 = OpLoad %10 %27 %43 = OpIAdd %10 %42 %14 OpStore %27 %43 OpBranch %41 %41 = OpLabel %44 = OpLoad %10 %12 OpSelectionMerge %47 None OpSwitch %44 %46 0 %45 %46 = OpLabel OpBranch %47 %45 = OpLabel OpStore %12 %38 OpBranch %46 %47 = OpLabel OpBranch %34 %34 = OpLabel OpBranch %21 %21 = OpLabel %50 = OpLoad %10 %16 %51 = OpIAdd %10 %50 %14 OpStore %16 %51 OpBranch %18 %20 = OpLabel OpBranch %9 %9 = OpLabel %52 = OpLoad %10 %12 %53 = OpLoad %10 %27 %54 = OpSGreaterThan %25 %52 %53 OpBranchConditional %54 %6 %8 %8 = OpLabel OpStore %55 %17 OpBranch %56 %56 = OpLabel OpLoopMerge %58 %59 None OpBranch %60 %60 = OpLabel %61 = OpLoad %10 %55 %62 = OpSLessThan %25 %61 %24 OpBranchConditional %62 %57 %58 %57 = OpLabel %63 = OpLoad %10 %12 %64 = OpIAdd %10 %63 %14 OpStore %12 %64 OpBranch %59 %59 = OpLabel %65 = OpLoad %10 %55 %66 = OpIAdd %10 %65 %14 OpStore %55 %66 OpBranch %56 %58 = OpLabel OpReturn OpFunctionEnd )"; const auto env = SPV_ENV_UNIVERSAL_1_3; const auto consumer = nullptr; const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); spvtools::ValidatorOptions validator_options; ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); TransformationContext transformation_context( MakeUnique(context.get()), validator_options); // The header and merge blocks const uint32_t header_do_while = 6; const uint32_t merge_do_while = 8; const uint32_t header_for_j = 18; const uint32_t merge_for_j = 20; const uint32_t header_for_i = 56; const uint32_t merge_for_i = 58; const uint32_t header_switch = 41; const uint32_t merge_switch = 47; const uint32_t header_if_x_eq_y = 19; const uint32_t merge_if_x_eq_y = 34; const uint32_t header_if_x_eq_2 = 33; const uint32_t merge_if_x_eq_2 = 41; // Loop continue targets const uint32_t continue_do_while = 9; const uint32_t continue_for_j = 21; const uint32_t continue_for_i = 59; // Some blocks in these constructs const uint32_t block_in_inner_if = 40; const uint32_t block_switch_case = 46; const uint32_t block_switch_default = 45; const uint32_t block_in_for_i_loop = 57; // Fine to break from any loop header to its merge ASSERT_TRUE( TransformationAddDeadBreak(header_do_while, merge_do_while, true, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_TRUE(TransformationAddDeadBreak(header_for_i, merge_for_i, false, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_TRUE(TransformationAddDeadBreak(header_for_j, merge_for_j, true, {}) .IsApplicable(context.get(), transformation_context)); // Fine to break from any of the blocks in constructs in the "for j" loop to // that loop's merge ASSERT_TRUE( TransformationAddDeadBreak(block_in_inner_if, merge_for_j, false, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_TRUE( TransformationAddDeadBreak(block_switch_case, merge_for_j, true, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_TRUE( TransformationAddDeadBreak(block_switch_default, merge_for_j, false, {}) .IsApplicable(context.get(), transformation_context)); // Fine to break from the body of the "for i" loop to that loop's merge ASSERT_TRUE( TransformationAddDeadBreak(block_in_for_i_loop, merge_for_i, true, {}) .IsApplicable(context.get(), transformation_context)); // Not OK to break from multiple loops ASSERT_FALSE( TransformationAddDeadBreak(block_in_inner_if, merge_do_while, false, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_FALSE( TransformationAddDeadBreak(block_switch_case, merge_do_while, true, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_FALSE(TransformationAddDeadBreak(block_switch_default, merge_do_while, false, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_FALSE( TransformationAddDeadBreak(header_for_j, merge_do_while, true, {}) .IsApplicable(context.get(), transformation_context)); // Not OK to break loop from its continue construct, except from the back-edge // block. ASSERT_FALSE( TransformationAddDeadBreak(continue_do_while, merge_do_while, true, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_TRUE(TransformationAddDeadBreak(continue_for_j, merge_for_j, false, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_TRUE(TransformationAddDeadBreak(continue_for_i, merge_for_i, true, {}) .IsApplicable(context.get(), transformation_context)); // Not OK to break out of multiple non-loop constructs if not breaking to a // loop merge ASSERT_FALSE( TransformationAddDeadBreak(block_in_inner_if, merge_if_x_eq_y, false, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_FALSE( TransformationAddDeadBreak(block_switch_case, merge_if_x_eq_y, true, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_FALSE(TransformationAddDeadBreak(block_switch_default, merge_if_x_eq_y, false, {}) .IsApplicable(context.get(), transformation_context)); // Some miscellaneous inapplicable transformations ASSERT_FALSE( TransformationAddDeadBreak(header_if_x_eq_2, header_if_x_eq_y, false, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_FALSE( TransformationAddDeadBreak(merge_if_x_eq_2, merge_switch, false, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_FALSE( TransformationAddDeadBreak(header_switch, header_switch, false, {}) .IsApplicable(context.get(), transformation_context)); auto transformation1 = TransformationAddDeadBreak(header_do_while, merge_do_while, true, {}); auto transformation2 = TransformationAddDeadBreak(header_for_i, merge_for_i, false, {}); auto transformation3 = TransformationAddDeadBreak(header_for_j, merge_for_j, true, {}); auto transformation4 = TransformationAddDeadBreak(block_in_inner_if, merge_for_j, false, {}); auto transformation5 = TransformationAddDeadBreak(block_switch_case, merge_for_j, true, {}); auto transformation6 = TransformationAddDeadBreak(block_switch_default, merge_for_j, false, {}); auto transformation7 = TransformationAddDeadBreak(block_in_for_i_loop, merge_for_i, true, {}); ASSERT_TRUE( transformation1.IsApplicable(context.get(), transformation_context)); ApplyAndCheckFreshIds(transformation1, context.get(), &transformation_context); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); ASSERT_TRUE( transformation2.IsApplicable(context.get(), transformation_context)); ApplyAndCheckFreshIds(transformation2, context.get(), &transformation_context); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); ASSERT_TRUE( transformation3.IsApplicable(context.get(), transformation_context)); ApplyAndCheckFreshIds(transformation3, context.get(), &transformation_context); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); ASSERT_TRUE( transformation4.IsApplicable(context.get(), transformation_context)); ApplyAndCheckFreshIds(transformation4, context.get(), &transformation_context); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); ASSERT_TRUE( transformation5.IsApplicable(context.get(), transformation_context)); ApplyAndCheckFreshIds(transformation5, context.get(), &transformation_context); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); ASSERT_TRUE( transformation6.IsApplicable(context.get(), transformation_context)); ApplyAndCheckFreshIds(transformation6, context.get(), &transformation_context); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); ASSERT_TRUE( transformation7.IsApplicable(context.get(), transformation_context)); ApplyAndCheckFreshIds(transformation7, context.get(), &transformation_context); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); std::string after_transformation = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource ESSL 310 OpName %4 "main" OpName %12 "x" OpName %16 "j" OpName %27 "y" OpName %55 "i" %2 = OpTypeVoid %3 = OpTypeFunction %2 %10 = OpTypeInt 32 1 %11 = OpTypePointer Function %10 %14 = OpConstant %10 1 %17 = OpConstant %10 0 %24 = OpConstant %10 100 %25 = OpTypeBool %38 = OpConstant %10 2 %67 = OpConstantTrue %25 %68 = OpConstantFalse %25 %4 = OpFunction %2 None %3 %5 = OpLabel %12 = OpVariable %11 Function %16 = OpVariable %11 Function %27 = OpVariable %11 Function %55 = OpVariable %11 Function OpBranch %6 %6 = OpLabel OpLoopMerge %8 %9 None OpBranchConditional %67 %7 %8 %7 = OpLabel %13 = OpLoad %10 %12 %15 = OpIAdd %10 %13 %14 OpStore %12 %15 OpStore %16 %17 OpBranch %18 %18 = OpLabel OpLoopMerge %20 %21 None OpBranchConditional %67 %22 %20 %22 = OpLabel %23 = OpLoad %10 %16 %26 = OpSLessThan %25 %23 %24 OpBranchConditional %26 %19 %20 %19 = OpLabel %28 = OpLoad %10 %27 %29 = OpIAdd %10 %28 %14 OpStore %27 %29 %30 = OpLoad %10 %12 %31 = OpLoad %10 %27 %32 = OpIEqual %25 %30 %31 OpSelectionMerge %34 None OpBranchConditional %32 %33 %34 %33 = OpLabel %35 = OpLoad %10 %12 %36 = OpIAdd %10 %35 %14 OpStore %12 %36 %37 = OpLoad %10 %12 %39 = OpIEqual %25 %37 %38 OpSelectionMerge %41 None OpBranchConditional %39 %40 %41 %40 = OpLabel %42 = OpLoad %10 %27 %43 = OpIAdd %10 %42 %14 OpStore %27 %43 OpBranchConditional %68 %20 %41 %41 = OpLabel %44 = OpLoad %10 %12 OpSelectionMerge %47 None OpSwitch %44 %46 0 %45 %46 = OpLabel OpBranchConditional %67 %47 %20 %45 = OpLabel OpStore %12 %38 OpBranchConditional %68 %20 %46 %47 = OpLabel OpBranch %34 %34 = OpLabel OpBranch %21 %21 = OpLabel %50 = OpLoad %10 %16 %51 = OpIAdd %10 %50 %14 OpStore %16 %51 OpBranch %18 %20 = OpLabel OpBranch %9 %9 = OpLabel %52 = OpLoad %10 %12 %53 = OpLoad %10 %27 %54 = OpSGreaterThan %25 %52 %53 OpBranchConditional %54 %6 %8 %8 = OpLabel OpStore %55 %17 OpBranch %56 %56 = OpLabel OpLoopMerge %58 %59 None OpBranchConditional %68 %58 %60 %60 = OpLabel %61 = OpLoad %10 %55 %62 = OpSLessThan %25 %61 %24 OpBranchConditional %62 %57 %58 %57 = OpLabel %63 = OpLoad %10 %12 %64 = OpIAdd %10 %63 %14 OpStore %12 %64 OpBranchConditional %67 %59 %58 %59 = OpLabel %65 = OpLoad %10 %55 %66 = OpIAdd %10 %65 %14 OpStore %55 %66 OpBranch %56 %58 = OpLabel OpReturn OpFunctionEnd )"; ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); } TEST(TransformationAddDeadBreakTest, NoBreakFromContinueConstruct) { // Checks that it is illegal to break straight from a continue construct. // The SPIR-V for this test is adapted from the following GLSL: // // void main() { // for (int i = 0; i < 100; i++) { // } // } std::string shader = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource ESSL 310 OpName %4 "main" OpName %8 "i" OpDecorate %8 RelaxedPrecision OpDecorate %15 RelaxedPrecision OpDecorate %19 RelaxedPrecision OpDecorate %21 RelaxedPrecision %2 = OpTypeVoid %3 = OpTypeFunction %2 %6 = OpTypeInt 32 1 %7 = OpTypePointer Function %6 %9 = OpConstant %6 0 %16 = OpConstant %6 100 %17 = OpTypeBool %22 = OpConstantTrue %17 %20 = OpConstant %6 1 %4 = OpFunction %2 None %3 %5 = OpLabel %8 = OpVariable %7 Function OpStore %8 %9 OpBranch %10 %10 = OpLabel OpLoopMerge %12 %13 None OpBranch %14 %14 = OpLabel %15 = OpLoad %6 %8 %18 = OpSLessThan %17 %15 %16 OpBranchConditional %18 %11 %12 %11 = OpLabel OpBranch %13 %13 = OpLabel %19 = OpLoad %6 %8 %21 = OpIAdd %6 %19 %20 OpBranch %23 %23 = OpLabel OpStore %8 %21 OpBranch %10 %12 = OpLabel OpReturn OpFunctionEnd )"; const auto env = SPV_ENV_UNIVERSAL_1_3; const auto consumer = nullptr; const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); spvtools::ValidatorOptions validator_options; ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); TransformationContext transformation_context( MakeUnique(context.get()), validator_options); // Not OK to break loop from its continue construct, except from the back-edge // block. ASSERT_FALSE(TransformationAddDeadBreak(13, 12, true, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_TRUE(TransformationAddDeadBreak(23, 12, true, {}) .IsApplicable(context.get(), transformation_context)); } TEST(TransformationAddDeadBreakTest, BreakFromBackEdgeBlock) { std::string reference_shader = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Vertex %10 "main" ; Types %2 = OpTypeVoid %3 = OpTypeFunction %2 %4 = OpTypeInt 32 0 %5 = OpTypeBool %6 = OpTypePointer Function %4 ; Constants %7 = OpConstant %4 0 %8 = OpConstant %4 1 %9 = OpConstantTrue %5 ; main function %10 = OpFunction %2 None %3 %11 = OpLabel %12 = OpVariable %6 Function OpStore %12 %7 OpBranch %13 %13 = OpLabel OpLoopMerge %21 %18 None ; structured loop OpBranch %14 %14 = OpLabel %15 = OpLoad %4 %12 %16 = OpULessThan %5 %15 %8 ; i < 1 ? OpBranchConditional %16 %17 %21 ; body or break %17 = OpLabel ; body OpBranch %18 %18 = OpLabel ; continue target does not strictly dominates the back-edge block %19 = OpLoad %4 %12 %20 = OpIAdd %4 %19 %8 ; ++i OpStore %12 %20 OpBranch %13 %21 = OpLabel OpReturn OpFunctionEnd )"; const auto env = SPV_ENV_UNIVERSAL_1_5; const auto consumer = nullptr; const auto context = BuildModule(env, consumer, reference_shader, kFuzzAssembleOption); spvtools::ValidatorOptions validator_options; ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); TransformationContext transformation_context( MakeUnique(context.get()), validator_options); auto transformation = TransformationAddDeadBreak(18, 21, true, {}); ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context); std::string variant_shader = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Vertex %10 "main" ; Types %2 = OpTypeVoid %3 = OpTypeFunction %2 %4 = OpTypeInt 32 0 %5 = OpTypeBool %6 = OpTypePointer Function %4 ; Constants %7 = OpConstant %4 0 %8 = OpConstant %4 1 %9 = OpConstantTrue %5 ; main function %10 = OpFunction %2 None %3 %11 = OpLabel %12 = OpVariable %6 Function OpStore %12 %7 OpBranch %13 %13 = OpLabel OpLoopMerge %21 %18 None ; structured loop OpBranch %14 %14 = OpLabel %15 = OpLoad %4 %12 %16 = OpULessThan %5 %15 %8 ; i < 1 ? OpBranchConditional %16 %17 %21 ; body or break %17 = OpLabel ; body OpBranch %18 %18 = OpLabel ; continue target does not strictly dominates the back-edge block %19 = OpLoad %4 %12 %20 = OpIAdd %4 %19 %8 ; ++i OpStore %12 %20 OpBranchConditional %9 %13 %21 %21 = OpLabel OpReturn OpFunctionEnd )"; ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); ASSERT_TRUE(IsEqual(env, variant_shader, context.get())); } TEST(TransformationAddDeadBreakTest, SelectionInContinueConstruct) { // Considers some scenarios where there is a selection construct in a loop's // continue construct. // The SPIR-V for this test is adapted from the following GLSL: // // void main() { // for (int i = 0; i < 100; i = (i < 50 ? i + 2 : i + 1)) { // } // } std::string shader = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource ESSL 310 OpName %4 "main" OpName %8 "i" %2 = OpTypeVoid %3 = OpTypeFunction %2 %6 = OpTypeInt 32 1 %7 = OpTypePointer Function %6 %9 = OpConstant %6 0 %16 = OpConstant %6 100 %17 = OpTypeBool %99 = OpConstantTrue %17 %20 = OpConstant %6 50 %26 = OpConstant %6 2 %30 = OpConstant %6 1 %4 = OpFunction %2 None %3 %5 = OpLabel %8 = OpVariable %7 Function %22 = OpVariable %7 Function OpStore %8 %9 OpBranch %10 %10 = OpLabel OpLoopMerge %12 %13 None OpBranch %14 %14 = OpLabel %15 = OpLoad %6 %8 %18 = OpSLessThan %17 %15 %16 OpBranchConditional %18 %11 %12 %11 = OpLabel OpBranch %13 %13 = OpLabel %19 = OpLoad %6 %8 %21 = OpSLessThan %17 %19 %20 OpSelectionMerge %24 None OpBranchConditional %21 %23 %28 %23 = OpLabel %25 = OpLoad %6 %8 OpBranch %100 %100 = OpLabel %27 = OpIAdd %6 %25 %26 OpStore %22 %27 OpBranch %24 %28 = OpLabel %29 = OpLoad %6 %8 OpBranch %101 %101 = OpLabel %31 = OpIAdd %6 %29 %30 OpStore %22 %31 OpBranch %24 %24 = OpLabel %32 = OpLoad %6 %22 OpStore %8 %32 OpBranch %10 %12 = OpLabel OpReturn OpFunctionEnd )"; const auto env = SPV_ENV_UNIVERSAL_1_3; const auto consumer = nullptr; const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); spvtools::ValidatorOptions validator_options; ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); TransformationContext transformation_context( MakeUnique(context.get()), validator_options); const uint32_t loop_merge = 12; const uint32_t selection_merge = 24; const uint32_t in_selection_1 = 23; const uint32_t in_selection_2 = 100; const uint32_t in_selection_3 = 28; const uint32_t in_selection_4 = 101; // Not OK to jump from the selection to the loop merge, as this would break // from the loop's continue construct. ASSERT_FALSE(TransformationAddDeadBreak(in_selection_1, loop_merge, true, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_FALSE(TransformationAddDeadBreak(in_selection_2, loop_merge, true, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_FALSE(TransformationAddDeadBreak(in_selection_3, loop_merge, true, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_FALSE(TransformationAddDeadBreak(in_selection_4, loop_merge, true, {}) .IsApplicable(context.get(), transformation_context)); // But fine to jump from the selection to its merge. auto transformation1 = TransformationAddDeadBreak(in_selection_1, selection_merge, true, {}); auto transformation2 = TransformationAddDeadBreak(in_selection_2, selection_merge, true, {}); auto transformation3 = TransformationAddDeadBreak(in_selection_3, selection_merge, true, {}); auto transformation4 = TransformationAddDeadBreak(in_selection_4, selection_merge, true, {}); ASSERT_TRUE( transformation1.IsApplicable(context.get(), transformation_context)); ApplyAndCheckFreshIds(transformation1, context.get(), &transformation_context); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); ASSERT_TRUE( transformation2.IsApplicable(context.get(), transformation_context)); ApplyAndCheckFreshIds(transformation2, context.get(), &transformation_context); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); ASSERT_TRUE( transformation3.IsApplicable(context.get(), transformation_context)); ApplyAndCheckFreshIds(transformation3, context.get(), &transformation_context); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); ASSERT_TRUE( transformation4.IsApplicable(context.get(), transformation_context)); ApplyAndCheckFreshIds(transformation4, context.get(), &transformation_context); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); std::string after_transformation = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource ESSL 310 OpName %4 "main" OpName %8 "i" %2 = OpTypeVoid %3 = OpTypeFunction %2 %6 = OpTypeInt 32 1 %7 = OpTypePointer Function %6 %9 = OpConstant %6 0 %16 = OpConstant %6 100 %17 = OpTypeBool %99 = OpConstantTrue %17 %20 = OpConstant %6 50 %26 = OpConstant %6 2 %30 = OpConstant %6 1 %4 = OpFunction %2 None %3 %5 = OpLabel %8 = OpVariable %7 Function %22 = OpVariable %7 Function OpStore %8 %9 OpBranch %10 %10 = OpLabel OpLoopMerge %12 %13 None OpBranch %14 %14 = OpLabel %15 = OpLoad %6 %8 %18 = OpSLessThan %17 %15 %16 OpBranchConditional %18 %11 %12 %11 = OpLabel OpBranch %13 %13 = OpLabel %19 = OpLoad %6 %8 %21 = OpSLessThan %17 %19 %20 OpSelectionMerge %24 None OpBranchConditional %21 %23 %28 %23 = OpLabel %25 = OpLoad %6 %8 OpBranchConditional %99 %100 %24 %100 = OpLabel %27 = OpIAdd %6 %25 %26 OpStore %22 %27 OpBranchConditional %99 %24 %24 %28 = OpLabel %29 = OpLoad %6 %8 OpBranchConditional %99 %101 %24 %101 = OpLabel %31 = OpIAdd %6 %29 %30 OpStore %22 %31 OpBranchConditional %99 %24 %24 %24 = OpLabel %32 = OpLoad %6 %22 OpStore %8 %32 OpBranch %10 %12 = OpLabel OpReturn OpFunctionEnd )"; ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); } TEST(TransformationAddDeadBreakTest, LoopInContinueConstruct) { // Considers some scenarios where there is a loop in a loop's continue // construct. // The SPIR-V for this test is adapted from the following GLSL, with inlining // applied so that the loop from foo is in the main loop's continue construct: // // int foo() { // int result = 0; // for (int j = 0; j < 10; j++) { // result++; // } // return result; // } // // void main() { // for (int i = 0; i < 100; i += foo()) { // } // } std::string shader = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource ESSL 310 OpName %4 "main" OpName %31 "i" %2 = OpTypeVoid %3 = OpTypeFunction %2 %6 = OpTypeInt 32 1 %7 = OpTypeFunction %6 %10 = OpTypePointer Function %6 %12 = OpConstant %6 0 %20 = OpConstant %6 10 %21 = OpTypeBool %100 = OpConstantTrue %21 %24 = OpConstant %6 1 %38 = OpConstant %6 100 %4 = OpFunction %2 None %3 %5 = OpLabel %43 = OpVariable %10 Function %44 = OpVariable %10 Function %45 = OpVariable %10 Function %31 = OpVariable %10 Function OpStore %31 %12 OpBranch %32 %32 = OpLabel OpLoopMerge %34 %35 None OpBranch %36 %36 = OpLabel %37 = OpLoad %6 %31 %39 = OpSLessThan %21 %37 %38 OpBranchConditional %39 %33 %34 %33 = OpLabel OpBranch %35 %35 = OpLabel OpStore %43 %12 OpStore %44 %12 OpBranch %46 %46 = OpLabel OpLoopMerge %47 %48 None OpBranch %49 %49 = OpLabel %50 = OpLoad %6 %44 %51 = OpSLessThan %21 %50 %20 OpBranchConditional %51 %52 %47 %52 = OpLabel %53 = OpLoad %6 %43 OpBranch %101 %101 = OpLabel %54 = OpIAdd %6 %53 %24 OpStore %43 %54 OpBranch %48 %48 = OpLabel %55 = OpLoad %6 %44 %56 = OpIAdd %6 %55 %24 OpStore %44 %56 OpBranch %46 %47 = OpLabel %57 = OpLoad %6 %43 OpStore %45 %57 %40 = OpLoad %6 %45 %41 = OpLoad %6 %31 %42 = OpIAdd %6 %41 %40 OpStore %31 %42 OpBranch %32 %34 = OpLabel OpReturn OpFunctionEnd )"; const auto env = SPV_ENV_UNIVERSAL_1_3; const auto consumer = nullptr; const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); spvtools::ValidatorOptions validator_options; ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); TransformationContext transformation_context( MakeUnique(context.get()), validator_options); const uint32_t outer_loop_merge = 34; const uint32_t outer_loop_block = 33; const uint32_t inner_loop_merge = 47; const uint32_t inner_loop_block = 52; // Some inapplicable cases ASSERT_FALSE( TransformationAddDeadBreak(inner_loop_block, outer_loop_merge, true, {}) .IsApplicable(context.get(), transformation_context)); ASSERT_FALSE( TransformationAddDeadBreak(outer_loop_block, inner_loop_merge, true, {}) .IsApplicable(context.get(), transformation_context)); auto transformation1 = TransformationAddDeadBreak(inner_loop_block, inner_loop_merge, true, {}); auto transformation2 = TransformationAddDeadBreak(outer_loop_block, outer_loop_merge, true, {}); ASSERT_TRUE( transformation1.IsApplicable(context.get(), transformation_context)); ApplyAndCheckFreshIds(transformation1, context.get(), &transformation_context); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); ASSERT_TRUE( transformation2.IsApplicable(context.get(), transformation_context)); ApplyAndCheckFreshIds(transformation2, context.get(), &transformation_context); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); std::string after_transformation = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource ESSL 310 OpName %4 "main" OpName %31 "i" %2 = OpTypeVoid %3 = OpTypeFunction %2 %6 = OpTypeInt 32 1 %7 = OpTypeFunction %6 %10 = OpTypePointer Function %6 %12 = OpConstant %6 0 %20 = OpConstant %6 10 %21 = OpTypeBool %100 = OpConstantTrue %21 %24 = OpConstant %6 1 %38 = OpConstant %6 100 %4 = OpFunction %2 None %3 %5 = OpLabel %43 = OpVariable %10 Function %44 = OpVariable %10 Function %45 = OpVariable %10 Function %31 = OpVariable %10 Function OpStore %31 %12 OpBranch %32 %32 = OpLabel OpLoopMerge %34 %35 None OpBranch %36 %36 = OpLabel %37 = OpLoad %6 %31 %39 = OpSLessThan %21 %37 %38 OpBranchConditional %39 %33 %34 %33 = OpLabel OpBranchConditional %100 %35 %34 %35 = OpLabel OpStore %43 %12 OpStore %44 %12 OpBranch %46 %46 = OpLabel OpLoopMerge %47 %48 None OpBranch %49 %49 = OpLabel %50 = OpLoad %6 %44 %51 = OpSLessThan %21 %50 %20 OpBranchConditional %51 %52 %47 %52 = OpLabel %53 = OpLoad %6 %43 OpBranchConditional %100 %101 %47 %101 = OpLabel %54 = OpIAdd %6 %53 %24 OpStore %43 %54 OpBranch %48 %48 = OpLabel %55 = OpLoad %6 %44 %56 = OpIAdd %6 %55 %24 OpStore %44 %56 OpBranch %46 %47 = OpLabel %57 = OpLoad %6 %43 OpStore %45 %57 %40 = OpLoad %6 %45 %41 = OpLoad %6 %31 %42 = OpIAdd %6 %41 %40 OpStore %31 %42 OpBranch %32 %34 = OpLabel OpReturn OpFunctionEnd )"; ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); } TEST(TransformationAddDeadBreakTest, PhiInstructions) { // Checks that the transformation works in the presence of phi instructions. // The SPIR-V for this test is adapted from the following GLSL, with a bit of // extra and artificial work to get some interesting uses of OpPhi: // // void main() { // int x; int y; // float f; // x = 2; // f = 3.0; // if (x > y) { // x = 3; // f = 4.0; // } else { // x = x + 2; // f = f + 10.0; // } // while (x < y) { // x = x + 1; // f = f + 1.0; // } // y = x; // f = f + 3.0; // } std::string shader = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource ESSL 310 OpName %4 "main" OpName %8 "x" OpName %12 "f" OpName %15 "y" %2 = OpTypeVoid %3 = OpTypeFunction %2 %6 = OpTypeInt 32 1 %7 = OpTypePointer Function %6 %9 = OpConstant %6 2 %10 = OpTypeFloat 32 %11 = OpTypePointer Function %10 %13 = OpConstant %10 3 %17 = OpTypeBool %80 = OpConstantTrue %17 %21 = OpConstant %6 3 %22 = OpConstant %10 4 %27 = OpConstant %10 10 %38 = OpConstant %6 1 %41 = OpConstant %10 1 %46 = OpUndef %6 %4 = OpFunction %2 None %3 %5 = OpLabel %8 = OpVariable %7 Function %12 = OpVariable %11 Function %15 = OpVariable %7 Function OpStore %8 %9 OpStore %12 %13 %18 = OpSGreaterThan %17 %9 %46 OpSelectionMerge %20 None OpBranchConditional %18 %19 %23 %19 = OpLabel OpStore %8 %21 OpStore %12 %22 OpBranch %20 %23 = OpLabel %25 = OpIAdd %6 %9 %9 OpStore %8 %25 OpBranch %70 %70 = OpLabel %28 = OpFAdd %10 %13 %27 OpStore %12 %28 OpBranch %20 %20 = OpLabel %52 = OpPhi %10 %22 %19 %28 %70 %48 = OpPhi %6 %21 %19 %25 %70 OpBranch %29 %29 = OpLabel %51 = OpPhi %10 %52 %20 %42 %32 %47 = OpPhi %6 %48 %20 %39 %32 OpLoopMerge %31 %32 None OpBranch %33 %33 = OpLabel %36 = OpSLessThan %17 %47 %46 OpBranchConditional %36 %30 %31 %30 = OpLabel %39 = OpIAdd %6 %47 %38 OpStore %8 %39 OpBranch %75 %75 = OpLabel %42 = OpFAdd %10 %51 %41 OpStore %12 %42 OpBranch %32 %32 = OpLabel OpBranch %29 %31 = OpLabel %71 = OpPhi %6 %47 %33 %72 = OpPhi %10 %51 %33 OpStore %15 %71 %45 = OpFAdd %10 %72 %13 OpStore %12 %45 OpReturn OpFunctionEnd )"; const auto env = SPV_ENV_UNIVERSAL_1_3; const auto consumer = nullptr; const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); spvtools::ValidatorOptions validator_options; ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); TransformationContext transformation_context( MakeUnique(context.get()), validator_options); // Some inapplicable transformations // Not applicable because there is already an edge 19->20, so the OpPhis at 20 // do not need to be updated ASSERT_FALSE(TransformationAddDeadBreak(19, 20, true, {13, 21}) .IsApplicable(context.get(), transformation_context)); // Not applicable because two OpPhis (not zero) need to be updated at 20 ASSERT_FALSE(TransformationAddDeadBreak(23, 20, true, {}) .IsApplicable(context.get(), transformation_context)); // Not applicable because two OpPhis (not just one) need to be updated at 20 ASSERT_FALSE(TransformationAddDeadBreak(23, 20, true, {13}) .IsApplicable(context.get(), transformation_context)); // Not applicable because the given ids do not have types that match the // OpPhis at 20, in order ASSERT_FALSE(TransformationAddDeadBreak(23, 20, true, {21, 13}) .IsApplicable(context.get(), transformation_context)); // Not applicable because id 23 is a label ASSERT_FALSE(TransformationAddDeadBreak(23, 20, true, {21, 23}) .IsApplicable(context.get(), transformation_context)); // Not applicable because 101 is not an id ASSERT_FALSE(TransformationAddDeadBreak(23, 20, true, {21, 101}) .IsApplicable(context.get(), transformation_context)); // Not applicable because ids 51 and 47 are not available at the end of block // 23 ASSERT_FALSE(TransformationAddDeadBreak(23, 20, true, {51, 47}) .IsApplicable(context.get(), transformation_context)); // Not applicable because OpConstantFalse is not present in the module ASSERT_FALSE(TransformationAddDeadBreak(19, 20, false, {}) .IsApplicable(context.get(), transformation_context)); auto transformation1 = TransformationAddDeadBreak(19, 20, true, {}); auto transformation2 = TransformationAddDeadBreak(23, 20, true, {13, 21}); auto transformation3 = TransformationAddDeadBreak(70, 20, true, {}); auto transformation4 = TransformationAddDeadBreak(30, 31, true, {21, 13}); auto transformation5 = TransformationAddDeadBreak(75, 31, true, {47, 51}); ASSERT_TRUE( transformation1.IsApplicable(context.get(), transformation_context)); ApplyAndCheckFreshIds(transformation1, context.get(), &transformation_context); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); ASSERT_TRUE( transformation2.IsApplicable(context.get(), transformation_context)); ApplyAndCheckFreshIds(transformation2, context.get(), &transformation_context); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); ASSERT_TRUE( transformation3.IsApplicable(context.get(), transformation_context)); ApplyAndCheckFreshIds(transformation3, context.get(), &transformation_context); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); ASSERT_TRUE( transformation4.IsApplicable(context.get(), transformation_context)); ApplyAndCheckFreshIds(transformation4, context.get(), &transformation_context); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); ASSERT_TRUE( transformation5.IsApplicable(context.get(), transformation_context)); ApplyAndCheckFreshIds(transformation5, context.get(), &transformation_context); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); std::string after_transformation = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource ESSL 310 OpName %4 "main" OpName %8 "x" OpName %12 "f" OpName %15 "y" %2 = OpTypeVoid %3 = OpTypeFunction %2 %6 = OpTypeInt 32 1 %7 = OpTypePointer Function %6 %9 = OpConstant %6 2 %10 = OpTypeFloat 32 %11 = OpTypePointer Function %10 %13 = OpConstant %10 3 %17 = OpTypeBool %80 = OpConstantTrue %17 %21 = OpConstant %6 3 %22 = OpConstant %10 4 %27 = OpConstant %10 10 %38 = OpConstant %6 1 %41 = OpConstant %10 1 %46 = OpUndef %6 %4 = OpFunction %2 None %3 %5 = OpLabel %8 = OpVariable %7 Function %12 = OpVariable %11 Function %15 = OpVariable %7 Function OpStore %8 %9 OpStore %12 %13 %18 = OpSGreaterThan %17 %9 %46 OpSelectionMerge %20 None OpBranchConditional %18 %19 %23 %19 = OpLabel OpStore %8 %21 OpStore %12 %22 OpBranchConditional %80 %20 %20 %23 = OpLabel %25 = OpIAdd %6 %9 %9 OpStore %8 %25 OpBranchConditional %80 %70 %20 %70 = OpLabel %28 = OpFAdd %10 %13 %27 OpStore %12 %28 OpBranchConditional %80 %20 %20 %20 = OpLabel %52 = OpPhi %10 %22 %19 %28 %70 %13 %23 %48 = OpPhi %6 %21 %19 %25 %70 %21 %23 OpBranch %29 %29 = OpLabel %51 = OpPhi %10 %52 %20 %42 %32 %47 = OpPhi %6 %48 %20 %39 %32 OpLoopMerge %31 %32 None OpBranch %33 %33 = OpLabel %36 = OpSLessThan %17 %47 %46 OpBranchConditional %36 %30 %31 %30 = OpLabel %39 = OpIAdd %6 %47 %38 OpStore %8 %39 OpBranchConditional %80 %75 %31 %75 = OpLabel %42 = OpFAdd %10 %51 %41 OpStore %12 %42 OpBranchConditional %80 %32 %31 %32 = OpLabel OpBranch %29 %31 = OpLabel %71 = OpPhi %6 %47 %33 %21 %30 %47 %75 %72 = OpPhi %10 %51 %33 %13 %30 %51 %75 OpStore %15 %71 %45 = OpFAdd %10 %72 %13 OpStore %12 %45 OpReturn OpFunctionEnd )"; ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); } TEST(TransformationAddDeadBreakTest, RespectDominanceRules1) { // Right after the loop, an OpCopyObject defined by the loop is used. Adding // a dead break would prevent that use from being dominated by its definition, // so is not allowed. std::string shader = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource ESSL 310 OpName %4 "main" %2 = OpTypeVoid %3 = OpTypeFunction %2 %10 = OpTypeBool %11 = OpConstantFalse %10 %4 = OpFunction %2 None %3 %5 = OpLabel OpBranch %100 %100 = OpLabel OpLoopMerge %101 %102 None OpBranch %103 %103 = OpLabel %200 = OpCopyObject %10 %11 OpBranch %104 %104 = OpLabel OpBranch %102 %102 = OpLabel OpBranchConditional %11 %100 %101 %101 = OpLabel %201 = OpCopyObject %10 %200 OpReturn OpFunctionEnd )"; const auto env = SPV_ENV_UNIVERSAL_1_3; const auto consumer = nullptr; const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); spvtools::ValidatorOptions validator_options; ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); TransformationContext transformation_context( MakeUnique(context.get()), validator_options); auto bad_transformation = TransformationAddDeadBreak(100, 101, false, {}); ASSERT_FALSE( bad_transformation.IsApplicable(context.get(), transformation_context)); } TEST(TransformationAddDeadBreakTest, RespectDominanceRules2) { // This example captures the following idiom: // // if { // L1: // } // definition; // L2: // use; // // Adding a dead jump from L1 to L2 would lead to 'definition' no longer // dominating 'use', and so is not allowed. std::string shader = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource ESSL 310 OpName %4 "main" %2 = OpTypeVoid %3 = OpTypeFunction %2 %10 = OpTypeBool %11 = OpConstantFalse %10 %4 = OpFunction %2 None %3 %5 = OpLabel OpBranch %100 %100 = OpLabel OpSelectionMerge %101 None OpBranchConditional %11 %102 %103 %102 = OpLabel OpBranch %103 %103 = OpLabel %200 = OpCopyObject %10 %11 OpBranch %101 %101 = OpLabel %201 = OpCopyObject %10 %200 OpReturn OpFunctionEnd )"; const auto env = SPV_ENV_UNIVERSAL_1_3; const auto consumer = nullptr; const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); spvtools::ValidatorOptions validator_options; ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); TransformationContext transformation_context( MakeUnique(context.get()), validator_options); auto bad_transformation = TransformationAddDeadBreak(102, 101, false, {}); ASSERT_FALSE( bad_transformation.IsApplicable(context.get(), transformation_context)); } TEST(TransformationAddDeadBreakTest, RespectDominanceRules3) { // Right after the loop, an OpCopyObject defined by the loop is used in an // OpPhi. Adding a dead break is OK in this case, due to the use being in an // OpPhi. std::string shader = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource ESSL 310 OpName %4 "main" %2 = OpTypeVoid %3 = OpTypeFunction %2 %10 = OpTypeBool %11 = OpConstantFalse %10 %4 = OpFunction %2 None %3 %5 = OpLabel OpBranch %100 %100 = OpLabel OpLoopMerge %101 %102 None OpBranch %103 %103 = OpLabel %200 = OpCopyObject %10 %11 OpBranch %104 %104 = OpLabel OpBranch %102 %102 = OpLabel OpBranchConditional %11 %100 %101 %101 = OpLabel %201 = OpPhi %10 %200 %102 OpReturn OpFunctionEnd )"; const auto env = SPV_ENV_UNIVERSAL_1_3; const auto consumer = nullptr; const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); spvtools::ValidatorOptions validator_options; ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); TransformationContext transformation_context( MakeUnique(context.get()), validator_options); auto good_transformation = TransformationAddDeadBreak(100, 101, false, {11}); ASSERT_TRUE( good_transformation.IsApplicable(context.get(), transformation_context)); ApplyAndCheckFreshIds(good_transformation, context.get(), &transformation_context); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); std::string after_transformation = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource ESSL 310 OpName %4 "main" %2 = OpTypeVoid %3 = OpTypeFunction %2 %10 = OpTypeBool %11 = OpConstantFalse %10 %4 = OpFunction %2 None %3 %5 = OpLabel OpBranch %100 %100 = OpLabel OpLoopMerge %101 %102 None OpBranchConditional %11 %101 %103 %103 = OpLabel %200 = OpCopyObject %10 %11 OpBranch %104 %104 = OpLabel OpBranch %102 %102 = OpLabel OpBranchConditional %11 %100 %101 %101 = OpLabel %201 = OpPhi %10 %200 %102 %11 %100 OpReturn OpFunctionEnd )"; ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); } TEST(TransformationAddDeadBreakTest, RespectDominanceRules4) { // This example captures the following idiom: // // if { // L1: // } // definition; // L2: // use in OpPhi; // // Adding a dead jump from L1 to L2 is OK, due to 'use' being in an OpPhi. std::string shader = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource ESSL 310 OpName %4 "main" %2 = OpTypeVoid %3 = OpTypeFunction %2 %10 = OpTypeBool %11 = OpConstantFalse %10 %4 = OpFunction %2 None %3 %5 = OpLabel OpBranch %100 %100 = OpLabel OpSelectionMerge %101 None OpBranchConditional %11 %102 %103 %102 = OpLabel OpBranch %103 %103 = OpLabel %200 = OpCopyObject %10 %11 OpBranch %101 %101 = OpLabel %201 = OpPhi %10 %200 %103 OpReturn OpFunctionEnd )"; const auto env = SPV_ENV_UNIVERSAL_1_3; const auto consumer = nullptr; const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); spvtools::ValidatorOptions validator_options; ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); TransformationContext transformation_context( MakeUnique(context.get()), validator_options); auto good_transformation = TransformationAddDeadBreak(102, 101, false, {11}); ASSERT_TRUE( good_transformation.IsApplicable(context.get(), transformation_context)); ApplyAndCheckFreshIds(good_transformation, context.get(), &transformation_context); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); std::string after_transformation = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource ESSL 310 OpName %4 "main" %2 = OpTypeVoid %3 = OpTypeFunction %2 %10 = OpTypeBool %11 = OpConstantFalse %10 %4 = OpFunction %2 None %3 %5 = OpLabel OpBranch %100 %100 = OpLabel OpSelectionMerge %101 None OpBranchConditional %11 %102 %103 %102 = OpLabel OpBranchConditional %11 %101 %103 %103 = OpLabel %200 = OpCopyObject %10 %11 OpBranch %101 %101 = OpLabel %201 = OpPhi %10 %200 %103 %11 %102 OpReturn OpFunctionEnd )"; ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); } TEST(TransformationAddDeadBreakTest, RespectDominanceRules5) { // After, but not right after, the loop, an OpCopyObject defined by the loop // is used in an OpPhi. Adding a dead break is not OK in this case. std::string shader = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource ESSL 310 OpName %4 "main" %2 = OpTypeVoid %3 = OpTypeFunction %2 %10 = OpTypeBool %11 = OpConstantFalse %10 %4 = OpFunction %2 None %3 %5 = OpLabel OpBranch %100 %100 = OpLabel OpLoopMerge %101 %102 None OpBranch %103 %103 = OpLabel %200 = OpCopyObject %10 %11 OpBranch %104 %104 = OpLabel OpBranch %102 %102 = OpLabel OpBranchConditional %11 %100 %101 %101 = OpLabel OpBranch %105 %105 = OpLabel %201 = OpPhi %10 %200 %101 OpReturn OpFunctionEnd )"; const auto env = SPV_ENV_UNIVERSAL_1_3; const auto consumer = nullptr; const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); spvtools::ValidatorOptions validator_options; ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); TransformationContext transformation_context( MakeUnique(context.get()), validator_options); auto bad_transformation = TransformationAddDeadBreak(100, 101, false, {}); ASSERT_FALSE( bad_transformation.IsApplicable(context.get(), transformation_context)); } TEST(TransformationAddDeadBreakTest, RespectDominanceRules6) { // This example captures the following idiom: // // if { // L1: // } // definition; // L2: // goto L3; // L3: // use in OpPhi; // // Adding a dead jump from L1 to L2 not OK, due to the use in an OpPhi being // in L3. std::string shader = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource ESSL 310 OpName %4 "main" %2 = OpTypeVoid %3 = OpTypeFunction %2 %10 = OpTypeBool %11 = OpConstantFalse %10 %4 = OpFunction %2 None %3 %5 = OpLabel OpBranch %100 %100 = OpLabel OpSelectionMerge %101 None OpBranchConditional %11 %102 %103 %102 = OpLabel OpBranch %103 %103 = OpLabel %200 = OpCopyObject %10 %11 OpBranch %101 %101 = OpLabel OpBranch %150 %150 = OpLabel %201 = OpPhi %10 %200 %101 OpReturn OpFunctionEnd )"; const auto env = SPV_ENV_UNIVERSAL_1_3; const auto consumer = nullptr; const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); spvtools::ValidatorOptions validator_options; ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); TransformationContext transformation_context( MakeUnique(context.get()), validator_options); auto bad_transformation = TransformationAddDeadBreak(102, 101, false, {}); ASSERT_FALSE( bad_transformation.IsApplicable(context.get(), transformation_context)); } TEST(TransformationAddDeadBreakTest, RespectDominanceRules7) { // This example - a variation on an earlier test - captures the following // idiom: // // loop { // L1: // } // definition; // L2: // use; // // Adding a dead jump from L1 to L2 would lead to 'definition' no longer // dominating 'use', and so is not allowed. // // This version of the test captures the case where L1 appears after the // loop merge (which SPIR-V dominance rules allow). std::string shader = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource ESSL 310 OpName %4 "main" %2 = OpTypeVoid %3 = OpTypeFunction %2 %10 = OpTypeBool %11 = OpConstantFalse %10 %4 = OpFunction %2 None %3 %5 = OpLabel OpBranch %100 %100 = OpLabel OpLoopMerge %101 %104 None OpBranch %105 %105 = OpLabel OpSelectionMerge %106 None OpBranchConditional %11 %102 %103 %103 = OpLabel %200 = OpCopyObject %10 %11 OpBranch %101 %101 = OpLabel %201 = OpCopyObject %10 %200 OpReturn %102 = OpLabel OpBranch %103 %106 = OpLabel OpUnreachable %104 = OpLabel OpBranch %100 OpFunctionEnd )"; const auto env = SPV_ENV_UNIVERSAL_1_3; const auto consumer = nullptr; const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); spvtools::ValidatorOptions validator_options; ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); TransformationContext transformation_context( MakeUnique(context.get()), validator_options); auto bad_transformation = TransformationAddDeadBreak(102, 101, false, {}); ASSERT_FALSE( bad_transformation.IsApplicable(context.get(), transformation_context)); } TEST(TransformationAddDeadBreakTest, RespectDominanceRules8) { // A variation of RespectDominanceRules8 where the defining block appears // in the loop, but after the definition of interest. std::string shader = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource ESSL 310 OpName %4 "main" %2 = OpTypeVoid %3 = OpTypeFunction %2 %10 = OpTypeBool %11 = OpConstantFalse %10 %4 = OpFunction %2 None %3 %5 = OpLabel OpBranch %100 %100 = OpLabel OpLoopMerge %101 %104 None OpBranch %105 %105 = OpLabel OpSelectionMerge %106 None OpBranchConditional %11 %102 %103 %103 = OpLabel %200 = OpCopyObject %10 %11 OpBranch %101 %102 = OpLabel OpBranch %103 %106 = OpLabel OpUnreachable %101 = OpLabel %201 = OpCopyObject %10 %200 OpReturn %104 = OpLabel OpBranch %100 OpFunctionEnd )"; const auto env = SPV_ENV_UNIVERSAL_1_3; const auto consumer = nullptr; const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); spvtools::ValidatorOptions validator_options; ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); TransformationContext transformation_context( MakeUnique(context.get()), validator_options); auto bad_transformation = TransformationAddDeadBreak(102, 101, false, {}); ASSERT_FALSE( bad_transformation.IsApplicable(context.get(), transformation_context)); } TEST(TransformationAddDeadBreakTest, BreakWouldDisobeyDominanceBlockOrderingRules) { std::string shader = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource ESSL 310 %2 = OpTypeVoid %3 = OpTypeFunction %2 %6 = OpTypeBool %9 = OpConstantTrue %6 %4 = OpFunction %2 None %3 %5 = OpLabel OpBranch %10 %10 = OpLabel OpLoopMerge %16 %15 None OpBranch %11 %11 = OpLabel OpSelectionMerge %14 None OpBranchConditional %9 %12 %13 %14 = OpLabel OpBranch %15 %12 = OpLabel OpBranch %16 %13 = OpLabel OpBranch %16 %15 = OpLabel OpBranch %10 %16 = OpLabel OpReturn OpFunctionEnd )"; const auto env = SPV_ENV_UNIVERSAL_1_3; const auto consumer = nullptr; const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); spvtools::ValidatorOptions validator_options; ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); TransformationContext transformation_context( MakeUnique(context.get()), validator_options); // Bad because 14 comes before 12 in the module, and 14 has no predecessors. // This means that an edge from 12 to 14 will lead to 12 dominating 14, which // is illegal if 12 appears after 14. auto bad_transformation = TransformationAddDeadBreak(12, 14, true, {}); ASSERT_FALSE( bad_transformation.IsApplicable(context.get(), transformation_context)); } } // namespace } // namespace fuzz } // namespace spvtools