SPIRV-Tools/test/opt/def_use_test.cpp
alan-baker d35a78db57
Switch SPIRV-Tools to use spirv.hpp11 internally (#4981)
Fixes #4960

* Switches to using enum classes with an underlying type to avoid
  undefined behaviour
2022-11-04 17:27:10 -04:00

1724 lines
51 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 <string>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "source/opt/build_module.h"
#include "source/opt/def_use_manager.h"
#include "source/opt/ir_context.h"
#include "source/opt/module.h"
#include "spirv-tools/libspirv.hpp"
#include "test/opt/pass_fixture.h"
#include "test/opt/pass_utils.h"
namespace spvtools {
namespace opt {
namespace analysis {
namespace {
using ::testing::Contains;
using ::testing::UnorderedElementsAre;
using ::testing::UnorderedElementsAreArray;
// Returns the number of uses of |id|.
uint32_t NumUses(const std::unique_ptr<IRContext>& context, uint32_t id) {
uint32_t count = 0;
context->get_def_use_mgr()->ForEachUse(
id, [&count](Instruction*, uint32_t) { ++count; });
return count;
}
// Returns the opcode of each use of |id|.
//
// If |id| is used multiple times in a single instruction, that instruction's
// opcode will appear a corresponding number of times.
std::vector<spv::Op> GetUseOpcodes(const std::unique_ptr<IRContext>& context,
uint32_t id) {
std::vector<spv::Op> opcodes;
context->get_def_use_mgr()->ForEachUse(
id, [&opcodes](Instruction* user, uint32_t) {
opcodes.push_back(user->opcode());
});
return opcodes;
}
// Disassembles the given |inst| and returns the disassembly.
std::string DisassembleInst(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 << "]";
auto def = actual_defs.at(id);
if (def->opcode() != spv::Op::OpConstant) {
// Constants don't disassemble properly without a full context.
EXPECT_EQ(expected_def, DisassembleInst(actual_defs.at(id)));
}
}
}
using UserMap = std::unordered_map<uint32_t, std::vector<Instruction*>>;
// Creates a mapping of all definitions to their users (except OpConstant).
//
// OpConstants are skipped because they cannot be disassembled in isolation.
UserMap BuildAllUsers(const DefUseManager* mgr, uint32_t idBound) {
UserMap userMap;
for (uint32_t id = 0; id != idBound; ++id) {
if (mgr->GetDef(id)) {
mgr->ForEachUser(id, [id, &userMap](Instruction* user) {
if (user->opcode() != spv::Op::OpConstant) {
userMap[id].push_back(user);
}
});
}
}
return userMap;
}
// Constants don't disassemble properly without a full context, so skip them as
// checks.
void CheckUse(const InstDefUse& expected_defs_uses, const DefUseManager* mgr,
uint32_t idBound) {
UserMap actual_uses = BuildAllUsers(mgr, idBound);
// 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));
}
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[] =
" %1 = OpTypeVoid "
" %6 = OpTypeInt 32 0 "
"%10 = OpTypeFloat 32 "
"%16 = OpTypeBool "
" %3 = OpTypeFunction %1 "
" %8 = OpConstant %6 0 "
"%18 = OpConstant %6 1 "
"%12 = OpConstant %10 1.0 "
" %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 %8 "
"%13 = OpFAdd %10 %11 %12 "
"%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<IRContext> context =
BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, JoinAllInsts(text),
SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
ASSERT_NE(nullptr, context);
// Analyze def and use.
DefUseManager manager(context->module());
CheckDef(tc.du, manager.id_to_defs());
CheckUse(tc.du, &manager, context->module()->IdBound());
}
// clang-format off
INSTANTIATE_TEST_SUITE_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
}
},
{ // 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
"%1 = OpTypeVoid "
"%2 = OpTypeBool "
"%3 = OpTypeFunction %1 "
"%4 = OpConstantTrue %2 "
"%5 = OpFunction %1 None %3 "
"%6 = OpLabel "
"OpBranchConditional %4 %7 %8 "
"%7 = OpLabel "
"OpBranch %7 "
"%8 = OpLabel "
"OpReturn "
"OpFunctionEnd",
{
{ // defs
{1, "%1 = OpTypeVoid"},
{2, "%2 = OpTypeBool"},
{3, "%3 = OpTypeFunction %1"},
{4, "%4 = OpConstantTrue %2"},
{5, "%5 = OpFunction %1 None %3"},
{6, "%6 = OpLabel"},
{7, "%7 = OpLabel"},
{8, "%8 = OpLabel"},
},
{ // uses
{1, {
"%3 = OpTypeFunction %1",
"%5 = OpFunction %1 None %3",
}
},
{2, {"%4 = OpConstantTrue %2"}},
{3, {"%5 = OpFunction %1 None %3"}},
{4, {"OpBranchConditional %4 %7 %8"}},
{7,
{
"OpBranchConditional %4 %7 %8",
"OpBranch %7",
}
},
{8, {"OpBranchConditional %4 %7 %8"}},
}
}
},
{ // cross function
"%1 = OpTypeBool "
"%3 = OpTypeFunction %1 "
"%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"},
{3, "%3 = OpTypeFunction %1"},
{4, "%4 = OpLabel"},
{5, "%5 = OpVariable %1 Function"},
{6, "%6 = OpFunctionCall %1 %2 %5"},
},
{ // uses
{1,
{
"%2 = OpFunction %1 None %3",
"%3 = OpTypeFunction %1",
"%5 = OpVariable %1 Function",
"%6 = OpFunctionCall %1 %2 %5",
}
},
{2, {"%6 = OpFunctionCall %1 %2 %5"}},
{3, {"%2 = OpFunction %1 None %3"}},
{5, {"%6 = OpFunctionCall %1 %2 %5"}},
{6, {"OpReturnValue %6"}},
}
}
},
{ // selection merge and loop merge
"%1 = OpTypeVoid "
"%3 = OpTypeFunction %1 "
"%10 = OpTypeBool "
"%8 = OpConstantTrue %10 "
"%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
{1, "%1 = OpTypeVoid"},
{2, "%2 = OpFunction %1 None %3"},
{3, "%3 = OpTypeFunction %1"},
{4, "%4 = OpLabel"},
{5, "%5 = OpLabel"},
{6, "%6 = OpLabel"},
{7, "%7 = OpLabel"},
{8, "%8 = OpConstantTrue %10"},
{9, "%9 = OpLabel"},
{10, "%10 = OpTypeBool"},
},
{ // uses
{1,
{
"%2 = OpFunction %1 None %3",
"%3 = OpTypeFunction %1",
}
},
{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"}},
{10, {"%8 = OpConstantTrue %10"}},
}
}
},
{ // 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
{1, "%1 = OpTypeVoid"},
{2, "%2 = OpFunction %1 None %3"},
{3, "%3 = OpTypeFunction %1"},
{4, "%4 = OpLabel"},
{5, "%5 = OpLabel"},
{6, "%6 = OpTypeInt 32 0"},
{7, "%7 = OpPhi %6 %8 %4 %9 %5"},
{8, "%8 = OpConstant %6 0"},
{9, "%9 = OpIAdd %6 %7 %8"},
{10, "%10 = OpTypeFloat 32"},
{11, "%11 = OpPhi %10 %12 %4 %13 %5"},
{12, "%12 = OpConstant %10 1.0"},
{13, "%13 = OpFAdd %10 %11 %12"},
{16, "%16 = OpTypeBool"},
{17, "%17 = OpSLessThan %16 %7 %18"},
{18, "%18 = OpConstant %6 1"},
{19, "%19 = OpLabel"},
},
{ // uses
{1,
{
"%2 = OpFunction %1 None %3",
"%3 = OpTypeFunction %1",
}
},
{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,
{
// Can't check constants properly
// "%8 = OpConstant %6 0",
// "%18 = OpConstant %6 1",
"%7 = OpPhi %6 %8 %4 %9 %5",
"%9 = OpIAdd %6 %7 %8",
}
},
{7,
{
"%9 = OpIAdd %6 %7 %8",
"%17 = OpSLessThan %16 %7 %18",
}
},
{8,
{
"%7 = OpPhi %6 %8 %4 %9 %5",
"%9 = OpIAdd %6 %7 %8",
}
},
{9, {"%7 = OpPhi %6 %8 %4 %9 %5"}},
{10,
{
// "%12 = OpConstant %10 1.0",
"%11 = OpPhi %10 %12 %4 %13 %5",
"%13 = OpFAdd %10 %11 %12",
}
},
{11, {"%13 = OpFAdd %10 %11 %12"}},
{12,
{
"%11 = OpPhi %10 %12 %4 %13 %5",
"%13 = OpFAdd %10 %11 %12",
}
},
{13, {"%11 = OpPhi %10 %12 %4 %13 %5"}},
{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 "
"%3 = OpTypeFunction %1 "
"%2 = OpConstantTrue %1 "
"%4 = OpFunction %1 None %3 "
"%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"},
{3, "%3 = OpTypeFunction %1"},
{4, "%4 = OpFunction %1 None %3"},
{6, "%6 = OpLabel"},
{7, "%7 = OpLabel"},
{8, "%8 = OpPhi %1 %8 %7 %2 %6"},
},
{ // uses
{1,
{
"%2 = OpConstantTrue %1",
"%3 = OpTypeFunction %1",
"%4 = OpFunction %1 None %3",
"%8 = OpPhi %1 %8 %7 %2 %6",
}
},
{2, {"%8 = OpPhi %1 %8 %7 %2 %6"}},
{3, {"%4 = OpFunction %1 None %3"}},
{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(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<IRContext> context =
BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, JoinAllInsts(text),
SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
ASSERT_NE(nullptr, context);
// Force a re-build of def-use manager.
context->InvalidateAnalyses(IRContext::Analysis::kAnalysisDefUse);
(void)context->get_def_use_mgr();
// Do the substitution.
for (const auto& candidate : tc.candidates) {
context->ReplaceAllUsesWith(candidate.first, candidate.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(), context->module()->IdBound());
}
// clang-format off
INSTANTIATE_TEST_SUITE_P(
TestCase, ReplaceUseTest,
::testing::ValuesIn(std::vector<ReplaceUseCase>{
{ // no use, no replace request
"", {}, "", {},
},
{ // replace one use
"%1 = OpTypeBool "
"%2 = OpTypeVector %1 3 "
"%3 = OpTypeInt 32 0 ",
{{1, 3}},
"%1 = OpTypeBool\n"
"%2 = OpTypeVector %3 3\n"
"%3 = OpTypeInt 32 0",
{
{ // defs
{1, "%1 = OpTypeBool"},
{2, "%2 = OpTypeVector %3 3"},
{3, "%3 = OpTypeInt 32 0"},
},
{ // uses
{3, {"%2 = OpTypeVector %3 3"}},
},
},
},
{ // replace and then replace back
"%1 = OpTypeBool "
"%2 = OpTypeVector %1 3 "
"%3 = OpTypeInt 32 0",
{{1, 3}, {3, 1}},
"%1 = OpTypeBool\n"
"%2 = OpTypeVector %1 3\n"
"%3 = OpTypeInt 32 0",
{
{ // defs
{1, "%1 = OpTypeBool"},
{2, "%2 = OpTypeVector %1 3"},
{3, "%3 = OpTypeInt 32 0"},
},
{ // 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 "
"%3 = OpTypeInt 32 0 "
"%4 = OpTypeInt 32 1 ",
{{1, 3}, {3, 4}},
"%1 = OpTypeBool\n"
"%2 = OpTypeVector %4 3\n"
"%3 = OpTypeInt 32 0\n"
"%4 = OpTypeInt 32 1",
{
{ // defs
{1, "%1 = OpTypeBool"},
{2, "%2 = OpTypeVector %4 3"},
{3, "%3 = OpTypeInt 32 0"},
{4, "%4 = OpTypeInt 32 1"},
},
{ // uses
{4, {"%2 = OpTypeVector %4 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 "
"%8 = OpTypeInt 32 0 "
"%9 = OpTypeInt 32 1 "
"%10 = OpTypeInt 64 0",
{{1, 8}, {2, 9}, {4, 10}},
"%1 = OpTypeBool\n"
"%2 = OpTypeVector %8 2\n"
"%3 = OpTypeVector %8 3\n"
"%4 = OpTypeVector %8 4\n"
"%5 = OpTypeMatrix %9 2\n"
"%6 = OpTypeMatrix %3 3\n"
"%7 = OpTypeMatrix %10 4\n"
"%8 = OpTypeInt 32 0\n"
"%9 = OpTypeInt 32 1\n"
"%10 = OpTypeInt 64 0",
{
{ // defs
{1, "%1 = OpTypeBool"},
{2, "%2 = OpTypeVector %8 2"},
{3, "%3 = OpTypeVector %8 3"},
{4, "%4 = OpTypeVector %8 4"},
{5, "%5 = OpTypeMatrix %9 2"},
{6, "%6 = OpTypeMatrix %3 3"},
{7, "%7 = OpTypeMatrix %10 4"},
{8, "%8 = OpTypeInt 32 0"},
{9, "%9 = OpTypeInt 32 1"},
{10, "%10 = OpTypeInt 64 0"},
},
{ // uses
{8,
{
"%2 = OpTypeVector %8 2",
"%3 = OpTypeVector %8 3",
"%4 = OpTypeVector %8 4",
}
},
{9, {"%5 = OpTypeMatrix %9 2"}},
{3, {"%6 = OpTypeMatrix %3 3"}},
{10, {"%7 = OpTypeMatrix %10 4"}},
},
},
},
{ // OpPhi.
kOpPhiTestFunction,
// replace one id used by OpPhi, replace one id generated by OpPhi
{{9, 13}, {11, 9}},
"%1 = OpTypeVoid\n"
"%6 = OpTypeInt 32 0\n"
"%10 = OpTypeFloat 32\n"
"%16 = OpTypeBool\n"
"%3 = OpTypeFunction %1\n"
"%8 = OpConstant %6 0\n"
"%18 = OpConstant %6 1\n"
"%12 = OpConstant %10 1\n"
"%2 = OpFunction %1 None %3\n"
"%4 = OpLabel\n"
"OpBranch %5\n"
"%5 = OpLabel\n"
"%7 = OpPhi %6 %8 %4 %13 %5\n" // %9 -> %13
"%11 = OpPhi %10 %12 %4 %13 %5\n"
"%9 = OpIAdd %6 %7 %8\n"
"%13 = OpFAdd %10 %9 %12\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.
{1, "%1 = OpTypeVoid"},
{2, "%2 = OpFunction %1 None %3"},
{3, "%3 = OpTypeFunction %1"},
{4, "%4 = OpLabel"},
{5, "%5 = OpLabel"},
{6, "%6 = OpTypeInt 32 0"},
{7, "%7 = OpPhi %6 %8 %4 %13 %5"},
{8, "%8 = OpConstant %6 0"},
{9, "%9 = OpIAdd %6 %7 %8"},
{10, "%10 = OpTypeFloat 32"},
{11, "%11 = OpPhi %10 %12 %4 %13 %5"},
{12, "%12 = OpConstant %10 1.0"},
{13, "%13 = OpFAdd %10 %9 %12"},
{16, "%16 = OpTypeBool"},
{17, "%17 = OpSLessThan %16 %7 %18"},
{18, "%18 = OpConstant %6 1"},
{19, "%19 = OpLabel"},
},
{ // uses
{1,
{
"%2 = OpFunction %1 None %3",
"%3 = OpTypeFunction %1",
}
},
{3, {"%2 = OpFunction %1 None %3"}},
{4,
{
"%7 = OpPhi %6 %8 %4 %13 %5",
"%11 = OpPhi %10 %12 %4 %13 %5",
}
},
{5,
{
"OpBranch %5",
"%7 = OpPhi %6 %8 %4 %13 %5",
"%11 = OpPhi %10 %12 %4 %13 %5",
"OpLoopMerge %19 %5 None",
"OpBranchConditional %17 %5 %19",
}
},
{6,
{
// Can't properly check constants
// "%8 = OpConstant %6 0",
// "%18 = OpConstant %6 1",
"%7 = OpPhi %6 %8 %4 %13 %5",
"%9 = OpIAdd %6 %7 %8"
}
},
{7,
{
"%9 = OpIAdd %6 %7 %8",
"%17 = OpSLessThan %16 %7 %18",
}
},
{8,
{
"%7 = OpPhi %6 %8 %4 %13 %5",
"%9 = OpIAdd %6 %7 %8",
}
},
{9, {"%13 = OpFAdd %10 %9 %12"}}, // uses of %9 changed from %7 to %13
{10,
{
"%11 = OpPhi %10 %12 %4 %13 %5",
// "%12 = OpConstant %10 1",
"%13 = OpFAdd %10 %9 %12"
}
},
// no more uses of %11
{12,
{
"%11 = OpPhi %10 %12 %4 %13 %5",
"%13 = OpFAdd %10 %9 %12"
}
},
{13, {
"%7 = OpPhi %6 %8 %4 %13 %5",
"%11 = OpPhi %10 %12 %4 %13 %5",
}
},
{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 "
"%3 = OpTypeFunction %1 "
"%2 = OpConstantTrue %1 "
"%4 = OpFunction %3 None %1 "
"%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"
"%3 = OpTypeFunction %1\n"
"%2 = OpConstantTrue %1\n"
"%4 = OpFunction %3 None %1\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"},
{3, "%3 = OpTypeFunction %1"},
{4, "%4 = OpFunction %3 None %1"},
{6, "%6 = OpLabel"},
{7, "%7 = OpLabel"},
{8, "%8 = OpPhi %1 %2 %7 %2 %6"},
},
{ // uses
{1,
{
"%2 = OpConstantTrue %1",
"%3 = OpTypeFunction %1",
"%4 = OpFunction %3 None %1",
"%8 = OpPhi %1 %2 %7 %2 %6",
}
},
{2,
{
// Only checking users
"%8 = OpPhi %1 %2 %7 %2 %6",
}
},
{3, {"%4 = OpFunction %3 None %1"}},
{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<IRContext> context =
BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, JoinAllInsts(text),
SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
ASSERT_NE(nullptr, context);
// Analyze def and use.
DefUseManager manager(context->module());
// 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(), context->module()->IdBound());
}
// clang-format off
INSTANTIATE_TEST_SUITE_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
"%2 = OpTypeVector %1 2\n"
"%4 = OpTypeVector %1 4\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
"%1 = OpTypeVoid\n"
"%6 = OpTypeInt 32 0\n"
"%10 = OpTypeFloat 32\n"
"%16 = OpTypeBool\n"
"%3 = OpTypeFunction %1\n"
"%8 = OpConstant %6 0\n"
"%18 = OpConstant %6 1\n"
"%12 = OpConstant %10 1\n"
"%2 = OpFunction %1 None %3\n"
"%4 = OpLabel\n"
"OpBranch %5\n"
"%5 = OpLabel\n"
"%7 = OpPhi %6 %8 %4 %9 %5\n"
"%13 = OpFAdd %10 %11 %12\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.
{1, "%1 = OpTypeVoid"},
{2, "%2 = OpFunction %1 None %3"},
{3, "%3 = OpTypeFunction %1"},
{4, "%4 = OpLabel"},
{5, "%5 = OpLabel"},
{6, "%6 = OpTypeInt 32 0"},
{7, "%7 = OpPhi %6 %8 %4 %9 %5"},
{8, "%8 = OpConstant %6 0"},
{10, "%10 = OpTypeFloat 32"},
{12, "%12 = OpConstant %10 1.0"},
{13, "%13 = OpFAdd %10 %11 %12"},
{16, "%16 = OpTypeBool"},
{17, "%17 = OpSLessThan %16 %7 %18"},
{18, "%18 = OpConstant %6 1"},
{19, "%19 = OpLabel"},
},
{ // uses
{1,
{
"%2 = OpFunction %1 None %3",
"%3 = OpTypeFunction %1",
}
},
{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,
{
// Can't properly check constants
// "%8 = OpConstant %6 0",
// "%18 = OpConstant %6 1",
"%7 = OpPhi %6 %8 %4 %9 %5",
// "%9 = OpIAdd %6 %7 %8"
}
},
{7, {"%17 = OpSLessThan %16 %7 %18"}},
{8,
{
"%7 = OpPhi %6 %8 %4 %9 %5",
// "%9 = OpIAdd %6 %7 %8",
}
},
// {9, {"%7 = OpPhi %6 %8 %4 %13 %5"}},
{10,
{
// "%11 = OpPhi %10 %12 %4 %13 %5",
// "%12 = OpConstant %10 1",
"%13 = OpFAdd %10 %11 %12"
}
},
// {11, {"%13 = OpFAdd %10 %11 %12"}},
{12,
{
// "%11 = OpPhi %10 %12 %4 %13 %5",
"%13 = OpFAdd %10 %11 %12"
}
},
// {13, {"%11 = OpPhi %10 %12 %4 %13 %5"}},
{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 "
"%3 = OpTypeFunction %1 "
"%2 = OpConstantTrue %1 "
"%4 = OpFunction %3 None %1 "
"%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"
"%3 = OpTypeFunction %1\n"
"%2 = OpConstantTrue %1\n"
"%4 = OpFunction %3 None %1\n"
"%6 = OpLabel\n"
"OpBranch %7\n"
"%7 = OpLabel\n"
"OpBranch %7\n"
"OpFunctionEnd",
{
{ // defs
{1, "%1 = OpTypeBool"},
{2, "%2 = OpConstantTrue %1"},
{3, "%3 = OpTypeFunction %1"},
{4, "%4 = OpFunction %3 None %1"},
{6, "%6 = OpLabel"},
{7, "%7 = OpLabel"},
// {8, "%8 = OpPhi %1 %8 %7 %2 %6"},
},
{ // uses
{1,
{
"%2 = OpConstantTrue %1",
"%3 = OpTypeFunction %1",
"%4 = OpFunction %3 None %1",
// "%8 = OpPhi %1 %8 %7 %2 %6",
}
},
// {2, {"%8 = OpPhi %1 %8 %7 %2 %6"}},
{3, {"%4 = OpFunction %3 None %1"}},
// {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 "
" %3 = OpTypePointer Input %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<IRContext> context =
BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, original_text,
SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
ASSERT_NE(nullptr, context);
// Force a re-build of def-use manager.
context->InvalidateAnalyses(IRContext::Analysis::kAnalysisDefUse);
(void)context->get_def_use_mgr();
// Do a bunch replacements.
context->ReplaceAllUsesWith(11, 7); // to existing id
context->ReplaceAllUsesWith(10, 11); // to existing id
context->ReplaceAllUsesWith(9, 10); // to existing id
// clang-format off
const char modified_text[] =
"%1 = OpTypeInt 64 1\n"
"%3 = OpTypePointer Input %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 %10 -4294967296 %11 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"},
{3, "%3 = OpTypePointer Input %1"},
{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());
{
EXPECT_EQ(2u, NumUses(context, 6));
std::vector<spv::Op> opcodes = GetUseOpcodes(context, 6u);
EXPECT_THAT(opcodes, UnorderedElementsAre(spv::Op::OpSwitch,
spv::Op::OpReturnValue));
}
{
EXPECT_EQ(6u, NumUses(context, 7));
std::vector<spv::Op> opcodes = GetUseOpcodes(context, 7u);
// OpSwitch is now a user of %7.
EXPECT_THAT(opcodes, UnorderedElementsAre(
spv::Op::OpSelectionMerge, spv::Op::OpBranch,
spv::Op::OpBranch, spv::Op::OpBranch,
spv::Op::OpBranch, spv::Op::OpSwitch));
}
// Check all ids only used by OpSwitch after replacement.
for (const auto id : {8u, 10u, 11u}) {
EXPECT_EQ(1u, NumUses(context, id));
EXPECT_EQ(spv::Op::OpSwitch, GetUseOpcodes(context, id).back());
}
}
// Test case for analyzing individual instructions.
struct AnalyzeInstDefUseTestCase {
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<IRContext> context =
BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, tc.module_text);
ASSERT_NE(nullptr, context);
// Analyze the instructions.
DefUseManager manager(context->module());
CheckDef(tc.expected_define_use, manager.id_to_defs());
CheckUse(tc.expected_define_use, &manager, context->module()->IdBound());
// CheckUse(tc.expected_define_use, manager.id_to_uses());
}
// clang-format off
INSTANTIATE_TEST_SUITE_P(
TestCase, AnalyzeInstDefUseTest,
::testing::ValuesIn(std::vector<AnalyzeInstDefUseTestCase>{
{ // A type declaring instruction.
"%1 = OpTypeInt 32 1",
{
// defs
{{1, "%1 = OpTypeInt 32 1"}},
{}, // no uses
},
},
{ // A type declaring instruction and a constant value.
"%1 = OpTypeBool "
"%2 = OpConstantTrue %1",
{
{ // defs
{1, "%1 = OpTypeBool"},
{2, "%2 = OpConstantTrue %1"},
},
{ // uses
{1, {"%2 = OpConstantTrue %1"}},
},
},
},
}));
// clang-format on
using AnalyzeInstDefUse = ::testing::Test;
TEST(AnalyzeInstDefUse, UseWithNoResultId) {
IRContext context(SPV_ENV_UNIVERSAL_1_2, nullptr);
// Analyze the instructions.
DefUseManager manager(context.module());
Instruction label(&context, spv::Op::OpLabel, 0, 2, {});
manager.AnalyzeInstDefUse(&label);
Instruction branch(&context, spv::Op::OpBranch, 0, 0,
{{SPV_OPERAND_TYPE_ID, {2}}});
manager.AnalyzeInstDefUse(&branch);
context.module()->SetIdBound(3);
InstDefUse expected = {
// defs
{
{2, "%2 = OpLabel"},
},
// uses
{{2, {"OpBranch %2"}}},
};
CheckDef(expected, manager.id_to_defs());
CheckUse(expected, &manager, context.module()->IdBound());
}
TEST(AnalyzeInstDefUse, AddNewInstruction) {
const std::string input = "%1 = OpTypeBool";
// Build module.
std::unique_ptr<IRContext> context =
BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, input);
ASSERT_NE(nullptr, context);
// Analyze the instructions.
DefUseManager manager(context->module());
Instruction newInst(context.get(), spv::Op::OpConstantTrue, 1, 2, {});
manager.AnalyzeInstDefUse(&newInst);
InstDefUse expected = {
{
// defs
{1, "%1 = OpTypeBool"},
{2, "%2 = OpConstantTrue %1"},
},
{
// uses
{1, {"%2 = OpConstantTrue %1"}},
},
};
CheckDef(expected, manager.id_to_defs());
CheckUse(expected, &manager, context->module()->IdBound());
}
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<IRContext> context =
BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, tc.before,
SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
ASSERT_NE(nullptr, context);
// Force a re-build of the def-use manager.
context->InvalidateAnalyses(IRContext::Analysis::kAnalysisDefUse);
(void)context->get_def_use_mgr();
// KillInst
context->module()->ForEachInst([&tc, &context](Instruction* inst) {
if (tc.indices_for_inst_to_kill.count(inst->result_id())) {
context->KillInst(inst);
}
});
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(),
context->module()->IdBound());
}
// clang-format off
INSTANTIATE_TEST_SUITE_P(
TestCase, KillInstTest,
::testing::ValuesIn(std::vector<KillInstTestCase>{
// Kill id defining instructions.
{
"%3 = OpTypeVoid "
"%1 = OpTypeFunction %3 "
"%2 = OpFunction %1 None %3 "
"%4 = OpLabel "
" OpBranch %5 "
"%5 = OpLabel "
" OpBranch %6 "
"%6 = OpLabel "
" OpBranch %4 "
"%7 = OpLabel "
" OpReturn "
" OpFunctionEnd",
{3, 5, 7},
"%1 = OpTypeFunction %3\n"
"%2 = OpFunction %1 None %3\n"
"%4 = OpLabel\n"
"OpBranch %5\n"
"OpNop\n"
"OpBranch %6\n"
"%6 = OpLabel\n"
"OpBranch %4\n"
"OpNop\n"
"OpReturn\n"
"OpFunctionEnd",
{
// defs
{
{1, "%1 = OpTypeFunction %3"},
{2, "%2 = OpFunction %1 None %3"},
{4, "%4 = OpLabel"},
{6, "%6 = OpLabel"},
},
// uses
{
{1, {"%2 = OpFunction %1 None %3"}},
{4, {"OpBranch %4"}},
{6, {"OpBranch %6"}},
}
}
},
// Kill instructions that do not have result ids.
{
"%3 = OpTypeVoid "
"%1 = OpTypeFunction %3 "
"%2 = OpFunction %1 None %3 "
"%4 = OpLabel "
" OpBranch %5 "
"%5 = OpLabel "
" OpBranch %6 "
"%6 = OpLabel "
" OpBranch %4 "
"%7 = OpLabel "
" OpReturn "
" OpFunctionEnd",
{2, 4},
"%3 = OpTypeVoid\n"
"%1 = OpTypeFunction %3\n"
"OpNop\n"
"OpNop\n"
"OpBranch %5\n"
"%5 = OpLabel\n"
"OpBranch %6\n"
"%6 = OpLabel\n"
"OpBranch %4\n"
"%7 = OpLabel\n"
"OpReturn\n"
"OpFunctionEnd",
{
// defs
{
{1, "%1 = OpTypeFunction %3"},
{3, "%3 = OpTypeVoid"},
{5, "%5 = OpLabel"},
{6, "%6 = OpLabel"},
{7, "%7 = OpLabel"},
},
// uses
{
{3, {"%1 = OpTypeFunction %3"}},
{5, {"OpBranch %5"}},
{6, {"OpBranch %6"}},
}
}
},
}));
// 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<IRContext> context =
BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, tc.code);
ASSERT_NE(nullptr, context);
// Get annotations
DefUseManager manager(context->module());
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_SUITE_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",
},
},
// member decorate
{
// code
"OpMemberDecorate %1 0 RelaxedPrecision "
"%2 = OpTypeInt 32 0 "
"%1 = OpTypeStruct %2 %2",
// id
1,
// annotations
{
"OpMemberDecorate %1 0 RelaxedPrecision",
},
},
}));
using UpdateUsesTest = PassTest<::testing::Test>;
TEST_F(UpdateUsesTest, KeepOldUses) {
const std::vector<const char*> text = {
// clang-format off
"OpCapability Shader",
"%1 = OpExtInstImport \"GLSL.std.450\"",
"OpMemoryModel Logical GLSL450",
"OpEntryPoint Vertex %main \"main\"",
"OpName %main \"main\"",
"%void = OpTypeVoid",
"%4 = OpTypeFunction %void",
"%uint = OpTypeInt 32 0",
"%uint_5 = OpConstant %uint 5",
"%25 = OpConstant %uint 25",
"%main = OpFunction %void None %4",
"%8 = OpLabel",
"%9 = OpIMul %uint %uint_5 %uint_5",
"%10 = OpIMul %uint %9 %uint_5",
"OpReturn",
"OpFunctionEnd"
// clang-format on
};
std::unique_ptr<IRContext> context =
BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, JoinAllInsts(text),
SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
ASSERT_NE(nullptr, context);
DefUseManager* def_use_mgr = context->get_def_use_mgr();
Instruction* def = def_use_mgr->GetDef(9);
Instruction* use = def_use_mgr->GetDef(10);
def->SetOpcode(spv::Op::OpCopyObject);
def->SetInOperands({{SPV_OPERAND_TYPE_ID, {25}}});
context->UpdateDefUse(def);
auto scanUser = [&](Instruction* user) { return user != use; };
bool userFound = !def_use_mgr->WhileEachUser(def, scanUser);
EXPECT_TRUE(userFound);
}
// clang-format on
} // namespace
} // namespace analysis
} // namespace opt
} // namespace spvtools