mirror of
https://github.com/KhronosGroup/SPIRV-Tools
synced 2024-12-26 01:31:06 +00:00
1a89ac8b28
Similar to the existing 'add dead breaks' pass, this adds a pass to add dead continues to blocks in loops where such a transformation is viable. Various functionality common to this new pass and 'add dead breaks' has been factored into 'fuzzer_util', and some small improvements to 'add dead breaks' that were identified while reviewing that code again have been applied. Fixes #2719.
1035 lines
34 KiB
C++
1035 lines
34 KiB
C++
// 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_continue.h"
|
|
#include "test/fuzz/fuzz_test_util.h"
|
|
|
|
namespace spvtools {
|
|
namespace fuzz {
|
|
namespace {
|
|
|
|
TEST(TransformationAddDeadContinueTest, SimpleExample) {
|
|
// For a simple loop, checks that some dead continue scenarios are possible,
|
|
// sanity-checks that some illegal scenarios are indeed not allowed, and then
|
|
// applies a transformation.
|
|
|
|
// 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 = 0;
|
|
// for (int i = 0; i < 10; i++) {
|
|
// x = x + i;
|
|
// x = x + 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 "x"
|
|
OpName %10 "i"
|
|
%2 = OpTypeVoid
|
|
%3 = OpTypeFunction %2
|
|
%6 = OpTypeInt 32 1
|
|
%7 = OpTypePointer Function %6
|
|
%9 = OpConstant %6 0
|
|
%17 = OpConstant %6 10
|
|
%18 = OpTypeBool
|
|
%41 = OpConstantTrue %18
|
|
%42 = OpConstantFalse %18
|
|
%27 = OpConstant %6 1
|
|
%4 = OpFunction %2 None %3
|
|
%5 = OpLabel
|
|
%8 = OpVariable %7 Function
|
|
%10 = OpVariable %7 Function
|
|
OpStore %8 %9
|
|
OpStore %10 %9
|
|
OpBranch %11
|
|
%11 = OpLabel
|
|
OpLoopMerge %13 %14 None
|
|
OpBranch %15
|
|
%15 = OpLabel
|
|
%16 = OpLoad %6 %10
|
|
%19 = OpSLessThan %18 %16 %17
|
|
OpBranchConditional %19 %12 %13
|
|
%12 = OpLabel
|
|
%20 = OpLoad %6 %8
|
|
%21 = OpLoad %6 %10
|
|
%22 = OpIAdd %6 %20 %21
|
|
OpStore %8 %22
|
|
OpBranch %40
|
|
%40 = OpLabel
|
|
%23 = OpLoad %6 %8
|
|
%24 = OpLoad %6 %10
|
|
%25 = OpIAdd %6 %23 %24
|
|
OpStore %8 %25
|
|
OpBranch %14
|
|
%14 = OpLabel
|
|
%26 = OpLoad %6 %10
|
|
%28 = OpIAdd %6 %26 %27
|
|
OpStore %10 %28
|
|
OpBranch %11
|
|
%13 = OpLabel
|
|
OpReturn
|
|
OpFunctionEnd
|
|
)";
|
|
|
|
const auto env = SPV_ENV_UNIVERSAL_1_3;
|
|
const auto consumer = nullptr;
|
|
const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
|
|
ASSERT_TRUE(IsValid(env, context.get()));
|
|
FactManager fact_manager;
|
|
|
|
// These are all possibilities.
|
|
ASSERT_TRUE(TransformationAddDeadContinue(11, true, {})
|
|
.IsApplicable(context.get(), fact_manager));
|
|
ASSERT_TRUE(TransformationAddDeadContinue(11, false, {})
|
|
.IsApplicable(context.get(), fact_manager));
|
|
ASSERT_TRUE(TransformationAddDeadContinue(12, true, {})
|
|
.IsApplicable(context.get(), fact_manager));
|
|
ASSERT_TRUE(TransformationAddDeadContinue(12, false, {})
|
|
.IsApplicable(context.get(), fact_manager));
|
|
ASSERT_TRUE(TransformationAddDeadContinue(40, true, {})
|
|
.IsApplicable(context.get(), fact_manager));
|
|
ASSERT_TRUE(TransformationAddDeadContinue(40, false, {})
|
|
.IsApplicable(context.get(), fact_manager));
|
|
|
|
// Inapplicable: 100 is not a block id.
|
|
ASSERT_FALSE(TransformationAddDeadContinue(100, true, {})
|
|
.IsApplicable(context.get(), fact_manager));
|
|
|
|
// Inapplicable: 10 is not in a loop.
|
|
ASSERT_FALSE(TransformationAddDeadContinue(10, true, {})
|
|
.IsApplicable(context.get(), fact_manager));
|
|
|
|
// Inapplicable: 15 does not branch unconditionally to a single successor.
|
|
ASSERT_FALSE(TransformationAddDeadContinue(15, true, {})
|
|
.IsApplicable(context.get(), fact_manager));
|
|
|
|
// Inapplicable: 13 is not in a loop and has no successor.
|
|
ASSERT_FALSE(TransformationAddDeadContinue(13, true, {})
|
|
.IsApplicable(context.get(), fact_manager));
|
|
|
|
// Inapplicable: 14 is the loop continue target, so it's not OK to jump to
|
|
// the loop continue from there.
|
|
ASSERT_FALSE(TransformationAddDeadContinue(14, false, {})
|
|
.IsApplicable(context.get(), fact_manager));
|
|
|
|
// These are the transformations we will apply.
|
|
auto transformation1 = TransformationAddDeadContinue(11, true, {});
|
|
auto transformation2 = TransformationAddDeadContinue(12, false, {});
|
|
auto transformation3 = TransformationAddDeadContinue(40, true, {});
|
|
|
|
ASSERT_TRUE(transformation1.IsApplicable(context.get(), fact_manager));
|
|
transformation1.Apply(context.get(), &fact_manager);
|
|
ASSERT_TRUE(IsValid(env, context.get()));
|
|
|
|
ASSERT_TRUE(transformation2.IsApplicable(context.get(), fact_manager));
|
|
transformation2.Apply(context.get(), &fact_manager);
|
|
ASSERT_TRUE(IsValid(env, context.get()));
|
|
|
|
ASSERT_TRUE(transformation3.IsApplicable(context.get(), fact_manager));
|
|
transformation3.Apply(context.get(), &fact_manager);
|
|
ASSERT_TRUE(IsValid(env, context.get()));
|
|
|
|
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 %10 "i"
|
|
%2 = OpTypeVoid
|
|
%3 = OpTypeFunction %2
|
|
%6 = OpTypeInt 32 1
|
|
%7 = OpTypePointer Function %6
|
|
%9 = OpConstant %6 0
|
|
%17 = OpConstant %6 10
|
|
%18 = OpTypeBool
|
|
%41 = OpConstantTrue %18
|
|
%42 = OpConstantFalse %18
|
|
%27 = OpConstant %6 1
|
|
%4 = OpFunction %2 None %3
|
|
%5 = OpLabel
|
|
%8 = OpVariable %7 Function
|
|
%10 = OpVariable %7 Function
|
|
OpStore %8 %9
|
|
OpStore %10 %9
|
|
OpBranch %11
|
|
%11 = OpLabel
|
|
OpLoopMerge %13 %14 None
|
|
OpBranchConditional %41 %15 %14
|
|
%15 = OpLabel
|
|
%16 = OpLoad %6 %10
|
|
%19 = OpSLessThan %18 %16 %17
|
|
OpBranchConditional %19 %12 %13
|
|
%12 = OpLabel
|
|
%20 = OpLoad %6 %8
|
|
%21 = OpLoad %6 %10
|
|
%22 = OpIAdd %6 %20 %21
|
|
OpStore %8 %22
|
|
OpBranchConditional %42 %14 %40
|
|
%40 = OpLabel
|
|
%23 = OpLoad %6 %8
|
|
%24 = OpLoad %6 %10
|
|
%25 = OpIAdd %6 %23 %24
|
|
OpStore %8 %25
|
|
OpBranchConditional %41 %14 %14
|
|
%14 = OpLabel
|
|
%26 = OpLoad %6 %10
|
|
%28 = OpIAdd %6 %26 %27
|
|
OpStore %10 %28
|
|
OpBranch %11
|
|
%13 = OpLabel
|
|
OpReturn
|
|
OpFunctionEnd
|
|
)";
|
|
|
|
ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
|
|
}
|
|
|
|
TEST(TransformationAddDeadContinueTest,
|
|
DoNotAllowContinueToMergeBlockOfAnotherLoop) {
|
|
// A loop header must dominate its merge block if that merge block is
|
|
// reachable. We are thus not allowed to add a dead continue that would result
|
|
// in violation of this property. This test checks for such a scenario.
|
|
|
|
std::string shader = R"(
|
|
OpCapability Shader
|
|
%1 = OpExtInstImport "GLSL.std.450"
|
|
OpMemoryModel Logical GLSL450
|
|
OpEntryPoint Fragment %4 "main" %16 %139
|
|
OpExecutionMode %4 OriginUpperLeft
|
|
OpSource ESSL 310
|
|
%2 = OpTypeVoid
|
|
%3 = OpTypeFunction %2
|
|
%6 = OpTypeFloat 32
|
|
%7 = OpTypePointer Function %6
|
|
%8 = OpTypeBool
|
|
%14 = OpTypeVector %6 4
|
|
%15 = OpTypePointer Input %14
|
|
%16 = OpVariable %15 Input
|
|
%138 = OpTypePointer Output %14
|
|
%139 = OpVariable %138 Output
|
|
%400 = OpConstantTrue %8
|
|
%4 = OpFunction %2 None %3
|
|
%5 = OpLabel
|
|
OpBranch %500
|
|
%500 = OpLabel
|
|
OpLoopMerge %501 %502 None
|
|
OpBranch %503 ; We are not allowed to change this to OpBranchConditional %400 %503 %502
|
|
%503 = OpLabel
|
|
OpLoopMerge %502 %504 None
|
|
OpBranchConditional %400 %505 %504
|
|
%505 = OpLabel
|
|
OpBranch %502
|
|
%504 = OpLabel
|
|
OpBranch %503
|
|
%502 = OpLabel
|
|
OpBranchConditional %400 %501 %500
|
|
%501 = OpLabel
|
|
OpReturn
|
|
OpFunctionEnd
|
|
)";
|
|
|
|
const auto env = SPV_ENV_UNIVERSAL_1_3;
|
|
const auto consumer = nullptr;
|
|
const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
|
|
ASSERT_TRUE(IsValid(env, context.get()));
|
|
FactManager fact_manager;
|
|
|
|
ASSERT_FALSE(TransformationAddDeadContinue(500, true, {})
|
|
.IsApplicable(context.get(), fact_manager));
|
|
ASSERT_FALSE(TransformationAddDeadContinue(500, false, {})
|
|
.IsApplicable(context.get(), fact_manager));
|
|
}
|
|
|
|
TEST(TransformationAddDeadContinueTest, DoNotAllowContinueToSelectionMerge) {
|
|
// A selection header must dominate its merge block if that merge block is
|
|
// reachable. We are thus not allowed to add a dead continue that would result
|
|
// in violation of this property. This test checks for such a scenario.
|
|
|
|
std::string shader = R"(
|
|
OpCapability Shader
|
|
%1 = OpExtInstImport "GLSL.std.450"
|
|
OpMemoryModel Logical GLSL450
|
|
OpEntryPoint Fragment %4 "main" %16 %139
|
|
OpExecutionMode %4 OriginUpperLeft
|
|
OpSource ESSL 310
|
|
%2 = OpTypeVoid
|
|
%3 = OpTypeFunction %2
|
|
%6 = OpTypeFloat 32
|
|
%7 = OpTypePointer Function %6
|
|
%8 = OpTypeBool
|
|
%14 = OpTypeVector %6 4
|
|
%15 = OpTypePointer Input %14
|
|
%16 = OpVariable %15 Input
|
|
%138 = OpTypePointer Output %14
|
|
%139 = OpVariable %138 Output
|
|
%400 = OpConstantTrue %8
|
|
%4 = OpFunction %2 None %3
|
|
%5 = OpLabel
|
|
OpBranch %500
|
|
%500 = OpLabel
|
|
OpLoopMerge %501 %502 None
|
|
OpBranch %503 ; We are not allowed to change this to OpBranchConditional %400 %503 %502
|
|
%503 = OpLabel
|
|
OpSelectionMerge %502 None
|
|
OpBranchConditional %400 %505 %504
|
|
%505 = OpLabel
|
|
OpBranch %502
|
|
%504 = OpLabel
|
|
OpBranch %502
|
|
%502 = OpLabel
|
|
OpBranchConditional %400 %501 %500
|
|
%501 = OpLabel
|
|
OpReturn
|
|
OpFunctionEnd
|
|
)";
|
|
|
|
const auto env = SPV_ENV_UNIVERSAL_1_3;
|
|
const auto consumer = nullptr;
|
|
const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
|
|
ASSERT_TRUE(IsValid(env, context.get()));
|
|
FactManager fact_manager;
|
|
|
|
ASSERT_FALSE(TransformationAddDeadContinue(500, true, {})
|
|
.IsApplicable(context.get(), fact_manager));
|
|
ASSERT_FALSE(TransformationAddDeadContinue(500, false, {})
|
|
.IsApplicable(context.get(), fact_manager));
|
|
}
|
|
|
|
TEST(TransformationAddDeadContinueTest, LoopNest) {
|
|
// Checks some allowed and disallowed scenarios for a nest of loops, including
|
|
// continuing a loop from an if or switch.
|
|
|
|
// 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);
|
|
ASSERT_TRUE(IsValid(env, context.get()));
|
|
|
|
FactManager fact_manager;
|
|
|
|
std::vector<uint32_t> good = {6, 7, 18, 20, 34, 40, 45, 46, 47, 56, 57};
|
|
std::vector<uint32_t> bad = {5, 8, 9, 19, 21, 22, 33, 41, 58, 59, 60};
|
|
|
|
for (uint32_t from_block : bad) {
|
|
ASSERT_FALSE(TransformationAddDeadContinue(from_block, true, {})
|
|
.IsApplicable(context.get(), fact_manager));
|
|
}
|
|
for (uint32_t from_block : good) {
|
|
const TransformationAddDeadContinue transformation(from_block, true, {});
|
|
ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
|
|
transformation.Apply(context.get(), &fact_manager);
|
|
ASSERT_FALSE(transformation.IsApplicable(context.get(), fact_manager));
|
|
}
|
|
|
|
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 %9
|
|
%7 = OpLabel
|
|
%13 = OpLoad %10 %12
|
|
%15 = OpIAdd %10 %13 %14
|
|
OpStore %12 %15
|
|
OpStore %16 %17
|
|
OpBranchConditional %67 %18 %9
|
|
%18 = OpLabel
|
|
OpLoopMerge %20 %21 None
|
|
OpBranchConditional %67 %22 %21
|
|
%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 %67 %41 %21
|
|
%41 = OpLabel
|
|
%44 = OpLoad %10 %12
|
|
OpSelectionMerge %47 None
|
|
OpSwitch %44 %46 0 %45
|
|
%46 = OpLabel
|
|
OpBranchConditional %67 %47 %21
|
|
%45 = OpLabel
|
|
OpStore %12 %38
|
|
OpBranchConditional %67 %46 %21
|
|
%47 = OpLabel
|
|
OpBranchConditional %67 %34 %21
|
|
%34 = OpLabel
|
|
OpBranchConditional %67 %21 %21
|
|
%21 = OpLabel
|
|
%50 = OpLoad %10 %16
|
|
%51 = OpIAdd %10 %50 %14
|
|
OpStore %16 %51
|
|
OpBranch %18
|
|
%20 = OpLabel
|
|
OpBranchConditional %67 %9 %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 %67 %60 %59
|
|
%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 %59
|
|
%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(TransformationAddDeadConditionalTest, 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 = OpConstantFalse %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);
|
|
ASSERT_TRUE(IsValid(env, context.get()));
|
|
|
|
FactManager fact_manager;
|
|
|
|
std::vector<uint32_t> good = {32, 33, 46, 52, 101};
|
|
std::vector<uint32_t> bad = {5, 34, 36, 35, 47, 49, 48};
|
|
|
|
for (uint32_t from_block : bad) {
|
|
ASSERT_FALSE(TransformationAddDeadContinue(from_block, false, {})
|
|
.IsApplicable(context.get(), fact_manager));
|
|
}
|
|
for (uint32_t from_block : good) {
|
|
const TransformationAddDeadContinue transformation(from_block, false, {});
|
|
ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
|
|
transformation.Apply(context.get(), &fact_manager);
|
|
ASSERT_FALSE(transformation.IsApplicable(context.get(), fact_manager));
|
|
}
|
|
|
|
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 = OpConstantFalse %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
|
|
OpBranchConditional %100 %35 %36
|
|
%36 = OpLabel
|
|
%37 = OpLoad %6 %31
|
|
%39 = OpSLessThan %21 %37 %38
|
|
OpBranchConditional %39 %33 %34
|
|
%33 = OpLabel
|
|
OpBranchConditional %100 %35 %35
|
|
%35 = OpLabel
|
|
OpStore %43 %12
|
|
OpStore %44 %12
|
|
OpBranch %46
|
|
%46 = OpLabel
|
|
OpLoopMerge %47 %48 None
|
|
OpBranchConditional %100 %48 %49
|
|
%49 = OpLabel
|
|
%50 = OpLoad %6 %44
|
|
%51 = OpSLessThan %21 %50 %20
|
|
OpBranchConditional %51 %52 %47
|
|
%52 = OpLabel
|
|
%53 = OpLoad %6 %43
|
|
OpBranchConditional %100 %48 %101
|
|
%101 = OpLabel
|
|
%54 = OpIAdd %6 %53 %24
|
|
OpStore %43 %54
|
|
OpBranchConditional %100 %48 %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(TransformationAddDeadContinueTest, 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 %100 %32
|
|
%47 = OpPhi %6 %48 %20 %101 %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
|
|
%100 = OpPhi %10 %42 %75
|
|
%101 = OpPhi %6 %39 %75
|
|
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);
|
|
ASSERT_TRUE(IsValid(env, context.get()));
|
|
|
|
FactManager fact_manager;
|
|
|
|
std::vector<uint32_t> bad = {5, 19, 20, 23, 31, 32, 33, 70};
|
|
|
|
std::vector<uint32_t> good = {29, 30, 75};
|
|
|
|
for (uint32_t from_block : bad) {
|
|
ASSERT_FALSE(TransformationAddDeadContinue(from_block, true, {})
|
|
.IsApplicable(context.get(), fact_manager));
|
|
}
|
|
auto transformation1 = TransformationAddDeadContinue(29, true, {13, 21});
|
|
ASSERT_TRUE(transformation1.IsApplicable(context.get(), fact_manager));
|
|
transformation1.Apply(context.get(), &fact_manager);
|
|
|
|
auto transformation2 = TransformationAddDeadContinue(30, true, {22, 46});
|
|
ASSERT_TRUE(transformation2.IsApplicable(context.get(), fact_manager));
|
|
transformation2.Apply(context.get(), &fact_manager);
|
|
|
|
// 75 already has the continue block as a successor, so we should not provide
|
|
// phi ids.
|
|
auto transformationBad = TransformationAddDeadContinue(75, true, {27, 46});
|
|
ASSERT_FALSE(transformationBad.IsApplicable(context.get(), fact_manager));
|
|
|
|
auto transformation3 = TransformationAddDeadContinue(75, true, {});
|
|
ASSERT_TRUE(transformation3.IsApplicable(context.get(), fact_manager));
|
|
transformation3.Apply(context.get(), &fact_manager);
|
|
|
|
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
|
|
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 %100 %32
|
|
%47 = OpPhi %6 %48 %20 %101 %32
|
|
OpLoopMerge %31 %32 None
|
|
OpBranchConditional %80 %33 %32
|
|
%33 = OpLabel
|
|
%36 = OpSLessThan %17 %47 %46
|
|
OpBranchConditional %36 %30 %31
|
|
%30 = OpLabel
|
|
%39 = OpIAdd %6 %47 %38
|
|
OpStore %8 %39
|
|
OpBranchConditional %80 %75 %32
|
|
%75 = OpLabel
|
|
%42 = OpFAdd %10 %51 %41
|
|
OpStore %12 %42
|
|
OpBranchConditional %80 %32 %32
|
|
%32 = OpLabel
|
|
%100 = OpPhi %10 %42 %75 %13 %29 %22 %30
|
|
%101 = OpPhi %6 %39 %75 %21 %29 %46 %30
|
|
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
|
|
)";
|
|
|
|
ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace fuzz
|
|
} // namespace spvtools
|