Optimization: Add type manager.

Type manager will construct a map of types gradually from
instructions.
This commit is contained in:
Lei Zhang 2016-07-28 12:15:14 -04:00
parent 6d4d15b9d0
commit c562e231e3
7 changed files with 543 additions and 2 deletions

View File

@ -11,6 +11,7 @@ add_library(SPIRV-Tools-opt
passes.h passes.h
pass_manager.h pass_manager.h
types.h types.h
type_manager.h
def_use_manager.cpp def_use_manager.cpp
function.cpp function.cpp
@ -20,6 +21,7 @@ add_library(SPIRV-Tools-opt
module.cpp module.cpp
passes.cpp passes.cpp
types.cpp types.cpp
type_manager.cpp
) )
spvtools_default_compile_options(SPIRV-Tools-opt) spvtools_default_compile_options(SPIRV-Tools-opt)

View File

@ -30,7 +30,7 @@
namespace spvtools { namespace spvtools {
namespace ir { namespace ir {
std::vector<Instruction*> Module::types() { std::vector<Instruction*> Module::GetTypes() {
std::vector<Instruction*> insts; std::vector<Instruction*> insts;
for (uint32_t i = 0; i < types_values_.size(); ++i) { for (uint32_t i = 0; i < types_values_.size(); ++i) {
if (IsTypeInst(types_values_[i]->opcode())) if (IsTypeInst(types_values_[i]->opcode()))
@ -39,6 +39,15 @@ std::vector<Instruction*> Module::types() {
return insts; return insts;
}; };
std::vector<const Instruction*> Module::GetTypes() const {
std::vector<const Instruction*> 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<Instruction*> Module::GetConstants() { std::vector<Instruction*> Module::GetConstants() {
std::vector<Instruction*> insts; std::vector<Instruction*> insts;
for (uint32_t i = 0; i < types_values_.size(); ++i) { for (uint32_t i = 0; i < types_values_.size(); ++i) {

View File

@ -83,7 +83,8 @@ class Module {
// Returns a vector of pointers to type-declaration instructions in this // Returns a vector of pointers to type-declaration instructions in this
// module. // module.
std::vector<Instruction*> types(); std::vector<Instruction*> GetTypes();
std::vector<const Instruction*> GetTypes() const;
// Returns the constant-defining instructions. // Returns the constant-defining instructions.
std::vector<Instruction*> GetConstants(); std::vector<Instruction*> GetConstants();
const std::vector<std::unique_ptr<Instruction>>& debugs() const { const std::vector<std::unique_ptr<Instruction>>& debugs() const {

226
source/opt/type_manager.cpp Normal file
View File

@ -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 <algorithm>
#include <cassert>
#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<SpvAccessQualifier>(inst.GetSingleWordInOperand(7));
type = new Image(
GetType(inst.GetSingleWordInOperand(0)),
static_cast<SpvDim>(inst.GetSingleWordInOperand(1)),
inst.GetSingleWordInOperand(2), inst.GetSingleWordInOperand(3),
inst.GetSingleWordInOperand(4), inst.GetSingleWordInOperand(5),
static_cast<SpvImageFormat>(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<Type*> 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<const char*>(data));
} break;
case SpvOpTypePointer: {
auto* ptr = new Pointer(
GetType(inst.GetSingleWordInOperand(1)),
static_cast<SpvStorageClass>(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<Type*> 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<SpvAccessQualifier>(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<SpvStorageClass>(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<uint32_t> 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<uint32_t> 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

86
source/opt/type_manager.h Normal file
View File

@ -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 <memory>
#include <unordered_map>
#include <unordered_set>
#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<uint32_t, std::unique_ptr<Type>>;
using ForwardPointerVector = std::vector<std::unique_ptr<ForwardPointer>>;
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<ForwardPointer*> unresolved_forward_pointers_;
};
} // namespace analysis
} // namespace opt
} // namespace spvtools
#endif // LIBSPIRV_OPT_TYPE_MANAGER_H_

View File

@ -68,3 +68,8 @@ add_spvtools_unittest(TARGET types
SRCS test_types.cpp SRCS test_types.cpp
LIBS SPIRV-Tools-opt LIBS SPIRV-Tools-opt
) )
add_spvtools_unittest(TARGET type_manager
SRCS test_type_manager.cpp
LIBS SPIRV-Tools-opt ${SPIRV_TOOLS}
)

View File

@ -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 <gmock/gmock.h>
#include <gtest/gtest.h>
#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<std::pair<uint32_t, std::string>> type_id_strs = {
{1, "void"},
{2, "bool"},
{3, "uint32"},
// Id 4 is used by the constant.
{5, "sint32"},
{6, "float64"},
{7, "<uint32, 3>"},
{8, "<<uint32, 3>, 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, <uint32, 3>}"},
{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<ir::Module> 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<ir::Module> 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<ir::Module> 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