// Copyright (c) 2016 Google Inc. // // 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 #include #include #include #include "opt/build_module.h" #include "opt/def_use_manager.h" #include "pass_utils.h" #include "spirv-tools/libspirv.hpp" namespace { using ::testing::ElementsAre; using namespace spvtools; using spvtools::opt::analysis::DefUseManager; // Disassembles the given |inst| and returns the disassembly. std::string DisassembleInst(ir::Instruction* inst) { SpirvTools tools(SPV_ENV_UNIVERSAL_1_1); std::vector binary; // We need this to generate the necessary header in the binary. tools.Assemble("", &binary); inst->ToBinaryWithoutAttachedDebugInsts(&binary); std::string text; // We'll need to check the underlying id numbers. // So turn off friendly names for ids. tools.Disassemble(binary, &text, SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); while (!text.empty() && text.back() == '\n') text.pop_back(); return text; } // A struct for holding expected id defs and uses. struct InstDefUse { using IdInstPair = std::pair; using IdInstsPair = std::pair>; // Ids and their corresponding def instructions. std::vector defs; // Ids and their corresponding use instructions. std::vector uses; }; // Checks that the |actual_defs| and |actual_uses| are in accord with // |expected_defs_uses|. void CheckDef(const InstDefUse& expected_defs_uses, const DefUseManager::IdToDefMap& actual_defs) { // Check defs. ASSERT_EQ(expected_defs_uses.defs.size(), actual_defs.size()); for (uint32_t i = 0; i < expected_defs_uses.defs.size(); ++i) { const auto id = expected_defs_uses.defs[i].first; const auto expected_def = expected_defs_uses.defs[i].second; ASSERT_EQ(1u, actual_defs.count(id)) << "expected to def id [" << id << "]"; EXPECT_EQ(expected_def, DisassembleInst(actual_defs.at(id))); } } void CheckUse(const InstDefUse& expected_defs_uses, const DefUseManager::IdToUsesMap& actual_uses) { // Check uses. ASSERT_EQ(expected_defs_uses.uses.size(), actual_uses.size()); for (uint32_t i = 0; i < expected_defs_uses.uses.size(); ++i) { const auto id = expected_defs_uses.uses[i].first; const auto& expected_uses = expected_defs_uses.uses[i].second; ASSERT_EQ(1u, actual_uses.count(id)) << "expected to use id [" << id << "]"; const auto& uses = actual_uses.at(id); ASSERT_EQ(expected_uses.size(), uses.size()) << "id [" << id << "] # uses: expected: " << expected_uses.size() << " actual: " << uses.size(); auto it = uses.cbegin(); for (const auto expected_use : expected_uses) { EXPECT_EQ(expected_use, DisassembleInst(it->inst)) << "id [" << id << "] use instruction mismatch"; ++it; } } } // The following test case mimics how LLVM handles induction variables. // But, yeah, it's not very readable. However, we only care about the id // defs and uses. So, no need to make sure this is valid OpPhi construct. const char kOpPhiTestFunction[] = " %2 = OpFunction %1 None %3 " " %4 = OpLabel " " OpBranch %5 " " %5 = OpLabel " " %7 = OpPhi %6 %8 %4 %9 %5 " "%11 = OpPhi %10 %12 %4 %13 %5 " " %9 = OpIAdd %6 %7 %14 " "%13 = OpFAdd %10 %11 %15 " "%17 = OpSLessThan %16 %7 %18 " " OpLoopMerge %19 %5 None " " OpBranchConditional %17 %5 %19 " "%19 = OpLabel " " OpReturn " " OpFunctionEnd"; struct ParseDefUseCase { const char* text; InstDefUse du; }; using ParseDefUseTest = ::testing::TestWithParam; TEST_P(ParseDefUseTest, Case) { const auto& tc = GetParam(); // Build module. const std::vector text = {tc.text}; std::unique_ptr module = BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, JoinAllInsts(text)); ASSERT_NE(nullptr, module); // Analyze def and use. opt::analysis::DefUseManager manager(nullptr, module.get()); CheckDef(tc.du, manager.id_to_defs()); CheckUse(tc.du, manager.id_to_uses()); } // clang-format off INSTANTIATE_TEST_CASE_P( TestCase, ParseDefUseTest, ::testing::ValuesIn(std::vector{ {"", {{}, {}}}, // no instruction {"OpMemoryModel Logical GLSL450", {{}, {}}}, // no def and use { // single def, no use "%1 = OpString \"wow\"", { {{1, "%1 = OpString \"wow\""}}, // defs {} // uses } }, { // multiple def, no use "%1 = OpString \"hello\" " "%2 = OpString \"world\" " "%3 = OpTypeVoid", { { // defs {1, "%1 = OpString \"hello\""}, {2, "%2 = OpString \"world\""}, {3, "%3 = OpTypeVoid"}, }, {} // uses } }, { // single use, no def "OpTypeForwardPointer %1 Input", { {}, // defs { // uses {1, {"OpTypeForwardPointer %1 Input"}}, } } }, { // multiple use, no def "OpEntryPoint Fragment %1 \"main\" " "OpTypeForwardPointer %2 Input " "OpTypeForwardPointer %3 Output", { {}, // defs { // uses {1, {"OpEntryPoint Fragment %1 \"main\""}}, {2, {"OpTypeForwardPointer %2 Input"}}, {3, {"OpTypeForwardPointer %3 Output"}}, } } }, { // multiple def, multiple use "%1 = OpTypeBool " "%2 = OpTypeVector %1 3 " "%3 = OpTypeMatrix %2 3", { { // defs {1, "%1 = OpTypeBool"}, {2, "%2 = OpTypeVector %1 3"}, {3, "%3 = OpTypeMatrix %2 3"}, }, { // uses {1, {"%2 = OpTypeVector %1 3"}}, {2, {"%3 = OpTypeMatrix %2 3"}}, } } }, { // multiple use of the same id "%1 = OpTypeBool " "%2 = OpTypeVector %1 2 " "%3 = OpTypeVector %1 3 " "%4 = OpTypeVector %1 4", { { // defs {1, "%1 = OpTypeBool"}, {2, "%2 = OpTypeVector %1 2"}, {3, "%3 = OpTypeVector %1 3"}, {4, "%4 = OpTypeVector %1 4"}, }, { // uses {1, { "%2 = OpTypeVector %1 2", "%3 = OpTypeVector %1 3", "%4 = OpTypeVector %1 4", } }, } } }, { // labels "%2 = OpFunction %1 None %3 " "%4 = OpLabel " "OpBranchConditional %5 %6 %7 " "%6 = OpLabel " "OpBranch %7 " "%7 = OpLabel " "OpReturn " "OpFunctionEnd", { { // defs {2, "%2 = OpFunction %1 None %3"}, {4, "%4 = OpLabel"}, {6, "%6 = OpLabel"}, {7, "%7 = OpLabel"}, }, { // uses {1, {"%2 = OpFunction %1 None %3"}}, {3, {"%2 = OpFunction %1 None %3"}}, {5, {"OpBranchConditional %5 %6 %7"}}, {6, {"OpBranchConditional %5 %6 %7"}}, {7, { "OpBranchConditional %5 %6 %7", "OpBranch %7", } }, } } }, { // cross function "%1 = OpTypeBool " "%2 = OpFunction %1 None %3 " "%4 = OpLabel " "%5 = OpVariable %1 Function " "%6 = OpFunctionCall %1 %2 %5 " "OpReturnValue %6 " "OpFunctionEnd", { { // defs {1, "%1 = OpTypeBool"}, {2, "%2 = OpFunction %1 None %3"}, {4, "%4 = OpLabel"}, {5, "%5 = OpVariable %1 Function"}, {6, "%6 = OpFunctionCall %1 %2 %5"}, }, { // uses {1, { "%2 = OpFunction %1 None %3", "%5 = OpVariable %1 Function", "%6 = OpFunctionCall %1 %2 %5", } }, {2, {"%6 = OpFunctionCall %1 %2 %5"}}, {5, {"%6 = OpFunctionCall %1 %2 %5"}}, {3, {"%2 = OpFunction %1 None %3"}}, {6, {"OpReturnValue %6"}}, } } }, { // selection merge and loop merge "%2 = OpFunction %1 None %3 " "%4 = OpLabel " "OpLoopMerge %5 %4 None " "OpBranch %6 " "%5 = OpLabel " "OpReturn " "%6 = OpLabel " "OpSelectionMerge %7 None " "OpBranchConditional %8 %9 %7 " "%7 = OpLabel " "OpReturn " "%9 = OpLabel " "OpReturn " "OpFunctionEnd", { { // defs {2, "%2 = OpFunction %1 None %3"}, {4, "%4 = OpLabel"}, {5, "%5 = OpLabel"}, {6, "%6 = OpLabel"}, {7, "%7 = OpLabel"}, {9, "%9 = OpLabel"}, }, { // uses {1, {"%2 = OpFunction %1 None %3"}}, {3, {"%2 = OpFunction %1 None %3"}}, {4, {"OpLoopMerge %5 %4 None"}}, {5, {"OpLoopMerge %5 %4 None"}}, {6, {"OpBranch %6"}}, {7, { "OpSelectionMerge %7 None", "OpBranchConditional %8 %9 %7", } }, {8, {"OpBranchConditional %8 %9 %7"}}, {9, {"OpBranchConditional %8 %9 %7"}}, } } }, { // Forward reference "OpDecorate %1 Block " "OpTypeForwardPointer %2 Input " "%3 = OpTypeInt 32 0 " "%1 = OpTypeStruct %3 " "%2 = OpTypePointer Input %3", { { // defs {1, "%1 = OpTypeStruct %3"}, {2, "%2 = OpTypePointer Input %3"}, {3, "%3 = OpTypeInt 32 0"}, }, { // uses {1, {"OpDecorate %1 Block"}}, {2, {"OpTypeForwardPointer %2 Input"}}, {3, { "%1 = OpTypeStruct %3", "%2 = OpTypePointer Input %3", } } }, }, }, { // OpPhi kOpPhiTestFunction, { { // defs {2, "%2 = OpFunction %1 None %3"}, {4, "%4 = OpLabel"}, {5, "%5 = OpLabel"}, {7, "%7 = OpPhi %6 %8 %4 %9 %5"}, {9, "%9 = OpIAdd %6 %7 %14"}, {11, "%11 = OpPhi %10 %12 %4 %13 %5"}, {13, "%13 = OpFAdd %10 %11 %15"}, {17, "%17 = OpSLessThan %16 %7 %18"}, {19, "%19 = OpLabel"}, }, { // uses {1, {"%2 = OpFunction %1 None %3"}}, {3, {"%2 = OpFunction %1 None %3"}}, {4, { "%7 = OpPhi %6 %8 %4 %9 %5", "%11 = OpPhi %10 %12 %4 %13 %5", } }, {5, { "OpBranch %5", "%7 = OpPhi %6 %8 %4 %9 %5", "%11 = OpPhi %10 %12 %4 %13 %5", "OpLoopMerge %19 %5 None", "OpBranchConditional %17 %5 %19", } }, {6, { "%7 = OpPhi %6 %8 %4 %9 %5", "%9 = OpIAdd %6 %7 %14", } }, {7, { "%9 = OpIAdd %6 %7 %14", "%17 = OpSLessThan %16 %7 %18", } }, {8, {"%7 = OpPhi %6 %8 %4 %9 %5"}}, {9, {"%7 = OpPhi %6 %8 %4 %9 %5"}}, {10, { "%11 = OpPhi %10 %12 %4 %13 %5", "%13 = OpFAdd %10 %11 %15", } }, {11, {"%13 = OpFAdd %10 %11 %15"}}, {12, {"%11 = OpPhi %10 %12 %4 %13 %5"}}, {13, {"%11 = OpPhi %10 %12 %4 %13 %5"}}, {14, {"%9 = OpIAdd %6 %7 %14"}}, {15, {"%13 = OpFAdd %10 %11 %15"}}, {16, {"%17 = OpSLessThan %16 %7 %18"}}, {17, {"OpBranchConditional %17 %5 %19"}}, {18, {"%17 = OpSLessThan %16 %7 %18"}}, {19, { "OpLoopMerge %19 %5 None", "OpBranchConditional %17 %5 %19", } }, }, }, }, { // OpPhi defining and referencing the same id. "%1 = OpTypeBool " "%2 = OpConstantTrue %1 " "%4 = OpFunction %3 None %5 " "%6 = OpLabel " " OpBranch %7 " "%7 = OpLabel " "%8 = OpPhi %1 %8 %7 %2 %6 " // both defines and uses %8 " OpBranch %7 " " OpFunctionEnd", { { // defs {1, "%1 = OpTypeBool"}, {2, "%2 = OpConstantTrue %1"}, {4, "%4 = OpFunction %3 None %5"}, {6, "%6 = OpLabel"}, {7, "%7 = OpLabel"}, {8, "%8 = OpPhi %1 %8 %7 %2 %6"}, }, { // uses {1, { "%2 = OpConstantTrue %1", "%8 = OpPhi %1 %8 %7 %2 %6", } }, {2, {"%8 = OpPhi %1 %8 %7 %2 %6"}}, {3, {"%4 = OpFunction %3 None %5"}}, {5, {"%4 = OpFunction %3 None %5"}}, {6, {"%8 = OpPhi %1 %8 %7 %2 %6"}}, {7, { "OpBranch %7", "%8 = OpPhi %1 %8 %7 %2 %6", "OpBranch %7", } }, {8, {"%8 = OpPhi %1 %8 %7 %2 %6"}}, }, }, }, }) ); // clang-format on struct ReplaceUseCase { const char* before; std::vector> candidates; const char* after; InstDefUse du; }; using ReplaceUseTest = ::testing::TestWithParam; // Disassembles the given |module| and returns the disassembly. std::string DisassembleModule(ir::Module* module) { SpirvTools tools(SPV_ENV_UNIVERSAL_1_1); std::vector binary; module->ToBinary(&binary, /* skip_nop = */ false); std::string text; // We'll need to check the underlying id numbers. // So turn off friendly names for ids. tools.Disassemble(binary, &text, SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); while (!text.empty() && text.back() == '\n') text.pop_back(); return text; } TEST_P(ReplaceUseTest, Case) { const auto& tc = GetParam(); // Build module. const std::vector text = {tc.before}; std::unique_ptr module = BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, JoinAllInsts(text)); ASSERT_NE(nullptr, module); // Analyze def and use. opt::analysis::DefUseManager manager(nullptr, module.get()); // Do the substitution. for (const auto& candiate : tc.candidates) { manager.ReplaceAllUsesWith(candiate.first, candiate.second); } EXPECT_EQ(tc.after, DisassembleModule(module.get())); CheckDef(tc.du, manager.id_to_defs()); CheckUse(tc.du, manager.id_to_uses()); } // clang-format off INSTANTIATE_TEST_CASE_P( TestCase, ReplaceUseTest, ::testing::ValuesIn(std::vector{ { // no use, no replace request "", {}, "", {}, }, { // no use, some replace requests "OpMemoryModel Logical GLSL450", {{1, 2}, {3, 4}, {7, 8}, {7, 9}, {7, 10}, {2, 10}, {3, 10}}, "OpMemoryModel Logical GLSL450", {}, }, { // replace one use "%1 = OpTypeBool " "%2 = OpTypeVector %1 3", {{1, 3}}, "%1 = OpTypeBool\n" "%2 = OpTypeVector %3 3", { { // defs {1, "%1 = OpTypeBool"}, {2, "%2 = OpTypeVector %3 3"}, }, { // uses {3, {"%2 = OpTypeVector %3 3"}}, }, }, }, { // replace and then replace back "%1 = OpTypeBool " "%2 = OpTypeVector %1 3", {{1, 3}, {3, 1}}, "%1 = OpTypeBool\n" "%2 = OpTypeVector %1 3", { { // defs {1, "%1 = OpTypeBool"}, {2, "%2 = OpTypeVector %1 3"}, }, { // uses {1, {"%2 = OpTypeVector %1 3"}}, }, }, }, { // replace with the same id "%1 = OpTypeBool " "%2 = OpTypeVector %1 3", {{1, 1}, {2, 2}, {3, 3}}, "%1 = OpTypeBool\n" "%2 = OpTypeVector %1 3", { { // defs {1, "%1 = OpTypeBool"}, {2, "%2 = OpTypeVector %1 3"}, }, { // uses {1, {"%2 = OpTypeVector %1 3"}}, }, }, }, { // replace in sequence "%1 = OpTypeBool " "%2 = OpTypeVector %1 3", {{1, 3}, {3, 4}, {4, 5}, {5, 100}}, "%1 = OpTypeBool\n" "%2 = OpTypeVector %100 3", { { // defs {1, "%1 = OpTypeBool"}, {2, "%2 = OpTypeVector %100 3"}, }, { // uses {100, {"%2 = OpTypeVector %100 3"}}, }, }, }, { // replace multiple uses "%1 = OpTypeBool " "%2 = OpTypeVector %1 2 " "%3 = OpTypeVector %1 3 " "%4 = OpTypeVector %1 4 " "%5 = OpTypeMatrix %2 2 " "%6 = OpTypeMatrix %3 3 " "%7 = OpTypeMatrix %4 4", {{1, 10}, {2, 20}, {4, 40}}, "%1 = OpTypeBool\n" "%2 = OpTypeVector %10 2\n" "%3 = OpTypeVector %10 3\n" "%4 = OpTypeVector %10 4\n" "%5 = OpTypeMatrix %20 2\n" "%6 = OpTypeMatrix %3 3\n" "%7 = OpTypeMatrix %40 4", { { // defs {1, "%1 = OpTypeBool"}, {2, "%2 = OpTypeVector %10 2"}, {3, "%3 = OpTypeVector %10 3"}, {4, "%4 = OpTypeVector %10 4"}, {5, "%5 = OpTypeMatrix %20 2"}, {6, "%6 = OpTypeMatrix %3 3"}, {7, "%7 = OpTypeMatrix %40 4"}, }, { // uses {10, { "%2 = OpTypeVector %10 2", "%3 = OpTypeVector %10 3", "%4 = OpTypeVector %10 4", } }, {20, {"%5 = OpTypeMatrix %20 2"}}, {3, {"%6 = OpTypeMatrix %3 3"}}, {40, {"%7 = OpTypeMatrix %40 4"}}, }, }, }, { // OpPhi. kOpPhiTestFunction, // replace one id used by OpPhi, replace one id generated by OpPhi {{9, 9000}, {11, 9}}, "%2 = OpFunction %1 None %3\n" "%4 = OpLabel\n" "OpBranch %5\n" "%5 = OpLabel\n" "%7 = OpPhi %6 %8 %4 %9000 %5\n" // %9 -> %9000 "%11 = OpPhi %10 %12 %4 %13 %5\n" "%9 = OpIAdd %6 %7 %14\n" "%13 = OpFAdd %10 %9 %15\n" // %11 -> %9 "%17 = OpSLessThan %16 %7 %18\n" "OpLoopMerge %19 %5 None\n" "OpBranchConditional %17 %5 %19\n" "%19 = OpLabel\n" "OpReturn\n" "OpFunctionEnd", { { // defs. {2, "%2 = OpFunction %1 None %3"}, {4, "%4 = OpLabel"}, {5, "%5 = OpLabel"}, {7, "%7 = OpPhi %6 %8 %4 %9000 %5"}, {9, "%9 = OpIAdd %6 %7 %14"}, {11, "%11 = OpPhi %10 %12 %4 %13 %5"}, {13, "%13 = OpFAdd %10 %9 %15"}, {17, "%17 = OpSLessThan %16 %7 %18"}, {19, "%19 = OpLabel"}, }, { // uses {1, {"%2 = OpFunction %1 None %3"}}, {3, {"%2 = OpFunction %1 None %3"}}, {4, { "%7 = OpPhi %6 %8 %4 %9000 %5", "%11 = OpPhi %10 %12 %4 %13 %5", } }, {5, { "OpBranch %5", "%7 = OpPhi %6 %8 %4 %9000 %5", "%11 = OpPhi %10 %12 %4 %13 %5", "OpLoopMerge %19 %5 None", "OpBranchConditional %17 %5 %19", } }, {6, { "%7 = OpPhi %6 %8 %4 %9000 %5", "%9 = OpIAdd %6 %7 %14", } }, {7, { "%9 = OpIAdd %6 %7 %14", "%17 = OpSLessThan %16 %7 %18", } }, {8, {"%7 = OpPhi %6 %8 %4 %9000 %5"}}, {9, {"%13 = OpFAdd %10 %9 %15"}}, // uses of %9 changed from %7 to %13 {10, { "%11 = OpPhi %10 %12 %4 %13 %5", "%13 = OpFAdd %10 %9 %15", } }, // no more uses of %11 {12, {"%11 = OpPhi %10 %12 %4 %13 %5"}}, {13, {"%11 = OpPhi %10 %12 %4 %13 %5"}}, {14, {"%9 = OpIAdd %6 %7 %14"}}, {15, {"%13 = OpFAdd %10 %9 %15"}}, {16, {"%17 = OpSLessThan %16 %7 %18"}}, {17, {"OpBranchConditional %17 %5 %19"}}, {18, {"%17 = OpSLessThan %16 %7 %18"}}, {19, { "OpLoopMerge %19 %5 None", "OpBranchConditional %17 %5 %19", } }, // new uses of %9000 {9000, {"%7 = OpPhi %6 %8 %4 %9000 %5"}}, }, }, }, { // OpPhi defining and referencing the same id. "%1 = OpTypeBool " "%2 = OpConstantTrue %1 " "%4 = OpFunction %3 None %5 " "%6 = OpLabel " " OpBranch %7 " "%7 = OpLabel " "%8 = OpPhi %1 %8 %7 %2 %6 " // both defines and uses %8 " OpBranch %7 " " OpFunctionEnd", {{8, 2}}, "%1 = OpTypeBool\n" "%2 = OpConstantTrue %1\n" "%4 = OpFunction %3 None %5\n" "%6 = OpLabel\n" "OpBranch %7\n" "%7 = OpLabel\n" "%8 = OpPhi %1 %2 %7 %2 %6\n" // use of %8 changed to %2 "OpBranch %7\n" "OpFunctionEnd", { { // defs {1, "%1 = OpTypeBool"}, {2, "%2 = OpConstantTrue %1"}, {4, "%4 = OpFunction %3 None %5"}, {6, "%6 = OpLabel"}, {7, "%7 = OpLabel"}, {8, "%8 = OpPhi %1 %2 %7 %2 %6"}, }, { // uses {1, { "%2 = OpConstantTrue %1", "%8 = OpPhi %1 %2 %7 %2 %6", } }, {2, { // TODO(antiagainst): address this. // We have duplication here because we didn't check existence // before inserting uses. "%8 = OpPhi %1 %2 %7 %2 %6", "%8 = OpPhi %1 %2 %7 %2 %6", } }, {3, {"%4 = OpFunction %3 None %5"}}, {5, {"%4 = OpFunction %3 None %5"}}, {6, {"%8 = OpPhi %1 %2 %7 %2 %6"}}, {7, { "OpBranch %7", "%8 = OpPhi %1 %2 %7 %2 %6", "OpBranch %7", } }, // {8, {"%8 = OpPhi %1 %8 %7 %2 %6"}}, }, }, }, }) ); // clang-format on struct KillDefCase { const char* before; std::vector ids_to_kill; const char* after; InstDefUse du; }; using KillDefTest = ::testing::TestWithParam; TEST_P(KillDefTest, Case) { const auto& tc = GetParam(); // Build module. const std::vector text = {tc.before}; std::unique_ptr module = BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, JoinAllInsts(text)); ASSERT_NE(nullptr, module); // Analyze def and use. opt::analysis::DefUseManager manager(nullptr, module.get()); // Do the substitution. for (const auto id : tc.ids_to_kill) manager.KillDef(id); EXPECT_EQ(tc.after, DisassembleModule(module.get())); CheckDef(tc.du, manager.id_to_defs()); CheckUse(tc.du, manager.id_to_uses()); } // clang-format off INSTANTIATE_TEST_CASE_P( TestCase, KillDefTest, ::testing::ValuesIn(std::vector{ { // no def, no use, no kill "", {}, "", {} }, { // kill nothing "%1 = OpTypeBool " "%2 = OpTypeVector %1 2 " "%3 = OpTypeVector %1 3 ", {}, "%1 = OpTypeBool\n" "%2 = OpTypeVector %1 2\n" "%3 = OpTypeVector %1 3", { { // defs {1, "%1 = OpTypeBool"}, {2, "%2 = OpTypeVector %1 2"}, {3, "%3 = OpTypeVector %1 3"}, }, { // uses {1, { "%2 = OpTypeVector %1 2", "%3 = OpTypeVector %1 3", } }, }, }, }, { // kill id used, kill id not used, kill id not defined "%1 = OpTypeBool " "%2 = OpTypeVector %1 2 " "%3 = OpTypeVector %1 3 " "%4 = OpTypeVector %1 4 " "%5 = OpTypeMatrix %3 3 " "%6 = OpTypeMatrix %2 3", {1, 3, 5, 10}, // ids to kill "OpNop\n" "%2 = OpTypeVector %1 2\n" "OpNop\n" "%4 = OpTypeVector %1 4\n" "OpNop\n" "%6 = OpTypeMatrix %2 3", { { // defs {2, "%2 = OpTypeVector %1 2"}, {4, "%4 = OpTypeVector %1 4"}, {6, "%6 = OpTypeMatrix %2 3"}, }, { // uses. %1 and %3 are both killed, so no uses // recorded for them anymore. {2, {"%6 = OpTypeMatrix %2 3"}}, } }, }, { // OpPhi. kOpPhiTestFunction, {9, 11}, // kill one id used by OpPhi, kill one id generated by OpPhi "%2 = OpFunction %1 None %3\n" "%4 = OpLabel\n" "OpBranch %5\n" "%5 = OpLabel\n" "%7 = OpPhi %6 %8 %4 %9 %5\n" "OpNop\n" "OpNop\n" "%13 = OpFAdd %10 %11 %15\n" "%17 = OpSLessThan %16 %7 %18\n" "OpLoopMerge %19 %5 None\n" "OpBranchConditional %17 %5 %19\n" "%19 = OpLabel\n" "OpReturn\n" "OpFunctionEnd", { { // defs. %9 & %11 are killed. {2, "%2 = OpFunction %1 None %3"}, {4, "%4 = OpLabel"}, {5, "%5 = OpLabel"}, {7, "%7 = OpPhi %6 %8 %4 %9 %5"}, {13, "%13 = OpFAdd %10 %11 %15"}, {17, "%17 = OpSLessThan %16 %7 %18"}, {19, "%19 = OpLabel"}, }, { // uses {1, {"%2 = OpFunction %1 None %3"}}, {3, {"%2 = OpFunction %1 None %3"}}, {4, { "%7 = OpPhi %6 %8 %4 %9 %5", // "%11 = OpPhi %10 %12 %4 %13 %5", } }, {5, { "OpBranch %5", "%7 = OpPhi %6 %8 %4 %9 %5", // "%11 = OpPhi %10 %12 %4 %13 %5", "OpLoopMerge %19 %5 None", "OpBranchConditional %17 %5 %19", } }, {6, { "%7 = OpPhi %6 %8 %4 %9 %5", // "%9 = OpIAdd %6 %7 %14", } }, {7, { // "%9 = OpIAdd %6 %7 %14", "%17 = OpSLessThan %16 %7 %18", } }, {8, {"%7 = OpPhi %6 %8 %4 %9 %5"}}, // {9, {"%7 = OpPhi %6 %8 %4 %9 %5"}}, {10, { // "%11 = OpPhi %10 %12 %4 %13 %5", "%13 = OpFAdd %10 %11 %15", } }, // {11, {"%13 = OpFAdd %10 %11 %15"}}, // {12, {"%11 = OpPhi %10 %12 %4 %13 %5"}}, // {13, {"%11 = OpPhi %10 %12 %4 %13 %5"}}, // {14, {"%9 = OpIAdd %6 %7 %14"}}, {15, {"%13 = OpFAdd %10 %11 %15"}}, {16, {"%17 = OpSLessThan %16 %7 %18"}}, {17, {"OpBranchConditional %17 %5 %19"}}, {18, {"%17 = OpSLessThan %16 %7 %18"}}, {19, { "OpLoopMerge %19 %5 None", "OpBranchConditional %17 %5 %19", } }, }, }, }, { // OpPhi defining and referencing the same id. "%1 = OpTypeBool " "%2 = OpConstantTrue %1 " "%4 = OpFunction %3 None %5 " "%6 = OpLabel " " OpBranch %7 " "%7 = OpLabel " "%8 = OpPhi %1 %8 %7 %2 %6 " // both defines and uses %8 " OpBranch %7 " " OpFunctionEnd", {8}, "%1 = OpTypeBool\n" "%2 = OpConstantTrue %1\n" "%4 = OpFunction %3 None %5\n" "%6 = OpLabel\n" "OpBranch %7\n" "%7 = OpLabel\n" "OpNop\n" "OpBranch %7\n" "OpFunctionEnd", { { // defs {1, "%1 = OpTypeBool"}, {2, "%2 = OpConstantTrue %1"}, {4, "%4 = OpFunction %3 None %5"}, {6, "%6 = OpLabel"}, {7, "%7 = OpLabel"}, // {8, "%8 = OpPhi %1 %8 %7 %2 %6"}, }, { // uses {1, { "%2 = OpConstantTrue %1", // "%8 = OpPhi %1 %8 %7 %2 %6", } }, // {2, {"%8 = OpPhi %1 %8 %7 %2 %6"}}, {3, {"%4 = OpFunction %3 None %5"}}, {5, {"%4 = OpFunction %3 None %5"}}, // {6, {"%8 = OpPhi %1 %8 %7 %2 %6"}}, {7, { "OpBranch %7", // "%8 = OpPhi %1 %8 %7 %2 %6", "OpBranch %7", } }, // {8, {"%8 = OpPhi %1 %8 %7 %2 %6"}}, }, }, }, }) ); // clang-format on // TEST(DefUseTest, OpSwitch) { // Because disassembler has basic type check for OpSwitch's selector, we // cannot use the DisassembleInst() in the above. Thus, this special spotcheck // test case. const char original_text[] = // int64 f(int64 v) { // switch (v) { // case 1: break; // case -4294967296: break; // case 9223372036854775807: break; // default: break; // } // return v; // } " %1 = OpTypeInt 64 1 " " %2 = OpFunction %1 None %3 " // %3 is int64(int64)* " %4 = OpFunctionParameter %1 " " %5 = OpLabel " " %6 = OpLoad %1 %4 " // selector value " OpSelectionMerge %7 None " " OpSwitch %6 %8 " " 1 %9 " // 1 " -4294967296 %10 " // -2^32 " 9223372036854775807 %11 " // 2^63-1 " %8 = OpLabel " // default " OpBranch %7 " " %9 = OpLabel " " OpBranch %7 " "%10 = OpLabel " " OpBranch %7 " "%11 = OpLabel " " OpBranch %7 " " %7 = OpLabel " " OpReturnValue %6 " " OpFunctionEnd"; std::unique_ptr module = BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, original_text); ASSERT_NE(nullptr, module); // Analyze def and use. opt::analysis::DefUseManager manager(nullptr, module.get()); // Do a bunch replacements. manager.ReplaceAllUsesWith(9, 900); // to unused id manager.ReplaceAllUsesWith(10, 1000); // to unused id manager.ReplaceAllUsesWith(11, 7); // to existing id // clang-format off const char modified_text[] = "%1 = OpTypeInt 64 1\n" "%2 = OpFunction %1 None %3\n" // %3 is int64(int64)* "%4 = OpFunctionParameter %1\n" "%5 = OpLabel\n" "%6 = OpLoad %1 %4\n" // selector value "OpSelectionMerge %7 None\n" "OpSwitch %6 %8 1 %900 -4294967296 %1000 9223372036854775807 %7\n" // changed! "%8 = OpLabel\n" // default "OpBranch %7\n" "%9 = OpLabel\n" "OpBranch %7\n" "%10 = OpLabel\n" "OpBranch %7\n" "%11 = OpLabel\n" "OpBranch %7\n" "%7 = OpLabel\n" "OpReturnValue %6\n" "OpFunctionEnd"; // clang-format on EXPECT_EQ(modified_text, DisassembleModule(module.get())); InstDefUse def_uses = {}; def_uses.defs = { {1, "%1 = OpTypeInt 64 1"}, {2, "%2 = OpFunction %1 None %3"}, {4, "%4 = OpFunctionParameter %1"}, {5, "%5 = OpLabel"}, {6, "%6 = OpLoad %1 %4"}, {7, "%7 = OpLabel"}, {8, "%8 = OpLabel"}, {9, "%9 = OpLabel"}, {10, "%10 = OpLabel"}, {11, "%11 = OpLabel"}, }; CheckDef(def_uses, manager.id_to_defs()); { auto* use_list = manager.GetUses(6); ASSERT_NE(nullptr, use_list); EXPECT_EQ(2u, use_list->size()); EXPECT_EQ(SpvOpSwitch, use_list->front().inst->opcode()); EXPECT_EQ(SpvOpReturnValue, use_list->back().inst->opcode()); } { auto* use_list = manager.GetUses(7); ASSERT_NE(nullptr, use_list); EXPECT_EQ(6u, use_list->size()); std::vector opcodes; for (const auto& use : *use_list) { opcodes.push_back(use.inst->opcode()); } // OpSwitch is now a user of %7. EXPECT_THAT(opcodes, ElementsAre(SpvOpSelectionMerge, SpvOpBranch, SpvOpBranch, SpvOpBranch, SpvOpBranch, SpvOpSwitch)); } // Check all ids only used by OpSwitch after replacement. for (const auto id : {8, 900, 1000}) { auto* use_list = manager.GetUses(id); ASSERT_NE(nullptr, use_list); EXPECT_EQ(1u, use_list->size()); EXPECT_EQ(SpvOpSwitch, use_list->front().inst->opcode()); } } // Creates an |result_id| = OpTypeInt 32 1 instruction. ir::Instruction Int32TypeInstruction(uint32_t result_id) { return ir::Instruction(SpvOp::SpvOpTypeInt, 0, result_id, {ir::Operand(SPV_OPERAND_TYPE_LITERAL_INTEGER, {32}), ir::Operand(SPV_OPERAND_TYPE_LITERAL_INTEGER, {1})}); } // Creates an |result_id| = OpConstantTrue/Flase |type_id| instruction. ir::Instruction ConstantBoolInstruction(bool value, uint32_t type_id, uint32_t result_id) { return ir::Instruction( value ? SpvOp::SpvOpConstantTrue : SpvOp::SpvOpConstantFalse, type_id, result_id, {}); } // Creates an |result_id| = OpLabel instruction. ir::Instruction LabelInstruction(uint32_t result_id) { return ir::Instruction(SpvOp::SpvOpLabel, 0, result_id, {}); } // Creates an OpBranch |target_id| instruction. ir::Instruction BranchInstruction(uint32_t target_id) { return ir::Instruction(SpvOp::SpvOpBranch, 0, 0, { ir::Operand(SPV_OPERAND_TYPE_ID, {target_id}), }); } // Test case for analyzing individual instructions. struct AnalyzeInstDefUseTestCase { std::vector insts; // instrutions to be analyzed in order. const char* module_text; InstDefUse expected_define_use; }; using AnalyzeInstDefUseTest = ::testing::TestWithParam; // Test the analyzing result for individual instructions. TEST_P(AnalyzeInstDefUseTest, Case) { auto tc = GetParam(); // Build module. std::unique_ptr module = BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, tc.module_text); ASSERT_NE(nullptr, module); // Analyze the instructions. opt::analysis::DefUseManager manager(nullptr, module.get()); for (ir::Instruction& inst : tc.insts) { manager.AnalyzeInstDefUse(&inst); } CheckDef(tc.expected_define_use, manager.id_to_defs()); CheckUse(tc.expected_define_use, manager.id_to_uses()); } // clang-format off INSTANTIATE_TEST_CASE_P( TestCase, AnalyzeInstDefUseTest, ::testing::ValuesIn(std::vector{ { // A type declaring instruction. {Int32TypeInstruction(1)}, "", { // defs {{1, "%1 = OpTypeInt 32 1"}}, {}, // no uses }, }, { // A type declaring instruction and a constant value. { Int32TypeInstruction(1), ConstantBoolInstruction(true, 1, 2), }, "", { { // defs {1, "%1 = OpTypeInt 32 1"}, {2, "%2 = OpConstantTrue %1"}, // It is fine the SPIR-V code here is invalid. }, { // uses {1, {"%2 = OpConstantTrue %1"}}, }, }, }, { // Analyze two instrutions that have same result id. The def use info // of the result id from the first instruction should be overwritten by // the second instruction. { ConstantBoolInstruction(true, 1, 2), // The def-use info of the following instruction should overwrite the // records of the above one. ConstantBoolInstruction(false, 3, 2), }, "", { // defs {{2, "%2 = OpConstantFalse %3"}}, // uses {{3, {"%2 = OpConstantFalse %3"}}} } }, { // Analyze forward reference instruction, also instruction that does // not have result id. { BranchInstruction(2), LabelInstruction(2), }, "", { // defs {{2, "%2 = OpLabel"}}, // uses {{2, {"OpBranch %2"}}}, } }, { // Analyzing an additional instruction with new result id to an // existing module. { ConstantBoolInstruction(true, 1, 2), }, "%1 = OpTypeInt 32 1 ", { { // defs {1, "%1 = OpTypeInt 32 1"}, {2, "%2 = OpConstantTrue %1"}, }, { // uses {1, {"%2 = OpConstantTrue %1"}}, }, } }, { // Analyzing an additional instruction with existing result id to an // existing module. { ConstantBoolInstruction(true, 1, 2), }, "%1 = OpTypeInt 32 1 " "%2 = OpTypeBool ", { { // defs {1, "%1 = OpTypeInt 32 1"}, {2, "%2 = OpConstantTrue %1"}, }, { // uses {1, {"%2 = OpConstantTrue %1"}}, }, } }, })); // clang-format on struct KillInstTestCase { const char* before; std::unordered_set indices_for_inst_to_kill; const char* after; InstDefUse expected_define_use; }; using KillInstTest = ::testing::TestWithParam; TEST_P(KillInstTest, Case) { auto tc = GetParam(); // Build module. std::unique_ptr module = BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, tc.before); ASSERT_NE(nullptr, module); // KillInst uint32_t index = 0; opt::analysis::DefUseManager manager(nullptr, module.get()); module->ForEachInst([&index, &tc, &manager](ir::Instruction* inst) { if (tc.indices_for_inst_to_kill.count(index) != 0) { manager.KillInst(inst); } index++; }); EXPECT_EQ(tc.after, DisassembleModule(module.get())); CheckDef(tc.expected_define_use, manager.id_to_defs()); CheckUse(tc.expected_define_use, manager.id_to_uses()); } // clang-format off INSTANTIATE_TEST_CASE_P( TestCase, KillInstTest, ::testing::ValuesIn(std::vector{ // Kill id defining instructions. { "%2 = OpFunction %1 None %3 " "%4 = OpLabel " " OpBranch %5 " "%5 = OpLabel " " OpBranch %6 " "%6 = OpLabel " " OpBranch %4 " "%7 = OpLabel " " OpReturn " " OpFunctionEnd", {0, 3, 5, 7}, "OpNop\n" "%4 = OpLabel\n" "OpBranch %5\n" "OpNop\n" "OpBranch %6\n" "OpNop\n" "OpBranch %4\n" "OpNop\n" "OpReturn\n" "OpFunctionEnd", { // defs {{4, "%4 = OpLabel"}}, // uses {{4, {"OpBranch %4"}}} } }, // Kill instructions that do not have result ids. { "%2 = OpFunction %1 None %3 " "%4 = OpLabel " " OpBranch %5 " "%5 = OpLabel " " OpBranch %6 " "%6 = OpLabel " " OpBranch %4 " "%7 = OpLabel " " OpReturn " " OpFunctionEnd", {2, 4}, "%2 = OpFunction %1 None %3\n" "%4 = OpLabel\n" "OpNop\n" "%5 = OpLabel\n" "OpNop\n" "%6 = OpLabel\n" "OpBranch %4\n" "%7 = OpLabel\n" "OpReturn\n" "OpFunctionEnd", { // defs { {2, "%2 = OpFunction %1 None %3"}, {4, "%4 = OpLabel"}, {5, "%5 = OpLabel"}, {6, "%6 = OpLabel"}, {7, "%7 = OpLabel"}, }, // uses { {1, {"%2 = OpFunction %1 None %3"}}, {3, {"%2 = OpFunction %1 None %3"}}, {4, {"OpBranch %4"}}, } } }, })); // clang-format on struct GetAnnotationsTestCase { const char* code; uint32_t id; std::vector annotations; }; using GetAnnotationsTest = ::testing::TestWithParam; TEST_P(GetAnnotationsTest, Case) { const GetAnnotationsTestCase& tc = GetParam(); // Build module. std::unique_ptr module = BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, tc.code); ASSERT_NE(nullptr, module); // Get annotations opt::analysis::DefUseManager manager(nullptr, module.get()); auto insts = manager.GetAnnotations(tc.id); // Check ASSERT_EQ(tc.annotations.size(), insts.size()) << "wrong number of annotation instructions"; auto inst_iter = insts.begin(); for (const std::string& expected_anno_inst : tc.annotations) { EXPECT_EQ(expected_anno_inst, DisassembleInst(*inst_iter)) << "annotation instruction mismatch"; inst_iter++; } } // clang-format off INSTANTIATE_TEST_CASE_P( TestCase, GetAnnotationsTest, ::testing::ValuesIn(std::vector{ // empty {"", 0, {}}, // basic { // code "OpDecorate %1 Block " "OpDecorate %1 RelaxedPrecision " "%3 = OpTypeInt 32 0 " "%1 = OpTypeStruct %3", // id 1, // annotations { "OpDecorate %1 Block", "OpDecorate %1 RelaxedPrecision", }, }, // with debug instructions { // code "OpName %1 \"struct_type\" " "OpName %3 \"int_type\" " "OpDecorate %1 Block " "OpDecorate %1 RelaxedPrecision " "%3 = OpTypeInt 32 0 " "%1 = OpTypeStruct %3", // id 1, // annotations { "OpDecorate %1 Block", "OpDecorate %1 RelaxedPrecision", }, }, // no annotations { // code "OpName %1 \"struct_type\" " "OpName %3 \"int_type\" " "OpDecorate %1 Block " "OpDecorate %1 RelaxedPrecision " "%3 = OpTypeInt 32 0 " "%1 = OpTypeStruct %3", // id 3, // annotations {}, }, // decoration group { // code "OpDecorate %1 Block " "OpDecorate %1 RelaxedPrecision " "%1 = OpDecorationGroup " "OpGroupDecorate %1 %2 %3 " "%4 = OpTypeInt 32 0 " "%2 = OpTypeStruct %4 " "%3 = OpTypeStruct %4 %4", // id 3, // annotations { "OpGroupDecorate %1 %2 %3", }, }, // memeber decorate { // code "OpMemberDecorate %1 0 RelaxedPrecision " "%2 = OpTypeInt 32 0 " "%1 = OpTypeStruct %2 %2", // id 1, // annotations { "OpMemberDecorate %1 0 RelaxedPrecision", }, }, })); // clang-format on } // anonymous namespace