From c562e231e3262315745f9e00e49280d7b48495b3 Mon Sep 17 00:00:00 2001 From: Lei Zhang Date: Thu, 28 Jul 2016 12:15:14 -0400 Subject: [PATCH] Optimization: Add type manager. Type manager will construct a map of types gradually from instructions. --- source/opt/CMakeLists.txt | 2 + source/opt/module.cpp | 11 +- source/opt/module.h | 3 +- source/opt/type_manager.cpp | 226 +++++++++++++++++++++++++++++++++ source/opt/type_manager.h | 86 +++++++++++++ test/opt/CMakeLists.txt | 5 + test/opt/test_type_manager.cpp | 212 +++++++++++++++++++++++++++++++ 7 files changed, 543 insertions(+), 2 deletions(-) create mode 100644 source/opt/type_manager.cpp create mode 100644 source/opt/type_manager.h create mode 100644 test/opt/test_type_manager.cpp diff --git a/source/opt/CMakeLists.txt b/source/opt/CMakeLists.txt index ef1b0c6fd..ed9b99f62 100644 --- a/source/opt/CMakeLists.txt +++ b/source/opt/CMakeLists.txt @@ -11,6 +11,7 @@ add_library(SPIRV-Tools-opt passes.h pass_manager.h types.h + type_manager.h def_use_manager.cpp function.cpp @@ -20,6 +21,7 @@ add_library(SPIRV-Tools-opt module.cpp passes.cpp types.cpp + type_manager.cpp ) spvtools_default_compile_options(SPIRV-Tools-opt) diff --git a/source/opt/module.cpp b/source/opt/module.cpp index 67385e91c..d83d21ef6 100644 --- a/source/opt/module.cpp +++ b/source/opt/module.cpp @@ -30,7 +30,7 @@ namespace spvtools { namespace ir { -std::vector Module::types() { +std::vector Module::GetTypes() { std::vector insts; for (uint32_t i = 0; i < types_values_.size(); ++i) { if (IsTypeInst(types_values_[i]->opcode())) @@ -39,6 +39,15 @@ std::vector Module::types() { return insts; }; +std::vector Module::GetTypes() const { + std::vector insts; + for (uint32_t i = 0; i < types_values_.size(); ++i) { + if (IsTypeInst(types_values_[i]->opcode())) + insts.push_back(types_values_[i].get()); + } + return insts; +}; + std::vector Module::GetConstants() { std::vector insts; for (uint32_t i = 0; i < types_values_.size(); ++i) { diff --git a/source/opt/module.h b/source/opt/module.h index 8eca515e7..a46329674 100644 --- a/source/opt/module.h +++ b/source/opt/module.h @@ -83,7 +83,8 @@ class Module { // Returns a vector of pointers to type-declaration instructions in this // module. - std::vector types(); + std::vector GetTypes(); + std::vector GetTypes() const; // Returns the constant-defining instructions. std::vector GetConstants(); const std::vector>& debugs() const { diff --git a/source/opt/type_manager.cpp b/source/opt/type_manager.cpp new file mode 100644 index 000000000..c01ae4a8f --- /dev/null +++ b/source/opt/type_manager.cpp @@ -0,0 +1,226 @@ +// 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 "reflect.h" +#include "type_manager.h" + +namespace spvtools { +namespace opt { +namespace analysis { + +Type* TypeManager::GetType(uint32_t id) const { + if (id_to_type_.count(id) != 0) return id_to_type_.at(id).get(); + return nullptr; +} + +ForwardPointer* TypeManager::GetForwardPointer(uint32_t index) const { + if (index >= forward_pointers_.size()) return nullptr; + return forward_pointers_.at(index).get(); +} + +void TypeManager::AnalyzeType(const spvtools::ir::Module& module) { + for (const auto* inst : module.GetTypes()) RecordIfTypeDefinition(*inst); + for (const auto& inst : module.annotations()) AttachIfTypeDecoration(*inst); +} + +Type* TypeManager::RecordIfTypeDefinition(const spvtools::ir::Instruction& inst) { + if (!spvtools::ir::IsTypeInst(inst.opcode())) return nullptr; + + Type* type = nullptr; + switch (inst.opcode()) { + case SpvOpTypeVoid: + type = new Void(); + break; + case SpvOpTypeBool: + type = new Bool(); + break; + case SpvOpTypeInt: + type = new Integer(inst.GetSingleWordInOperand(0), + inst.GetSingleWordInOperand(1)); + break; + case SpvOpTypeFloat: + type = new Float(inst.GetSingleWordInOperand(0)); + break; + case SpvOpTypeVector: + type = new Vector(GetType(inst.GetSingleWordInOperand(0)), + inst.GetSingleWordInOperand(1)); + break; + case SpvOpTypeMatrix: + type = new Matrix(GetType(inst.GetSingleWordInOperand(0)), + inst.GetSingleWordInOperand(1)); + break; + case SpvOpTypeImage: { + const SpvAccessQualifier access = + inst.NumInOperands() < 8 + ? SpvAccessQualifierReadOnly + : static_cast(inst.GetSingleWordInOperand(7)); + type = new Image( + GetType(inst.GetSingleWordInOperand(0)), + static_cast(inst.GetSingleWordInOperand(1)), + inst.GetSingleWordInOperand(2), inst.GetSingleWordInOperand(3), + inst.GetSingleWordInOperand(4), inst.GetSingleWordInOperand(5), + static_cast(inst.GetSingleWordInOperand(6)), access); + } break; + case SpvOpTypeSampler: + type = new Sampler(); + break; + case SpvOpTypeSampledImage: + type = new SampledImage(GetType(inst.GetSingleWordInOperand(0))); + break; + case SpvOpTypeArray: + type = new Array(GetType(inst.GetSingleWordInOperand(0)), + inst.GetSingleWordInOperand(1)); + break; + case SpvOpTypeRuntimeArray: + type = new RuntimeArray(GetType(inst.GetSingleWordInOperand(0))); + break; + case SpvOpTypeStruct: { + std::vector element_types; + for (uint32_t i = 0; i < inst.NumInOperands(); ++i) { + element_types.push_back(GetType(inst.GetSingleWordInOperand(i))); + } + type = new Struct(element_types); + } break; + case SpvOpTypeOpaque: { + const uint32_t* data = inst.GetInOperand(0).words.data(); + type = new Opaque(reinterpret_cast(data)); + } break; + case SpvOpTypePointer: { + auto* ptr = new Pointer( + GetType(inst.GetSingleWordInOperand(1)), + static_cast(inst.GetSingleWordInOperand(0))); + // Let's see if somebody forward references this pointer. + for (auto* fp : unresolved_forward_pointers_) { + if (fp->target_id() == inst.result_id()) { + fp->SetTargetPointer(ptr); + unresolved_forward_pointers_.erase(fp); + break; + } + } + type = ptr; + } break; + case SpvOpTypeFunction: { + Type* return_type = GetType(inst.GetSingleWordInOperand(0)); + std::vector param_types; + for (uint32_t i = 1; i < inst.NumInOperands(); ++i) { + param_types.push_back(GetType(inst.GetSingleWordInOperand(i))); + } + type = new Function(return_type, param_types); + } break; + case SpvOpTypeEvent: + type = new Event(); + break; + case SpvOpTypeDeviceEvent: + type = new DeviceEvent(); + break; + case SpvOpTypeReserveId: + type = new ReserveId(); + break; + case SpvOpTypeQueue: + type = new Queue(); + break; + case SpvOpTypePipe: + type = new Pipe( + static_cast(inst.GetSingleWordInOperand(0))); + break; + case SpvOpTypeForwardPointer: { + // Handling of forward pointers is different from the other types. + auto* fp = new ForwardPointer( + inst.GetSingleWordInOperand(0), + static_cast(inst.GetSingleWordInOperand(1))); + forward_pointers_.emplace_back(fp); + unresolved_forward_pointers_.insert(fp); + return fp; + } + case SpvOpTypePipeStorage: + type = new PipeStorage(); + break; + case SpvOpTypeNamedBarrier: + type = new NamedBarrier(); + break; + default: + assert(0 && "unhandled type found"); + break; + } + + uint32_t id = inst.result_id(); + if (id == 0) { + assert(inst.opcode() == SpvOpTypeForwardPointer && + "instruction without result id found"); + } else { + assert(type != nullptr && "type should not be nullptr at this point"); + id_to_type_[id].reset(type); + } + return type; +} + +void TypeManager::AttachIfTypeDecoration(const ir::Instruction& inst) { + const SpvOp opcode = inst.opcode(); + if (!ir::IsAnnotationInst(opcode)) return; + const uint32_t id = inst.GetSingleWordOperand(0); + // Do nothing if the id to be decorated is not for a known type. + if (!id_to_type_.count(id)) return; + + Type* target_type = id_to_type_[id].get(); + switch (opcode) { + case SpvOpDecorate: { + const auto count = inst.NumOperands(); + std::vector data; + for (uint32_t i = 1; i < count; ++i) { + data.push_back(inst.GetSingleWordOperand(i)); + } + target_type->AddDecoration(std::move(data)); + } break; + case SpvOpMemberDecorate: { + const auto count = inst.NumOperands(); + const uint32_t index = inst.GetSingleWordOperand(1); + std::vector data; + for (uint32_t i = 2; i < count; ++i) { + data.push_back(inst.GetSingleWordOperand(i)); + } + if (Struct* st = target_type->AsStruct()) { + st->AddMemeberDecoration(index, std::move(data)); + } else { + assert(0 && "OpMemberDecorate on non-struct type"); + } + } break; + case SpvOpDecorationGroup: + case SpvOpGroupDecorate: + case SpvOpGroupMemberDecorate: + assert(0 && "unhandled decoration"); + break; + default: + assert(0 && "unreachable"); + break; + } +} + +} // namespace analysis +} // namespace opt +} // namespace spvtools diff --git a/source/opt/type_manager.h b/source/opt/type_manager.h new file mode 100644 index 000000000..9fb3330dc --- /dev/null +++ b/source/opt/type_manager.h @@ -0,0 +1,86 @@ +// 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. + +#ifndef LIBSPIRV_OPT_TYPE_MANAGER_H_ +#define LIBSPIRV_OPT_TYPE_MANAGER_H_ + +#include +#include +#include + +#include "module.h" +#include "types.h" + +namespace spvtools { +namespace opt { +namespace analysis { + +// A class for managing the SPIR-V type hierarchy. +class TypeManager { + public: + using IdToTypeMap = std::unordered_map>; + using ForwardPointerVector = std::vector>; + + TypeManager() = default; + TypeManager(const TypeManager&) = delete; + TypeManager(TypeManager&&) = delete; + TypeManager& operator=(const TypeManager&) = delete; + TypeManager& operator=(TypeManager&&) = delete; + + // Returns the type for the given type |id|. Returns nullptr if the given |id| + // does not define a type. + Type* GetType(uint32_t id) const; + // Returns the number of types hold in this manager. + size_t NumTypes() const { return id_to_type_.size(); } + + // Returns the forward pointer type at the given |index|. + ForwardPointer* GetForwardPointer(uint32_t index) const; + // Returns the number of forward pointer types hold in this manager. + size_t NumForwardPointers() const { return forward_pointers_.size(); } + + // Analyzes the types and decorations on types in the given |module|. + void AnalyzeType(const spvtools::ir::Module& module); + + private: + // Creates and returns a type from the given SPIR-V |inst|. Returns nullptr if + // the given instruction is not for defining a type. + Type* RecordIfTypeDefinition(const spvtools::ir::Instruction& inst); + // Attaches the decoration encoded in |inst| to a type. Does nothing if the + // given instruction is not a decoration instruction or not decorating a type. + void AttachIfTypeDecoration(const spvtools::ir::Instruction& inst); + + IdToTypeMap id_to_type_; // Mapping from ids to their type representations. + ForwardPointerVector forward_pointers_; // All forward pointer declarations. + // All unresolved forward pointer declarations. + // Refers the contents in the above vector. + std::unordered_set unresolved_forward_pointers_; +}; + +} // namespace analysis +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_OPT_TYPE_MANAGER_H_ diff --git a/test/opt/CMakeLists.txt b/test/opt/CMakeLists.txt index f2fa17e4d..1167e0d0d 100644 --- a/test/opt/CMakeLists.txt +++ b/test/opt/CMakeLists.txt @@ -68,3 +68,8 @@ add_spvtools_unittest(TARGET types SRCS test_types.cpp LIBS SPIRV-Tools-opt ) + +add_spvtools_unittest(TARGET type_manager + SRCS test_type_manager.cpp + LIBS SPIRV-Tools-opt ${SPIRV_TOOLS} +) diff --git a/test/opt/test_type_manager.cpp b/test/opt/test_type_manager.cpp new file mode 100644 index 000000000..b5f460c37 --- /dev/null +++ b/test/opt/test_type_manager.cpp @@ -0,0 +1,212 @@ +// 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 "opt/instruction.h" +#include "opt/libspirv.hpp" +#include "opt/type_manager.h" + +namespace { + +using namespace spvtools; + +TEST(TypeManager, TypeStrings) { + const std::string text = R"( + OpTypeForwardPointer !20 !2 ; id for %p is 20, Uniform is 2 + OpTypeForwardPointer !10000 !1 + %void = OpTypeVoid + %bool = OpTypeBool + %u32 = OpTypeInt 32 0 + %id4 = OpConstant %u32 4 + %s32 = OpTypeInt 32 1 + %f64 = OpTypeFloat 64 + %v3u32 = OpTypeVector %u32 3 + %m3x3 = OpTypeMatrix %v3u32 3 + %img1 = OpTypeImage %s32 Cube 0 1 1 0 R32f ReadWrite + %img2 = OpTypeImage %s32 Cube 0 1 1 0 R32f + %sampler = OpTypeSampler + %si1 = OpTypeSampledImage %img1 + %si2 = OpTypeSampledImage %img2 + %a5u32 = OpTypeArray %u32 %id4 + %af64 = OpTypeRuntimeArray %f64 + %st1 = OpTypeStruct %u32 + %st2 = OpTypeStruct %f64 %s32 %v3u32 + %opaque1 = OpTypeOpaque "" + %opaque2 = OpTypeOpaque "opaque" + %p = OpTypePointer Uniform %st1 + %f = OpTypeFunction %void %u32 %u32 + %event = OpTypeEvent + %de = OpTypeDeviceEvent + %ri = OpTypeReserveId + %queue = OpTypeQueue + %pipe = OpTypePipe ReadOnly + %ps = OpTypePipeStorage + %nb = OpTypeNamedBarrier + )"; + + std::vector> type_id_strs = { + {1, "void"}, + {2, "bool"}, + {3, "uint32"}, + // Id 4 is used by the constant. + {5, "sint32"}, + {6, "float64"}, + {7, ""}, + {8, "<, 3>"}, + {9, "image(sint32, 3, 0, 1, 1, 0, 3, 2)"}, + {10, "image(sint32, 3, 0, 1, 1, 0, 3, 0)"}, + {11, "sampler"}, + {12, "sampled_image(image(sint32, 3, 0, 1, 1, 0, 3, 2))"}, + {13, "sampled_image(image(sint32, 3, 0, 1, 1, 0, 3, 0))"}, + {14, "[uint32, id(4)]"}, + {15, "[float64]"}, + {16, "{uint32}"}, + {17, "{float64, sint32, }"}, + {18, "opaque('')"}, + {19, "opaque('opaque')"}, + {20, "{uint32}*"}, + {21, "(uint32, uint32) -> void"}, + {22, "event"}, + {23, "device_event"}, + {24, "reserve_id"}, + {25, "queue"}, + {26, "pipe(0)"}, + {27, "pipe_storage"}, + {28, "named_barrier"}, + }; + + opt::analysis::TypeManager manager; + std::unique_ptr module = + SpvTools(SPV_ENV_UNIVERSAL_1_1).BuildModule(text); + manager.AnalyzeType(*module); + + EXPECT_EQ(type_id_strs.size(), manager.NumTypes()); + EXPECT_EQ(2u, manager.NumForwardPointers()); + + for (const auto& p : type_id_strs) { + EXPECT_EQ(p.second, manager.GetType(p.first)->str()); + } + EXPECT_EQ("forward_pointer({uint32}*)", manager.GetForwardPointer(0)->str()); + EXPECT_EQ("forward_pointer(10000)", manager.GetForwardPointer(1)->str()); +} + +TEST(Struct, DecorationOnStruct) { + const std::string text = R"( + OpDecorate %struct1 Block + OpDecorate %struct2 Block + OpDecorate %struct3 Block + OpDecorate %struct4 Block + + %u32 = OpTypeInt 32 0 ; id: 5 + %f32 = OpTypeFloat 32 ; id: 6 + %struct1 = OpTypeStruct %u32 %f32 ; base + %struct2 = OpTypeStruct %f32 %u32 ; different member order + %struct3 = OpTypeStruct %f32 ; different member list + %struct4 = OpTypeStruct %u32 %f32 ; the same + %struct7 = OpTypeStruct %f32 ; no decoration + )"; + opt::analysis::TypeManager manager; + std::unique_ptr module = + SpvTools(SPV_ENV_UNIVERSAL_1_1).BuildModule(text); + manager.AnalyzeType(*module); + + ASSERT_EQ(7u, manager.NumTypes()); + ASSERT_EQ(0u, manager.NumForwardPointers()); + // Make sure we get ids correct. + ASSERT_EQ("uint32", manager.GetType(5)->str()); + ASSERT_EQ("float32", manager.GetType(6)->str()); + + // Try all combinations of pairs. Expect to be the same type only when the + // same id or (1, 4). + for (const auto id1 : {1, 2, 3, 4, 7}) { + for (const auto id2 : {1, 2, 3, 4, 7}) { + if (id1 == id2 || (id1 == 1 && id2 == 4) || (id1 == 4 && id2 == 1)) { + EXPECT_TRUE(manager.GetType(id1)->IsSame(manager.GetType(id2))) + << "%struct" << id1 << " is expected to be the same as %struct" + << id2; + } else { + EXPECT_FALSE(manager.GetType(id1)->IsSame(manager.GetType(id2))) + << "%struct" << id1 << " is expected to be different with %struct" + << id2; + } + } + } +} + +TEST(Struct, DecorationOnMember) { + const std::string text = R"( + OpMemberDecorate %struct1 0 Offset 0 + OpMemberDecorate %struct2 0 Offset 0 + OpMemberDecorate %struct3 0 Offset 0 + OpMemberDecorate %struct4 0 Offset 0 + OpMemberDecorate %struct5 1 Offset 0 + OpMemberDecorate %struct6 0 Offset 4 + + OpDecorate %struct7 Block + OpMemberDecorate %struct7 0 Offset 0 + + %u32 = OpTypeInt 32 0 ; id: 8 + %f32 = OpTypeFloat 32 ; id: 9 + %struct1 = OpTypeStruct %u32 %f32 ; base + %struct2 = OpTypeStruct %f32 %u32 ; different member order + %struct3 = OpTypeStruct %f32 ; different member list + %struct4 = OpTypeStruct %u32 %f32 ; the same + %struct5 = OpTypeStruct %u32 %f32 ; member decorate different field + %struct6 = OpTypeStruct %u32 %f32 ; different member decoration parameter + %struct7 = OpTypeStruct %u32 %f32 ; extra decoration on the struct + %struct10 = OpTypeStruct %u32 %f32 ; no member decoration + )"; + opt::analysis::TypeManager manager; + std::unique_ptr module = + SpvTools(SPV_ENV_UNIVERSAL_1_1).BuildModule(text); + manager.AnalyzeType(*module); + + ASSERT_EQ(10u, manager.NumTypes()); + ASSERT_EQ(0u, manager.NumForwardPointers()); + // Make sure we get ids correct. + ASSERT_EQ("uint32", manager.GetType(8)->str()); + ASSERT_EQ("float32", manager.GetType(9)->str()); + + // Try all combinations of pairs. Expect to be the same type only when the + // same id or (1, 4). + for (const auto id1 : {1, 2, 3, 4, 5, 6, 7, 10}) { + for (const auto id2 : {1, 2, 3, 4, 5, 6, 7, 10}) { + if (id1 == id2 || (id1 == 1 && id2 == 4) || (id1 == 4 && id2 == 1)) { + EXPECT_TRUE(manager.GetType(id1)->IsSame(manager.GetType(id2))) + << "%struct" << id1 << " is expected to be the same as %struct" + << id2; + } else { + EXPECT_FALSE(manager.GetType(id1)->IsSame(manager.GetType(id2))) + << "%struct" << id1 << " is expected to be different with %struct" + << id2; + } + } + } +} + +} // anonymous namespace