Handle decoration groups with no decorations. (#1921)
In DecorationManager::RemoveDecorationsFrom, we do not remove the id
from a decoration group if the group has no decorations. This causes
problems because KillNamesAndDecorates is suppose to remove all
references to the id, but in this case, there is still a reference.
This is fixed by adding a special case.
Also, there is the possibility of a double free because
RemoveDecorationsFrom will delete the instructions defining |id| when
|id| is a decoration group. Later, KillInst would later write to memory
that has been deleted when trying to turn it into a Nop. To fix this,
we will only remove the decorations that use |id| and not its definition
in RemoveDecorationsFrom.
diff --git a/source/opt/decoration_manager.cpp b/source/opt/decoration_manager.cpp
index 82aa495..185dcb7 100644
--- a/source/opt/decoration_manager.cpp
+++ b/source/opt/decoration_manager.cpp
@@ -29,7 +29,9 @@
void DecorationManager::RemoveDecorationsFrom(
uint32_t id, std::function<bool(const Instruction&)> pred) {
const auto ids_iter = id_to_decoration_insts_.find(id);
- if (ids_iter == id_to_decoration_insts_.end()) return;
+ if (ids_iter == id_to_decoration_insts_.end()) {
+ return;
+ }
TargetData& decorations_info = ids_iter->second;
auto context = module_->context();
@@ -59,8 +61,14 @@
if (!pred(*decoration)) group_decorations_to_keep.push_back(decoration);
}
- // If all decorations should be kept, move to the next group
- if (group_decorations_to_keep.size() == group_decorations.size()) continue;
+ // If all decorations should be kept, then we can keep |id| part of the
+ // group. However, if the group itself has no decorations, we should remove
+ // the id from the group. This is needed to make |KillNameAndDecorate| work
+ // correctly when a decoration group has no decorations.
+ if (group_decorations_to_keep.size() == group_decorations.size() &&
+ group_decorations.size() != 0) {
+ continue;
+ }
// Otherwise, remove |id| from the targets of |group_id|
const uint32_t stride = inst->opcode() == SpvOpGroupDecorate ? 1u : 2u;
@@ -136,9 +144,6 @@
decorations_info.indirect_decorations.empty() &&
decorations_info.decorate_insts.empty()) {
id_to_decoration_insts_.erase(ids_iter);
-
- // Remove the OpDecorationGroup defining this group.
- if (is_group) context->KillInst(context->get_def_use_mgr()->GetDef(id));
}
}
diff --git a/source/opt/decoration_manager.h b/source/opt/decoration_manager.h
index a517ba2..fb9cfb6 100644
--- a/source/opt/decoration_manager.h
+++ b/source/opt/decoration_manager.h
@@ -36,11 +36,22 @@
}
DecorationManager() = delete;
- // Removes all decorations from |id| (either directly or indirectly) for
- // which |pred| returns true.
- // If |id| is a group ID, OpGroupDecorate and OpGroupMemberDecorate will be
- // removed if they have no targets left, and OpDecorationGroup will be
- // removed if the group is not applied to anyone and contains no decorations.
+ // Changes all of the decorations (direct and through groups) where |pred| is
+ // true and that apply to |id| so that they no longer apply to |id|.
+ //
+ // If |id| is part of a group, it will be removed from the group if it
+ // does not use all of the group's decorations, or, if there are no
+ // decorations that apply to the group.
+ //
+ // If decoration groups become empty, the |OpGroupDecorate| and
+ // |OpGroupMemberDecorate| instructions will be killed.
+ //
+ // Decoration instructions that apply directly to |id| will be killed.
+ //
+ // If |id| is a decoration group and all of the group's decorations are
+ // removed, then the |OpGroupDecorate| and
+ // |OpGroupMemberDecorate| for the group will be killed, but not the defining
+ // |OpDecorationGroup| instruction.
void RemoveDecorationsFrom(uint32_t id,
std::function<bool(const Instruction&)> pred =
[](const Instruction&) { return true; });
diff --git a/test/opt/decoration_manager_test.cpp b/test/opt/decoration_manager_test.cpp
index cf82e8e..f85ff6a 100644
--- a/test/opt/decoration_manager_test.cpp
+++ b/test/opt/decoration_manager_test.cpp
@@ -422,6 +422,7 @@
OpCapability Linkage
OpMemoryModel Logical GLSL450
OpDecorate %1 Constant
+%2 = OpDecorationGroup
%4 = OpTypeInt 32 0
%1 = OpVariable %4 Uniform
%3 = OpVariable %4 Uniform
diff --git a/test/opt/ir_context_test.cpp b/test/opt/ir_context_test.cpp
index 41e708e..10a92a7 100644
--- a/test/opt/ir_context_test.cpp
+++ b/test/opt/ir_context_test.cpp
@@ -282,6 +282,94 @@
EXPECT_EQ(i, localContext.TakeNextUniqueId());
}
+TEST_F(IRContextTest, KillGroupDecorationWitNoDecorations) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ %3 = OpDecorationGroup
+ OpGroupDecorate %3 %4 %5
+ %6 = OpTypeFloat 32
+ %7 = OpTypePointer Function %6
+ %8 = OpTypeStruct %6
+ %9 = OpTypeVoid
+ %10 = OpTypeFunction %9
+ %2 = OpFunction %9 None %10
+ %11 = OpLabel
+ %4 = OpVariable %7 Function
+ %5 = OpVariable %7 Function
+ OpReturn
+ OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+
+ // Build the decoration manager.
+ context->get_decoration_mgr();
+
+ // Delete the second variable.
+ context->KillDef(5);
+
+ // The two decoration instructions should still be there. The first one
+ // should be the same, but the second should have %5 removed.
+
+ // Check the OpDecorationGroup Instruction
+ auto inst = context->annotation_begin();
+ EXPECT_EQ(inst->opcode(), SpvOpDecorationGroup);
+ EXPECT_EQ(inst->result_id(), 3);
+
+ // Check that %5 is no longer part of the group.
+ ++inst;
+ EXPECT_EQ(inst->opcode(), SpvOpGroupDecorate);
+ EXPECT_EQ(inst->NumInOperands(), 2);
+ EXPECT_EQ(inst->GetSingleWordInOperand(0), 3);
+ EXPECT_EQ(inst->GetSingleWordInOperand(1), 4);
+
+ // Check that we are at the end.
+ ++inst;
+ EXPECT_EQ(inst, context->annotation_end());
+}
+
+TEST_F(IRContextTest, KillDecorationGroup) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ %3 = OpDecorationGroup
+ OpGroupDecorate %3 %4 %5
+ %6 = OpTypeFloat 32
+ %7 = OpTypePointer Function %6
+ %8 = OpTypeStruct %6
+ %9 = OpTypeVoid
+ %10 = OpTypeFunction %9
+ %2 = OpFunction %9 None %10
+ %11 = OpLabel
+ %4 = OpVariable %7 Function
+ %5 = OpVariable %7 Function
+ OpReturn
+ OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+
+ // Build the decoration manager.
+ context->get_decoration_mgr();
+
+ // Delete the second variable.
+ context->KillDef(3);
+
+ // Check the OpDecorationGroup Instruction is still there.
+ EXPECT_TRUE(context->annotations().empty());
+}
+
} // namespace
} // namespace opt
} // namespace spvtools