SPIRV-Tools/test/opt/def_use_test.cpp
Steven Perron f32d11f74b Add the IRContext (part 2): Add def-use manager
This change will move the instances of the def-use manager to the
IRContext.  This allows it to persists across optimization, and does
not have to be rebuilt multiple times.

Added test to ensure that the IRContext is validating and invalidating
the analyses correctly.
2017-11-08 13:35:34 -05:00

1530 lines
45 KiB
C++

// 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 <memory>
#include <unordered_set>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "opt/build_module.h"
#include "opt/def_use_manager.h"
#include "opt/ir_context.h"
#include "pass_utils.h"
#include "spirv-tools/libspirv.hpp"
namespace {
using ::testing::UnorderedElementsAre;
using ::testing::UnorderedElementsAreArray;
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<uint32_t> 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<uint32_t, std::string>;
using IdInstsPair = std::pair<uint32_t, std::vector<std::string>>;
// Ids and their corresponding def instructions.
std::vector<IdInstPair> defs;
// Ids and their corresponding use instructions.
std::vector<IdInstsPair> 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();
std::vector<std::string> actual_uses_disassembled;
for (const auto actual_use : uses) {
actual_uses_disassembled.emplace_back(DisassembleInst(actual_use.inst));
}
EXPECT_THAT(actual_uses_disassembled,
UnorderedElementsAreArray(expected_uses));
}
}
// 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<ParseDefUseCase>;
TEST_P(ParseDefUseTest, Case) {
const auto& tc = GetParam();
// Build module.
const std::vector<const char*> text = {tc.text};
std::unique_ptr<ir::Module> module =
BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, JoinAllInsts(text));
ASSERT_NE(nullptr, module);
// Analyze def and use.
opt::analysis::DefUseManager manager(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<ParseDefUseCase>{
{"", {{}, {}}}, // 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<std::pair<uint32_t, uint32_t>> candidates;
const char* after;
InstDefUse du;
};
using ReplaceUseTest = ::testing::TestWithParam<ReplaceUseCase>;
// Disassembles the given |module| and returns the disassembly.
std::string DisassembleModule(ir::Module* module) {
SpirvTools tools(SPV_ENV_UNIVERSAL_1_1);
std::vector<uint32_t> 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<const char*> text = {tc.before};
std::unique_ptr<ir::Module> module =
BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, JoinAllInsts(text));
ASSERT_NE(nullptr, module);
ir::IRContext context(std::move(module), spvtools::MessageConsumer());
// Analyze def and use.
context.BuildDefUseManager();
// Do the substitution.
for (const auto& candiate : tc.candidates) {
context.ReplaceAllUsesWith(candiate.first, candiate.second);
}
EXPECT_EQ(tc.after, DisassembleModule(context.module()));
CheckDef(tc.du, context.get_def_use_mgr()->id_to_defs());
CheckUse(tc.du, context.get_def_use_mgr()->id_to_uses());
}
// clang-format off
INSTANTIATE_TEST_CASE_P(
TestCase, ReplaceUseTest,
::testing::ValuesIn(std::vector<ReplaceUseCase>{
{ // 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<uint32_t> ids_to_kill;
const char* after;
InstDefUse du;
};
using KillDefTest = ::testing::TestWithParam<KillDefCase>;
TEST_P(KillDefTest, Case) {
const auto& tc = GetParam();
// Build module.
const std::vector<const char*> text = {tc.before};
std::unique_ptr<ir::Module> module =
BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, JoinAllInsts(text));
ASSERT_NE(nullptr, module);
ir::IRContext context(std::move(module), spvtools::MessageConsumer());
// Analyze def and use.
opt::analysis::DefUseManager manager(module.get());
// Do the substitution.
for (const auto id : tc.ids_to_kill) context.KillDef(id);
EXPECT_EQ(tc.after, DisassembleModule(context.module()));
CheckDef(tc.du, context.get_def_use_mgr()->id_to_defs());
CheckUse(tc.du, context.get_def_use_mgr()->id_to_uses());
}
// clang-format off
INSTANTIATE_TEST_CASE_P(
TestCase, KillDefTest,
::testing::ValuesIn(std::vector<KillDefCase>{
{ // 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<ir::Module> module =
BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, original_text);
ASSERT_NE(nullptr, module);
ir::IRContext context(std::move(module), spvtools::MessageConsumer());
// Analyze def and use.
context.BuildDefUseManager();
// Do a bunch replacements.
context.ReplaceAllUsesWith(9, 900); // to unused id
context.ReplaceAllUsesWith(10, 1000); // to unused id
context.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(context.module()));
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, context.get_def_use_mgr()->id_to_defs());
{
auto* use_list = context.get_def_use_mgr()->GetUses(6);
ASSERT_NE(nullptr, use_list);
EXPECT_EQ(2u, use_list->size());
std::vector<SpvOp> opcodes = {use_list->front().inst->opcode(),
use_list->back().inst->opcode()};
EXPECT_THAT(opcodes, UnorderedElementsAre(SpvOpSwitch, SpvOpReturnValue));
}
{
auto* use_list = context.get_def_use_mgr()->GetUses(7);
ASSERT_NE(nullptr, use_list);
EXPECT_EQ(6u, use_list->size());
std::vector<SpvOp> opcodes;
for (const auto& use : *use_list) {
opcodes.push_back(use.inst->opcode());
}
// OpSwitch is now a user of %7.
EXPECT_THAT(opcodes, UnorderedElementsAre(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 = context.get_def_use_mgr()->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<ir::Instruction> insts; // instrutions to be analyzed in order.
const char* module_text;
InstDefUse expected_define_use;
};
using AnalyzeInstDefUseTest =
::testing::TestWithParam<AnalyzeInstDefUseTestCase>;
// Test the analyzing result for individual instructions.
TEST_P(AnalyzeInstDefUseTest, Case) {
auto tc = GetParam();
// Build module.
std::unique_ptr<ir::Module> module =
BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, tc.module_text);
ASSERT_NE(nullptr, module);
// Analyze the instructions.
opt::analysis::DefUseManager manager(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<AnalyzeInstDefUseTestCase>{
{ // 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<uint32_t> indices_for_inst_to_kill;
const char* after;
InstDefUse expected_define_use;
};
using KillInstTest = ::testing::TestWithParam<KillInstTestCase>;
TEST_P(KillInstTest, Case) {
auto tc = GetParam();
// Build module.
std::unique_ptr<ir::Module> module =
BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, tc.before);
ASSERT_NE(nullptr, module);
ir::IRContext context(std::move(module), spvtools::MessageConsumer());
context.BuildDefUseManager();
// KillInst
uint32_t index = 0;
context.module()->ForEachInst([&index, &tc, &context](ir::Instruction* inst) {
if (tc.indices_for_inst_to_kill.count(index) != 0) {
context.KillInst(inst);
}
index++;
});
EXPECT_EQ(tc.after, DisassembleModule(context.module()));
CheckDef(tc.expected_define_use, context.get_def_use_mgr()->id_to_defs());
CheckUse(tc.expected_define_use, context.get_def_use_mgr()->id_to_uses());
}
// clang-format off
INSTANTIATE_TEST_CASE_P(
TestCase, KillInstTest,
::testing::ValuesIn(std::vector<KillInstTestCase>{
// 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<std::string> annotations;
};
using GetAnnotationsTest = ::testing::TestWithParam<GetAnnotationsTestCase>;
TEST_P(GetAnnotationsTest, Case) {
const GetAnnotationsTestCase& tc = GetParam();
// Build module.
std::unique_ptr<ir::Module> module =
BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, tc.code);
ASSERT_NE(nullptr, module);
// Get annotations
opt::analysis::DefUseManager manager(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<GetAnnotationsTestCase>{
// 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