mirror of
https://github.com/KhronosGroup/SPIRV-Tools
synced 2024-12-29 03:01:08 +00:00
502e982956
This fixes a problem where TransformationInlineFunction could lead to distinct instructions having identical unique ids. It adds a validity check to detect this problem in general. Fixes #3911.
379 lines
11 KiB
C++
379 lines
11 KiB
C++
// Copyright (c) 2020 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/call_graph.h"
|
|
|
|
#include "gtest/gtest.h"
|
|
#include "source/fuzz/fuzzer_util.h"
|
|
#include "test/fuzz/fuzz_test_util.h"
|
|
|
|
namespace spvtools {
|
|
namespace fuzz {
|
|
namespace {
|
|
|
|
// The SPIR-V came from this GLSL, slightly modified
|
|
// (main is %2, A is %35, B is %48, C is %50, D is %61):
|
|
//
|
|
// #version 310 es
|
|
//
|
|
// int A (int x) {
|
|
// return x + 1;
|
|
// }
|
|
//
|
|
// void D() {
|
|
// }
|
|
//
|
|
// void C() {
|
|
// int x = 0;
|
|
// int y = 0;
|
|
//
|
|
// while (x < 10) {
|
|
// while (y < 10) {
|
|
// y = A(y);
|
|
// }
|
|
// x = A(x);
|
|
// }
|
|
// }
|
|
//
|
|
// void B () {
|
|
// int x = 0;
|
|
// int y = 0;
|
|
//
|
|
// while (x < 10) {
|
|
// D();
|
|
// while (y < 10) {
|
|
// y = A(y);
|
|
// C();
|
|
// }
|
|
// x++;
|
|
// }
|
|
//
|
|
// }
|
|
//
|
|
// void main()
|
|
// {
|
|
// int x = 0;
|
|
// int y = 0;
|
|
// int z = 0;
|
|
//
|
|
// while (x < 10) {
|
|
// while(y < 10) {
|
|
// y = A(x);
|
|
// while (z < 10) {
|
|
// z = A(z);
|
|
// }
|
|
// }
|
|
// x += 2;
|
|
// }
|
|
//
|
|
// B();
|
|
// C();
|
|
// }
|
|
std::string shader = R"(
|
|
OpCapability Shader
|
|
%1 = OpExtInstImport "GLSL.std.450"
|
|
OpMemoryModel Logical GLSL450
|
|
OpEntryPoint Fragment %2 "main"
|
|
OpExecutionMode %2 OriginUpperLeft
|
|
OpSource ESSL 310
|
|
%3 = OpTypeVoid
|
|
%4 = OpTypeFunction %3
|
|
%5 = OpTypeInt 32 1
|
|
%6 = OpTypePointer Function %5
|
|
%7 = OpTypeFunction %5 %6
|
|
%8 = OpConstant %5 1
|
|
%9 = OpConstant %5 0
|
|
%10 = OpConstant %5 10
|
|
%11 = OpTypeBool
|
|
%12 = OpConstant %5 2
|
|
%2 = OpFunction %3 None %4
|
|
%13 = OpLabel
|
|
%14 = OpVariable %6 Function
|
|
%15 = OpVariable %6 Function
|
|
%16 = OpVariable %6 Function
|
|
%17 = OpVariable %6 Function
|
|
%18 = OpVariable %6 Function
|
|
OpStore %14 %9
|
|
OpStore %15 %9
|
|
OpStore %16 %9
|
|
OpBranch %19
|
|
%19 = OpLabel
|
|
OpLoopMerge %20 %21 None
|
|
OpBranch %22
|
|
%22 = OpLabel
|
|
%23 = OpLoad %5 %14
|
|
%24 = OpSLessThan %11 %23 %10
|
|
OpBranchConditional %24 %25 %20
|
|
%25 = OpLabel
|
|
OpBranch %26
|
|
%26 = OpLabel
|
|
OpLoopMerge %27 %28 None
|
|
OpBranch %29
|
|
%29 = OpLabel
|
|
%30 = OpLoad %5 %15
|
|
%31 = OpSLessThan %11 %30 %10
|
|
OpBranchConditional %31 %32 %27
|
|
%32 = OpLabel
|
|
%33 = OpLoad %5 %14
|
|
OpStore %17 %33
|
|
%34 = OpFunctionCall %5 %35 %17
|
|
OpStore %15 %34
|
|
OpBranch %36
|
|
%36 = OpLabel
|
|
OpLoopMerge %37 %38 None
|
|
OpBranch %39
|
|
%39 = OpLabel
|
|
%40 = OpLoad %5 %16
|
|
%41 = OpSLessThan %11 %40 %10
|
|
OpBranchConditional %41 %42 %37
|
|
%42 = OpLabel
|
|
%43 = OpLoad %5 %16
|
|
OpStore %18 %43
|
|
%44 = OpFunctionCall %5 %35 %18
|
|
OpStore %16 %44
|
|
OpBranch %38
|
|
%38 = OpLabel
|
|
OpBranch %36
|
|
%37 = OpLabel
|
|
OpBranch %28
|
|
%28 = OpLabel
|
|
OpBranch %26
|
|
%27 = OpLabel
|
|
%45 = OpLoad %5 %14
|
|
%46 = OpIAdd %5 %45 %12
|
|
OpStore %14 %46
|
|
OpBranch %21
|
|
%21 = OpLabel
|
|
OpBranch %19
|
|
%20 = OpLabel
|
|
%47 = OpFunctionCall %3 %48
|
|
%49 = OpFunctionCall %3 %50
|
|
OpReturn
|
|
OpFunctionEnd
|
|
%35 = OpFunction %5 None %7
|
|
%51 = OpFunctionParameter %6
|
|
%52 = OpLabel
|
|
%53 = OpLoad %5 %51
|
|
%54 = OpIAdd %5 %53 %8
|
|
OpReturnValue %54
|
|
OpFunctionEnd
|
|
%48 = OpFunction %3 None %4
|
|
%55 = OpLabel
|
|
%56 = OpVariable %6 Function
|
|
%57 = OpVariable %6 Function
|
|
%58 = OpVariable %6 Function
|
|
OpStore %56 %9
|
|
OpStore %57 %9
|
|
OpBranch %59
|
|
%59 = OpLabel
|
|
%60 = OpFunctionCall %3 %61
|
|
OpLoopMerge %62 %63 None
|
|
OpBranch %64
|
|
%64 = OpLabel
|
|
OpLoopMerge %65 %66 None
|
|
OpBranch %67
|
|
%67 = OpLabel
|
|
%68 = OpLoad %5 %57
|
|
%69 = OpSLessThan %11 %68 %10
|
|
OpBranchConditional %69 %70 %65
|
|
%70 = OpLabel
|
|
%71 = OpLoad %5 %57
|
|
OpStore %58 %71
|
|
%72 = OpFunctionCall %5 %35 %58
|
|
OpStore %57 %72
|
|
%73 = OpFunctionCall %3 %50
|
|
OpBranch %66
|
|
%66 = OpLabel
|
|
OpBranch %64
|
|
%65 = OpLabel
|
|
%74 = OpLoad %5 %56
|
|
%75 = OpIAdd %5 %74 %8
|
|
OpStore %56 %75
|
|
OpBranch %63
|
|
%63 = OpLabel
|
|
%76 = OpLoad %5 %56
|
|
%77 = OpSLessThan %11 %76 %10
|
|
OpBranchConditional %77 %59 %62
|
|
%62 = OpLabel
|
|
OpReturn
|
|
OpFunctionEnd
|
|
%50 = OpFunction %3 None %4
|
|
%78 = OpLabel
|
|
%79 = OpVariable %6 Function
|
|
%80 = OpVariable %6 Function
|
|
%81 = OpVariable %6 Function
|
|
%82 = OpVariable %6 Function
|
|
OpStore %79 %9
|
|
OpStore %80 %9
|
|
OpBranch %83
|
|
%83 = OpLabel
|
|
OpLoopMerge %84 %85 None
|
|
OpBranch %86
|
|
%86 = OpLabel
|
|
%87 = OpLoad %5 %79
|
|
%88 = OpSLessThan %11 %87 %10
|
|
OpBranchConditional %88 %89 %84
|
|
%89 = OpLabel
|
|
OpBranch %90
|
|
%90 = OpLabel
|
|
OpLoopMerge %91 %92 None
|
|
OpBranch %93
|
|
%93 = OpLabel
|
|
%94 = OpLoad %5 %80
|
|
%95 = OpSLessThan %11 %94 %10
|
|
OpBranchConditional %95 %96 %91
|
|
%96 = OpLabel
|
|
%97 = OpLoad %5 %80
|
|
OpStore %81 %97
|
|
%98 = OpFunctionCall %5 %35 %81
|
|
OpStore %80 %98
|
|
OpBranch %92
|
|
%92 = OpLabel
|
|
OpBranch %90
|
|
%91 = OpLabel
|
|
%99 = OpLoad %5 %79
|
|
OpStore %82 %99
|
|
%100 = OpFunctionCall %5 %35 %82
|
|
OpStore %79 %100
|
|
OpBranch %85
|
|
%85 = OpLabel
|
|
OpBranch %83
|
|
%84 = OpLabel
|
|
OpReturn
|
|
OpFunctionEnd
|
|
%61 = OpFunction %3 None %4
|
|
%101 = OpLabel
|
|
OpReturn
|
|
OpFunctionEnd
|
|
)";
|
|
|
|
// We have that:
|
|
// main calls:
|
|
// - A (maximum loop nesting depth of function call: 3)
|
|
// - B (0)
|
|
// - C (0)
|
|
// A calls nothing.
|
|
// B calls:
|
|
// - A (2)
|
|
// - C (2)
|
|
// - D (1)
|
|
// C calls:
|
|
// - A (2)
|
|
// D calls nothing.
|
|
|
|
TEST(CallGraphTest, FunctionInDegree) {
|
|
const auto env = SPV_ENV_UNIVERSAL_1_5;
|
|
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));
|
|
|
|
const auto graph = CallGraph(context.get());
|
|
|
|
const auto& function_in_degree = graph.GetFunctionInDegree();
|
|
// Check the in-degrees of, in order: main, A, B, C, D.
|
|
ASSERT_EQ(function_in_degree.at(2), 0);
|
|
ASSERT_EQ(function_in_degree.at(35), 3);
|
|
ASSERT_EQ(function_in_degree.at(48), 1);
|
|
ASSERT_EQ(function_in_degree.at(50), 2);
|
|
ASSERT_EQ(function_in_degree.at(61), 1);
|
|
}
|
|
|
|
TEST(CallGraphTest, DirectCallees) {
|
|
const auto env = SPV_ENV_UNIVERSAL_1_5;
|
|
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));
|
|
|
|
const auto graph = CallGraph(context.get());
|
|
|
|
// Check the callee sets of, in order: main, A, B, C, D.
|
|
ASSERT_EQ(graph.GetDirectCallees(2), std::set<uint32_t>({35, 48, 50}));
|
|
ASSERT_EQ(graph.GetDirectCallees(35), std::set<uint32_t>({}));
|
|
ASSERT_EQ(graph.GetDirectCallees(48), std::set<uint32_t>({35, 50, 61}));
|
|
ASSERT_EQ(graph.GetDirectCallees(50), std::set<uint32_t>({35}));
|
|
ASSERT_EQ(graph.GetDirectCallees(61), std::set<uint32_t>({}));
|
|
}
|
|
|
|
TEST(CallGraphTest, IndirectCallees) {
|
|
const auto env = SPV_ENV_UNIVERSAL_1_5;
|
|
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));
|
|
|
|
const auto graph = CallGraph(context.get());
|
|
|
|
// Check the callee sets of, in order: main, A, B, C, D.
|
|
ASSERT_EQ(graph.GetIndirectCallees(2), std::set<uint32_t>({35, 48, 50, 61}));
|
|
ASSERT_EQ(graph.GetDirectCallees(35), std::set<uint32_t>({}));
|
|
ASSERT_EQ(graph.GetDirectCallees(48), std::set<uint32_t>({35, 50, 61}));
|
|
ASSERT_EQ(graph.GetDirectCallees(50), std::set<uint32_t>({35}));
|
|
ASSERT_EQ(graph.GetDirectCallees(61), std::set<uint32_t>({}));
|
|
}
|
|
|
|
TEST(CallGraphTest, TopologicalOrder) {
|
|
const auto env = SPV_ENV_UNIVERSAL_1_5;
|
|
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));
|
|
|
|
const auto graph = CallGraph(context.get());
|
|
|
|
const auto& topological_ordering = graph.GetFunctionsInTopologicalOrder();
|
|
|
|
// The possible topological orderings are:
|
|
// - main, B, D, C, A
|
|
// - main, B, C, D, A
|
|
// - main, B, C, A, D
|
|
ASSERT_TRUE(
|
|
topological_ordering == std::vector<uint32_t>({2, 48, 61, 50, 35}) ||
|
|
topological_ordering == std::vector<uint32_t>({2, 48, 50, 61, 35}) ||
|
|
topological_ordering == std::vector<uint32_t>({2, 48, 50, 35, 61}));
|
|
}
|
|
|
|
TEST(CallGraphTest, LoopNestingDepth) {
|
|
const auto env = SPV_ENV_UNIVERSAL_1_5;
|
|
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));
|
|
|
|
const auto graph = CallGraph(context.get());
|
|
|
|
// Check the maximum loop nesting depth for function calls to, in order:
|
|
// main, A, B, C, D
|
|
ASSERT_EQ(graph.GetMaxCallNestingDepth(2), 0);
|
|
ASSERT_EQ(graph.GetMaxCallNestingDepth(35), 4);
|
|
ASSERT_EQ(graph.GetMaxCallNestingDepth(48), 0);
|
|
ASSERT_EQ(graph.GetMaxCallNestingDepth(50), 2);
|
|
ASSERT_EQ(graph.GetMaxCallNestingDepth(61), 1);
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace fuzz
|
|
} // namespace spvtools
|