Coverage Report

Created: 2026-05-16 06:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/spirv-tools/source/opt/remove_duplicates_pass.cpp
Line
Count
Source
1
// Copyright (c) 2017 Pierre Moreau
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/remove_duplicates_pass.h"
16
17
#include <algorithm>
18
#include <string>
19
#include <unordered_map>
20
#include <unordered_set>
21
#include <vector>
22
23
#include "source/opcode.h"
24
#include "source/opt/decoration_manager.h"
25
#include "source/opt/ir_context.h"
26
27
namespace spvtools {
28
namespace opt {
29
30
0
Pass::Status RemoveDuplicatesPass::Process() {
31
0
  bool modified = RemoveDuplicateCapabilities();
32
0
  modified |= RemoveDuplicateExtensions();
33
0
  modified |= RemoveDuplicatesExtInstImports();
34
0
  modified |= RemoveDuplicateTypes();
35
0
  modified |= RemoveDuplicateDecorations();
36
37
0
  return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
38
0
}
39
40
0
bool RemoveDuplicatesPass::RemoveDuplicateExtensions() const {
41
0
  bool modified = false;
42
43
0
  if (context()->extensions().empty()) {
44
0
    return modified;
45
0
  }
46
47
  // set of {condition ID, extension name}
48
  // ID 0 means unconditional extension, ie., OpExtension, otherwise the ID is
49
  // the condition operand of OpConditionalExtensionINTEL.
50
0
  std::set<std::pair<uint32_t, std::string>> extensions;
51
0
  for (auto* inst = &*context()->extension_begin(); inst;) {
52
0
    uint32_t cond_id = 0;
53
0
    uint32_t i_name = 0;
54
0
    if (inst->opcode() == spv::Op::OpConditionalExtensionINTEL) {
55
0
      cond_id = inst->GetOperand(0).AsId();
56
0
      i_name = 1;
57
0
    }
58
59
0
    auto res =
60
0
        extensions.insert({cond_id, inst->GetOperand(i_name).AsString()});
61
62
0
    if (res.second) {
63
      // Never seen before, keep it.
64
0
      inst = inst->NextNode();
65
0
    } else {
66
      // It's a duplicate, remove it.
67
0
      inst = context()->KillInst(inst);
68
0
      modified = true;
69
0
    }
70
0
  }
71
72
0
  return modified;
73
0
}
74
75
0
bool RemoveDuplicatesPass::RemoveDuplicateCapabilities() const {
76
0
  bool modified = false;
77
78
0
  if (context()->capabilities().empty()) {
79
0
    return modified;
80
0
  }
81
82
  // set of {condition ID, capability}
83
  // ID 0 means unconditional capability, ie., OpCapability, otherwise the ID is
84
  // the condition operand of OpConditionalCapabilityINTEL.
85
0
  std::set<std::pair<uint32_t, uint32_t>> capabilities;
86
0
  for (auto* inst = &*context()->capability_begin(); inst;) {
87
0
    uint32_t cond_id = 0;
88
0
    uint32_t i_cap = 0;
89
0
    if (inst->opcode() == spv::Op::OpConditionalCapabilityINTEL) {
90
0
      cond_id = inst->GetOperand(0).AsId();
91
0
      i_cap = 1;
92
0
    }
93
94
0
    auto res =
95
0
        capabilities.insert({cond_id, inst->GetSingleWordOperand(i_cap)});
96
97
0
    if (res.second) {
98
      // Never seen before, keep it.
99
0
      inst = inst->NextNode();
100
0
    } else {
101
      // It's a duplicate, remove it.
102
0
      inst = context()->KillInst(inst);
103
0
      modified = true;
104
0
    }
105
0
  }
106
107
0
  return modified;
108
0
}
109
110
0
bool RemoveDuplicatesPass::RemoveDuplicatesExtInstImports() const {
111
0
  bool modified = false;
112
113
0
  if (context()->ext_inst_imports().empty()) {
114
0
    return modified;
115
0
  }
116
117
0
  std::unordered_map<std::string, spv::Id> ext_inst_imports;
118
0
  for (auto* i = &*context()->ext_inst_import_begin(); i;) {
119
0
    auto res = ext_inst_imports.emplace(i->GetInOperand(0u).AsString(),
120
0
                                        i->result_id());
121
0
    if (res.second) {
122
      // Never seen before, keep it.
123
0
      i = i->NextNode();
124
0
    } else {
125
      // It's a duplicate, remove it.
126
0
      context()->ReplaceAllUsesWith(i->result_id(), res.first->second);
127
0
      i = context()->KillInst(i);
128
0
      modified = true;
129
0
    }
130
0
  }
131
132
0
  return modified;
133
0
}
134
135
0
bool RemoveDuplicatesPass::RemoveDuplicateTypes() const {
136
0
  bool modified = false;
137
138
0
  if (context()->types_values().empty()) {
139
0
    return modified;
140
0
  }
141
142
0
  analysis::TypeManager type_manager(context()->consumer(), context());
143
144
0
  std::vector<Instruction*> visited_types;
145
0
  std::vector<analysis::ForwardPointer> visited_forward_pointers;
146
0
  std::vector<Instruction*> to_delete;
147
0
  for (auto* i = &*context()->types_values_begin(); i; i = i->NextNode()) {
148
0
    const bool is_i_forward_pointer =
149
0
        i->opcode() == spv::Op::OpTypeForwardPointer;
150
151
    // We only care about types.
152
0
    if (!spvOpcodeGeneratesType(i->opcode()) && !is_i_forward_pointer) {
153
0
      continue;
154
0
    }
155
156
0
    if (!is_i_forward_pointer) {
157
      // Is the current type equal to one of the types we have already visited?
158
0
      spv::Id id_to_keep = 0u;
159
0
      analysis::Type* i_type = type_manager.GetType(i->result_id());
160
0
      assert(i_type);
161
      // TODO(dneto0): Use a trie to avoid quadratic behaviour? Extract the
162
      // ResultIdTrie from unify_const_pass.cpp for this.
163
0
      for (auto j : visited_types) {
164
0
        analysis::Type* j_type = type_manager.GetType(j->result_id());
165
0
        assert(j_type);
166
0
        if (*i_type == *j_type) {
167
0
          id_to_keep = j->result_id();
168
0
          break;
169
0
        }
170
0
      }
171
172
0
      if (id_to_keep == 0u) {
173
        // This is a never seen before type, keep it around.
174
0
        visited_types.emplace_back(i);
175
0
      } else {
176
        // The same type has already been seen before, remove this one.
177
0
        context()->KillNamesAndDecorates(i->result_id());
178
0
        context()->ReplaceAllUsesWith(i->result_id(), id_to_keep);
179
0
        modified = true;
180
0
        to_delete.emplace_back(i);
181
0
      }
182
0
    } else {
183
0
      analysis::ForwardPointer i_type(
184
0
          i->GetSingleWordInOperand(0u),
185
0
          (spv::StorageClass)i->GetSingleWordInOperand(1u));
186
0
      i_type.SetTargetPointer(
187
0
          type_manager.GetType(i_type.target_id())->AsPointer());
188
189
      // TODO(dneto0): Use a trie to avoid quadratic behaviour? Extract the
190
      // ResultIdTrie from unify_const_pass.cpp for this.
191
0
      const bool found_a_match =
192
0
          std::find(std::begin(visited_forward_pointers),
193
0
                    std::end(visited_forward_pointers),
194
0
                    i_type) != std::end(visited_forward_pointers);
195
196
0
      if (!found_a_match) {
197
        // This is a never seen before type, keep it around.
198
0
        visited_forward_pointers.emplace_back(i_type);
199
0
      } else {
200
        // The same type has already been seen before, remove this one.
201
0
        modified = true;
202
0
        to_delete.emplace_back(i);
203
0
      }
204
0
    }
205
0
  }
206
207
0
  for (auto i : to_delete) {
208
0
    context()->KillInst(i);
209
0
  }
210
211
0
  return modified;
212
0
}
213
214
// TODO(pierremoreau): Duplicate decoration groups should be removed. For
215
// example, in
216
//     OpDecorate %1 Constant
217
//     %1 = OpDecorationGroup
218
//     OpDecorate %2 Constant
219
//     %2 = OpDecorationGroup
220
//     OpGroupDecorate %1 %3
221
//     OpGroupDecorate %2 %4
222
// group %2 could be removed.
223
0
bool RemoveDuplicatesPass::RemoveDuplicateDecorations() const {
224
0
  bool modified = false;
225
226
0
  std::vector<const Instruction*> visited_decorations;
227
228
0
  analysis::DecorationManager decoration_manager(context()->module());
229
0
  for (auto* i = &*context()->annotation_begin(); i;) {
230
    // Is the current decoration equal to one of the decorations we have
231
    // already visited?
232
0
    bool already_visited = false;
233
    // TODO(dneto0): Use a trie to avoid quadratic behaviour? Extract the
234
    // ResultIdTrie from unify_const_pass.cpp for this.
235
0
    for (const Instruction* j : visited_decorations) {
236
0
      if (decoration_manager.AreDecorationsTheSame(&*i, j, false)) {
237
0
        already_visited = true;
238
0
        break;
239
0
      }
240
0
    }
241
242
0
    if (!already_visited) {
243
      // This is a never seen before decoration, keep it around.
244
0
      visited_decorations.emplace_back(&*i);
245
0
      i = i->NextNode();
246
0
    } else {
247
      // The same decoration has already been seen before, remove this one.
248
0
      modified = true;
249
0
      i = context()->KillInst(i);
250
0
    }
251
0
  }
252
253
0
  return modified;
254
0
}
255
256
}  // namespace opt
257
}  // namespace spvtools