Coverage Report

Created: 2026-11-12 06:47

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/spirv-tools/source/opt/fix_storage_class.cpp
Line
Count
Source
1
// Copyright (c) 2019 Google LLC
2
// Modifications Copyright (C) 2024 Advanced Micro Devices, Inc. All rights
3
// reserved.
4
//
5
// Licensed under the Apache License, Version 2.0 (the "License");
6
// you may not use this file except in compliance with the License.
7
// You may obtain a copy of the License at
8
//
9
//     http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing, software
12
// distributed under the License is distributed on an "AS IS" BASIS,
13
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
// See the License for the specific language governing permissions and
15
// limitations under the License.
16
17
#include "fix_storage_class.h"
18
19
#include <set>
20
21
#include "source/opt/instruction.h"
22
#include "source/opt/ir_context.h"
23
24
namespace spvtools {
25
namespace opt {
26
27
1.18k
Pass::Status FixStorageClass::Process() {
28
1.18k
  bool modified = false;
29
30
3.98M
  get_module()->ForEachInst([this, &modified](Instruction* inst) {
31
3.98M
    if (inst->opcode() == spv::Op::OpVariable) {
32
65.2k
      std::set<uint32_t> seen;
33
65.2k
      std::vector<std::pair<Instruction*, uint32_t>> uses;
34
65.2k
      get_def_use_mgr()->ForEachUse(inst,
35
298k
                                    [&uses](Instruction* use, uint32_t op_idx) {
36
298k
                                      uses.push_back({use, op_idx});
37
298k
                                    });
38
39
298k
      for (auto& use : uses) {
40
298k
        modified |= PropagateStorageClass(
41
298k
            use.first,
42
298k
            static_cast<spv::StorageClass>(inst->GetSingleWordInOperand(0)),
43
298k
            &seen);
44
298k
        assert(seen.empty() && "Seen was not properly reset.");
45
298k
        modified |=
46
298k
            PropagateType(use.first, inst->type_id(), use.second, &seen);
47
298k
        assert(seen.empty() && "Seen was not properly reset.");
48
298k
      }
49
65.2k
    }
50
3.98M
  });
51
1.18k
  if (context()->id_overflow()) {
52
0
    return Status::Failure;
53
0
  }
54
1.18k
  return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
55
1.18k
}
56
57
bool FixStorageClass::PropagateStorageClass(Instruction* inst,
58
                                            spv::StorageClass storage_class,
59
371k
                                            std::set<uint32_t>* seen) {
60
371k
  if (!IsPointerResultType(inst)) {
61
299k
    return false;
62
299k
  }
63
64
72.1k
  if (inst->opcode() == spv::Op::OpPhi) {
65
0
    if (!seen->insert(inst->result_id()).second) {
66
0
      return false;
67
0
    }
68
0
  }
69
70
72.1k
  bool modified = false;
71
72.1k
  if (IsPointerToStorageClass(inst, storage_class)) {
72
72.1k
    std::vector<Instruction*> uses;
73
72.1k
    get_def_use_mgr()->ForEachUser(
74
73.2k
        inst, [&uses](Instruction* use) { uses.push_back(use); });
75
73.2k
    for (Instruction* use : uses) {
76
73.2k
      modified |= PropagateStorageClass(use, storage_class, seen);
77
73.2k
    }
78
72.1k
  } else {
79
0
    switch (inst->opcode()) {
80
0
      case spv::Op::OpAccessChain:
81
0
      case spv::Op::OpPtrAccessChain:
82
0
      case spv::Op::OpInBoundsAccessChain:
83
0
      case spv::Op::OpCopyObject:
84
0
      case spv::Op::OpPhi:
85
0
      case spv::Op::OpSelect:
86
0
        FixInstructionStorageClass(inst, storage_class, seen);
87
0
        modified = true;
88
0
        break;
89
0
      case spv::Op::OpFunctionCall:
90
        // We cannot be sure of the actual connection between the storage class
91
        // of the parameter and the storage class of the result, so we should
92
        // not do anything.  If the result type needs to be fixed, the function
93
        // call should be inlined.
94
0
        break;
95
0
      case spv::Op::OpImageTexelPointer:
96
0
      case spv::Op::OpLoad:
97
0
      case spv::Op::OpStore:
98
0
      case spv::Op::OpCopyMemory:
99
0
      case spv::Op::OpCopyMemorySized:
100
0
      case spv::Op::OpVariable:
101
0
      case spv::Op::OpBitcast:
102
0
      case spv::Op::OpAllocateNodePayloadsAMDX:
103
        // Nothing to change for these opcode.  The result type is the same
104
        // regardless of the storage class of the operand.
105
0
        break;
106
0
      default:
107
0
        assert(false &&
108
0
               "Not expecting instruction to have a pointer result type.");
109
0
        break;
110
0
    }
111
0
  }
112
113
72.1k
  if (inst->opcode() == spv::Op::OpPhi) {
114
0
    seen->erase(inst->result_id());
115
0
  }
116
72.1k
  return modified;
117
72.1k
}
118
119
void FixStorageClass::FixInstructionStorageClass(
120
    Instruction* inst, spv::StorageClass storage_class,
121
0
    std::set<uint32_t>* seen) {
122
0
  assert(IsPointerResultType(inst) &&
123
0
         "The result type of the instruction must be a pointer.");
124
125
0
  ChangeResultStorageClass(inst, storage_class);
126
127
0
  std::vector<Instruction*> uses;
128
0
  get_def_use_mgr()->ForEachUser(
129
0
      inst, [&uses](Instruction* use) { uses.push_back(use); });
130
0
  for (Instruction* use : uses) {
131
0
    PropagateStorageClass(use, storage_class, seen);
132
0
  }
133
0
}
134
135
void FixStorageClass::ChangeResultStorageClass(
136
0
    Instruction* inst, spv::StorageClass storage_class) const {
137
0
  analysis::TypeManager* type_mgr = context()->get_type_mgr();
138
0
  Instruction* result_type_inst = get_def_use_mgr()->GetDef(inst->type_id());
139
0
  assert(result_type_inst->opcode() == spv::Op::OpTypePointer);
140
0
  uint32_t pointee_type_id = result_type_inst->GetSingleWordInOperand(1);
141
0
  uint32_t new_result_type_id =
142
0
      type_mgr->FindPointerToType(pointee_type_id, storage_class);
143
0
  if (new_result_type_id == 0) {
144
0
    return;
145
0
  }
146
0
  inst->SetResultType(new_result_type_id);
147
0
  context()->UpdateDefUse(inst);
148
0
}
149
150
371k
bool FixStorageClass::IsPointerResultType(Instruction* inst) {
151
371k
  if (inst->type_id() == 0) {
152
133k
    return false;
153
133k
  }
154
155
237k
  Instruction* type_def = get_def_use_mgr()->GetDef(inst->type_id());
156
237k
  return type_def->opcode() == spv::Op::OpTypePointer;
157
371k
}
158
159
bool FixStorageClass::IsPointerToStorageClass(Instruction* inst,
160
72.1k
                                              spv::StorageClass storage_class) {
161
72.1k
  if (inst->type_id() == 0) {
162
0
    return false;
163
0
  }
164
165
72.1k
  Instruction* type_def = get_def_use_mgr()->GetDef(inst->type_id());
166
72.1k
  if (type_def->opcode() != spv::Op::OpTypePointer) {
167
0
    return false;
168
0
  }
169
170
72.1k
  const uint32_t kPointerTypeStorageClassIndex = 0;
171
72.1k
  spv::StorageClass pointer_storage_class = static_cast<spv::StorageClass>(
172
72.1k
      type_def->GetSingleWordInOperand(kPointerTypeStorageClassIndex));
173
72.1k
  return pointer_storage_class == storage_class;
174
72.1k
}
175
176
bool FixStorageClass::ChangeResultType(Instruction* inst,
177
235k
                                       uint32_t new_type_id) {
178
235k
  if (new_type_id == 0 || inst->type_id() == new_type_id) {
179
235k
    return false;
180
235k
  }
181
182
0
  context()->ForgetUses(inst);
183
0
  inst->SetResultType(new_type_id);
184
0
  context()->AnalyzeUses(inst);
185
0
  return true;
186
235k
}
187
188
bool FixStorageClass::PropagateType(Instruction* inst, uint32_t type_id,
189
572k
                                    uint32_t op_idx, std::set<uint32_t>* seen) {
190
572k
  assert(type_id != 0 && "Not given a valid type in PropagateType");
191
572k
  bool modified = false;
192
193
  // If the type of operand |op_idx| forces the result type of |inst| to a
194
  // particular type, then we want find that type.
195
572k
  uint32_t new_type_id = 0;
196
572k
  switch (inst->opcode()) {
197
96.9k
    case spv::Op::OpAccessChain:
198
96.9k
    case spv::Op::OpPtrAccessChain:
199
97.3k
    case spv::Op::OpInBoundsAccessChain:
200
97.3k
    case spv::Op::OpInBoundsPtrAccessChain:
201
97.3k
      if (op_idx == 2) {
202
72.1k
        new_type_id = WalkAccessChainType(inst, type_id);
203
72.1k
      }
204
97.3k
      break;
205
19
    case spv::Op::OpCopyObject:
206
19
      new_type_id = type_id;
207
19
      break;
208
615
    case spv::Op::OpPhi:
209
615
      if (seen->insert(inst->result_id()).second) {
210
527
        new_type_id = type_id;
211
527
      }
212
615
      break;
213
0
    case spv::Op::OpSelect:
214
0
      if (op_idx > 2) {
215
0
        new_type_id = type_id;
216
0
      }
217
0
      break;
218
2.09k
    case spv::Op::OpFunctionCall:
219
      // We cannot be sure of the actual connection between the type
220
      // of the parameter and the type of the result, so we should not
221
      // do anything.  If the result type needs to be fixed, the function call
222
      // should be inlined.
223
2.09k
      return false;
224
163k
    case spv::Op::OpLoad: {
225
163k
      Instruction* type_inst = get_def_use_mgr()->GetDef(type_id);
226
163k
      new_type_id = type_inst->GetSingleWordInOperand(1);
227
163k
      break;
228
97.3k
    }
229
160k
    case spv::Op::OpStore: {
230
160k
      uint32_t obj_id = inst->GetSingleWordInOperand(1);
231
160k
      Instruction* obj_inst = get_def_use_mgr()->GetDef(obj_id);
232
160k
      uint32_t obj_type_id = obj_inst->type_id();
233
234
160k
      uint32_t ptr_id = inst->GetSingleWordInOperand(0);
235
160k
      Instruction* ptr_inst = get_def_use_mgr()->GetDef(ptr_id);
236
160k
      uint32_t pointee_type_id = GetPointeeTypeId(ptr_inst);
237
238
160k
      if (obj_type_id != pointee_type_id) {
239
0
        if (context()->get_type_mgr()->GetType(obj_type_id)->AsImage() &&
240
0
            context()->get_type_mgr()->GetType(pointee_type_id)->AsImage()) {
241
          // When storing an image, allow the type mismatch
242
          // and let the later legalization passes eliminate the OpStore.
243
          // This is to support assigning an image to a variable,
244
          // where the assigned image does not have a pre-defined
245
          // image format.
246
0
          return false;
247
0
        }
248
249
0
        uint32_t copy_id = GenerateCopy(obj_inst, pointee_type_id, inst);
250
0
        if (copy_id == 0) {
251
0
          return false;
252
0
        }
253
0
        inst->SetInOperand(1, {copy_id});
254
0
        context()->UpdateDefUse(inst);
255
0
      }
256
160k
    } break;
257
160k
    case spv::Op::OpCopyMemory:
258
0
    case spv::Op::OpCopyMemorySized:
259
      // TODO: May need to expand the copy as we do with the stores.
260
0
      break;
261
8.43k
    case spv::Op::OpCompositeConstruct:
262
11.0k
    case spv::Op::OpCompositeExtract:
263
11.1k
    case spv::Op::OpCompositeInsert:
264
      // TODO: DXC does not seem to generate code that will require changes to
265
      // these opcode.  The can be implemented when they come up.
266
11.1k
      break;
267
0
    case spv::Op::OpImageTexelPointer:
268
4
    case spv::Op::OpBitcast:
269
      // Nothing to change for these opcode.  The result type is the same
270
      // regardless of the type of the operand.
271
4
      return false;
272
137k
    default:
273
      // I expect the remaining instructions to act on types that are guaranteed
274
      // to be unique, so no change will be necessary.
275
137k
      break;
276
572k
  }
277
278
  // If the operand forces the result type, then make sure the result type
279
  // matches, and update the uses of |inst|.  We do not have to check the uses
280
  // of |inst| in the result type is not forced because we are only looking for
281
  // issue that come from mismatches between function formal and actual
282
  // parameters after the function has been inlined.  These parameters are
283
  // pointers. Once the type no longer depends on the type of the parameter,
284
  // then the types should have be correct.
285
569k
  if (new_type_id != 0) {
286
235k
    modified = ChangeResultType(inst, new_type_id);
287
288
235k
    std::vector<std::pair<Instruction*, uint32_t>> uses;
289
235k
    get_def_use_mgr()->ForEachUse(inst,
290
274k
                                  [&uses](Instruction* use, uint32_t idx) {
291
274k
                                    uses.push_back({use, idx});
292
274k
                                  });
293
294
274k
    for (auto& use : uses) {
295
274k
      PropagateType(use.first, new_type_id, use.second, seen);
296
274k
    }
297
298
235k
    if (inst->opcode() == spv::Op::OpPhi) {
299
527
      seen->erase(inst->result_id());
300
527
    }
301
235k
  }
302
569k
  return modified;
303
572k
}
304
305
72.1k
uint32_t FixStorageClass::WalkAccessChainType(Instruction* inst, uint32_t id) {
306
72.1k
  uint32_t start_idx = 0;
307
72.1k
  switch (inst->opcode()) {
308
71.8k
    case spv::Op::OpAccessChain:
309
72.1k
    case spv::Op::OpInBoundsAccessChain:
310
72.1k
      start_idx = 1;
311
72.1k
      break;
312
0
    case spv::Op::OpPtrAccessChain:
313
0
    case spv::Op::OpInBoundsPtrAccessChain:
314
0
      start_idx = 2;
315
0
      break;
316
0
    default:
317
0
      assert(false);
318
0
      break;
319
72.1k
  }
320
321
72.1k
  Instruction* id_type_inst = get_def_use_mgr()->GetDef(id);
322
72.1k
  assert(id_type_inst->opcode() == spv::Op::OpTypePointer);
323
72.1k
  id = id_type_inst->GetSingleWordInOperand(1);
324
72.1k
  spv::StorageClass input_storage_class =
325
72.1k
      static_cast<spv::StorageClass>(id_type_inst->GetSingleWordInOperand(0));
326
327
163k
  for (uint32_t i = start_idx; i < inst->NumInOperands(); ++i) {
328
91.2k
    Instruction* type_inst = get_def_use_mgr()->GetDef(id);
329
91.2k
    switch (type_inst->opcode()) {
330
29.3k
      case spv::Op::OpTypeArray:
331
29.3k
      case spv::Op::OpTypeRuntimeArray:
332
29.3k
      case spv::Op::OpTypeNodePayloadArrayAMDX:
333
29.9k
      case spv::Op::OpTypeMatrix:
334
55.5k
      case spv::Op::OpTypeVector:
335
55.5k
      case spv::Op::OpTypeVectorIdEXT:
336
55.5k
      case spv::Op::OpTypeCooperativeMatrixKHR:
337
55.5k
      case spv::Op::OpTypeCooperativeMatrixNV:
338
55.5k
        id = type_inst->GetSingleWordInOperand(0);
339
55.5k
        break;
340
35.6k
      case spv::Op::OpTypeStruct: {
341
35.6k
        const analysis::Constant* index_const =
342
35.6k
            context()->get_constant_mgr()->FindDeclaredConstant(
343
35.6k
                inst->GetSingleWordInOperand(i));
344
        // It is highly unlikely that any type would have more fields than could
345
        // be indexed by a 32-bit integer, and GetSingleWordInOperand only takes
346
        // a 32-bit value, so we would not be able to handle it anyway. But the
347
        // specification does allow any scalar integer type, treated as signed,
348
        // so we simply downcast the index to 32-bits.
349
35.6k
        uint32_t index =
350
35.6k
            static_cast<uint32_t>(index_const->GetSignExtendedValue());
351
35.6k
        id = type_inst->GetSingleWordInOperand(index);
352
35.6k
        break;
353
55.5k
      }
354
0
      default:
355
0
        break;
356
91.2k
    }
357
91.2k
    assert(id != 0 &&
358
91.2k
           "Tried to extract from an object where it cannot be done.");
359
91.2k
  }
360
361
72.1k
  Instruction* orig_type_inst = get_def_use_mgr()->GetDef(inst->type_id());
362
72.1k
  spv::StorageClass orig_storage_class =
363
72.1k
      static_cast<spv::StorageClass>(orig_type_inst->GetSingleWordInOperand(0));
364
72.1k
  assert(orig_type_inst->opcode() == spv::Op::OpTypePointer);
365
72.1k
  if (orig_type_inst->GetSingleWordInOperand(1) == id &&
366
72.1k
      input_storage_class == orig_storage_class) {
367
    // The existing type is correct. Avoid the search for the type. Note that if
368
    // there is a duplicate type, the search below could return a different type
369
    // forcing more changes to the code than necessary.
370
72.1k
    return inst->type_id();
371
72.1k
  }
372
373
0
  return context()->get_type_mgr()->FindPointerToType(id, input_storage_class);
374
72.1k
}
375
376
// namespace opt
377
378
}  // namespace opt
379
}  // namespace spvtools