1// Copyright (c) 2017 Google Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#include "source/opt/flatten_decoration_pass.h"
16
17#include <cassert>
18#include <memory>
19#include <unordered_map>
20#include <unordered_set>
21#include <utility>
22#include <vector>
23
24#include "source/opt/ir_context.h"
25
26namespace spvtools {
27namespace opt {
28
29using Words = std::vector<uint32_t>;
30using OrderedUsesMap = std::unordered_map<uint32_t, Words>;
31
32Pass::Status FlattenDecorationPass::Process() {
33 bool modified = false;
34
35 // The target Id of OpDecorationGroup instructions.
36 // We have to track this separately from its uses, in case it
37 // has no uses.
38 std::unordered_set<uint32_t> group_ids;
39 // Maps a decoration group Id to its GroupDecorate targets, in order
40 // of appearance.
41 OrderedUsesMap normal_uses;
42 // Maps a decoration group Id to its GroupMemberDecorate targets and
43 // their indices, in of appearance.
44 OrderedUsesMap member_uses;
45
46 auto annotations = context()->annotations();
47
48 // On the first pass, record each OpDecorationGroup with its ordered uses.
49 // Rely on unordered_map::operator[] to create its entries on first access.
50 for (const auto& inst : annotations) {
51 switch (inst.opcode()) {
52 case SpvOp::SpvOpDecorationGroup:
53 group_ids.insert(inst.result_id());
54 break;
55 case SpvOp::SpvOpGroupDecorate: {
56 Words& words = normal_uses[inst.GetSingleWordInOperand(0)];
57 for (uint32_t i = 1; i < inst.NumInOperandWords(); i++) {
58 words.push_back(inst.GetSingleWordInOperand(i));
59 }
60 } break;
61 case SpvOp::SpvOpGroupMemberDecorate: {
62 Words& words = member_uses[inst.GetSingleWordInOperand(0)];
63 for (uint32_t i = 1; i < inst.NumInOperandWords(); i++) {
64 words.push_back(inst.GetSingleWordInOperand(i));
65 }
66 } break;
67 default:
68 break;
69 }
70 }
71
72 // On the second pass, replace OpDecorationGroup and its uses with
73 // equivalent normal and struct member uses.
74 auto inst_iter = annotations.begin();
75 // We have to re-evaluate the end pointer
76 while (inst_iter != context()->annotations().end()) {
77 // Should we replace this instruction?
78 bool replace = false;
79 switch (inst_iter->opcode()) {
80 case SpvOp::SpvOpDecorationGroup:
81 case SpvOp::SpvOpGroupDecorate:
82 case SpvOp::SpvOpGroupMemberDecorate:
83 replace = true;
84 break;
85 case SpvOp::SpvOpDecorate: {
86 // If this decoration targets a group, then replace it
87 // by sets of normal and member decorations.
88 const uint32_t group = inst_iter->GetSingleWordOperand(0);
89 const auto normal_uses_iter = normal_uses.find(group);
90 if (normal_uses_iter != normal_uses.end()) {
91 for (auto target : normal_uses[group]) {
92 std::unique_ptr<Instruction> new_inst(inst_iter->Clone(context()));
93 new_inst->SetInOperand(0, Words{target});
94 inst_iter = inst_iter.InsertBefore(std::move(new_inst));
95 ++inst_iter;
96 replace = true;
97 }
98 }
99 const auto member_uses_iter = member_uses.find(group);
100 if (member_uses_iter != member_uses.end()) {
101 const Words& member_id_pairs = (*member_uses_iter).second;
102 // The collection is a sequence of pairs.
103 assert((member_id_pairs.size() % 2) == 0);
104 for (size_t i = 0; i < member_id_pairs.size(); i += 2) {
105 // Make an OpMemberDecorate instruction for each (target, member)
106 // pair.
107 const uint32_t target = member_id_pairs[i];
108 const uint32_t member = member_id_pairs[i + 1];
109 std::vector<Operand> operands;
110 operands.push_back(Operand(SPV_OPERAND_TYPE_ID, {target}));
111 operands.push_back(
112 Operand(SPV_OPERAND_TYPE_LITERAL_INTEGER, {member}));
113 auto decoration_operands_iter = inst_iter->begin();
114 decoration_operands_iter++; // Skip the group target.
115 operands.insert(operands.end(), decoration_operands_iter,
116 inst_iter->end());
117 std::unique_ptr<Instruction> new_inst(new Instruction(
118 context(), SpvOp::SpvOpMemberDecorate, 0, 0, operands));
119 inst_iter = inst_iter.InsertBefore(std::move(new_inst));
120 ++inst_iter;
121 replace = true;
122 }
123 }
124 // If this is an OpDecorate targeting the OpDecorationGroup itself,
125 // remove it even if that decoration group itself is not the target of
126 // any OpGroupDecorate or OpGroupMemberDecorate.
127 if (!replace && group_ids.count(group)) {
128 replace = true;
129 }
130 } break;
131 default:
132 break;
133 }
134 if (replace) {
135 inst_iter = inst_iter.Erase();
136 modified = true;
137 } else {
138 // Handle the case of decorations unrelated to decoration groups.
139 ++inst_iter;
140 }
141 }
142
143 // Remove OpName instructions which reference the removed group decorations.
144 // An OpDecorationGroup instruction might not have been used by an
145 // OpGroupDecorate or OpGroupMemberDecorate instruction.
146 if (!group_ids.empty()) {
147 for (auto debug_inst_iter = context()->debug2_begin();
148 debug_inst_iter != context()->debug2_end();) {
149 if (debug_inst_iter->opcode() == SpvOp::SpvOpName) {
150 const uint32_t target = debug_inst_iter->GetSingleWordOperand(0);
151 if (group_ids.count(target)) {
152 debug_inst_iter = debug_inst_iter.Erase();
153 modified = true;
154 } else {
155 ++debug_inst_iter;
156 }
157 }
158 }
159 }
160
161 return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
162}
163
164} // namespace opt
165} // namespace spvtools
166