// Copyright (c) 2016 Google Inc. // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and/or associated documentation files (the // "Materials"), to deal in the Materials without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Materials, and to // permit persons to whom the Materials are furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Materials. // // MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS // KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS // SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT // https://www.khronos.org/registry/ // // THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE // MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. #include #include #include #include "opt/def_use_manager.h" #include "opt/libspirv.hpp" #include "pass_utils.h" 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) { SpvTools tools(SPV_ENV_UNIVERSAL_1_1); std::vector binary; // We need this to generate the necessary header in the binary. tools.Assemble("", &binary); inst->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; } // 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 = SpvTools(SPV_ENV_UNIVERSAL_1_1).BuildModule(JoinAllInsts(text)); ASSERT_NE(nullptr, module); // Analyze def and use. opt::analysis::DefUseManager manager; manager.AnalyzeDefUse(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) { SpvTools 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 = SpvTools(SPV_ENV_UNIVERSAL_1_1).BuildModule(JoinAllInsts(text)); ASSERT_NE(nullptr, module); // Analyze def and use. opt::analysis::DefUseManager manager; manager.AnalyzeDefUse(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 = SpvTools(SPV_ENV_UNIVERSAL_1_1).BuildModule(JoinAllInsts(text)); ASSERT_NE(nullptr, module); // Analyze def and use. opt::analysis::DefUseManager manager; manager.AnalyzeDefUse(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 = SpvTools(SPV_ENV_UNIVERSAL_1_1).BuildModule(original_text); ASSERT_NE(nullptr, module); // Analyze def and use. opt::analysis::DefUseManager manager; manager.AnalyzeDefUse(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()); } } } // anonymous namespace