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/scalar_replacement_pass.cpp
Line
Count
Source
1
// Copyright (c) 2017 Google Inc.
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 "source/opt/scalar_replacement_pass.h"
18
19
#include <algorithm>
20
#include <queue>
21
#include <tuple>
22
#include <utility>
23
24
#include "source/extensions.h"
25
#include "source/opt/reflect.h"
26
#include "source/opt/types.h"
27
#include "source/util/make_unique.h"
28
29
namespace spvtools {
30
namespace opt {
31
namespace {
32
constexpr uint32_t kDebugValueOperandValueIndex = 5;
33
constexpr uint32_t kDebugValueOperandExpressionIndex = 6;
34
constexpr uint32_t kDebugDeclareOperandVariableIndex = 5;
35
}  // namespace
36
37
9.06k
Pass::Status ScalarReplacementPass::Process() {
38
9.06k
  Status status = Status::SuccessWithoutChange;
39
10.8k
  for (auto& f : *get_module()) {
40
10.8k
    if (f.IsDeclaration()) {
41
0
      continue;
42
0
    }
43
44
10.8k
    Status functionStatus = ProcessFunction(&f);
45
10.8k
    if (functionStatus == Status::Failure)
46
0
      return functionStatus;
47
10.8k
    else if (functionStatus == Status::SuccessWithChange)
48
2.63k
      status = functionStatus;
49
10.8k
  }
50
51
9.06k
  if (context()->id_overflow()) {
52
0
    return Status::Failure;
53
0
  }
54
9.06k
  return status;
55
9.06k
}
56
57
10.8k
Pass::Status ScalarReplacementPass::ProcessFunction(Function* function) {
58
10.8k
  std::queue<Instruction*> worklist;
59
10.8k
  BasicBlock& entry = *function->begin();
60
237k
  for (auto iter = entry.begin(); iter != entry.end(); ++iter) {
61
    // Function storage class OpVariables must appear as the first instructions
62
    // of the entry block.
63
237k
    if (iter->opcode() != spv::Op::OpVariable) break;
64
65
226k
    Instruction* varInst = &*iter;
66
226k
    if (CanReplaceVariable(varInst)) {
67
15.8k
      worklist.push(varInst);
68
15.8k
    }
69
226k
  }
70
71
10.8k
  Status status = Status::SuccessWithoutChange;
72
26.8k
  while (!worklist.empty()) {
73
15.9k
    Instruction* varInst = worklist.front();
74
15.9k
    worklist.pop();
75
76
15.9k
    Status var_status = ReplaceVariable(varInst, &worklist);
77
15.9k
    if (var_status == Status::Failure)
78
0
      return var_status;
79
15.9k
    else if (var_status == Status::SuccessWithChange)
80
15.9k
      status = var_status;
81
15.9k
  }
82
83
10.8k
  return status;
84
10.8k
}
85
86
Pass::Status ScalarReplacementPass::ReplaceVariable(
87
15.9k
    Instruction* inst, std::queue<Instruction*>* worklist) {
88
15.9k
  std::vector<Instruction*> replacements;
89
15.9k
  if (!CreateReplacementVariables(inst, &replacements)) {
90
0
    return Status::Failure;
91
0
  }
92
93
15.9k
  std::vector<Instruction*> dead;
94
15.9k
  bool replaced_all_uses = get_def_use_mgr()->WhileEachUser(
95
59.7k
      inst, [this, &replacements, &dead](Instruction* user) {
96
59.7k
        if (user->GetCommonDebugOpcode() == CommonDebugInfoDebugDeclare) {
97
0
          if (ReplaceWholeDebugDeclare(user, replacements)) {
98
0
            dead.push_back(user);
99
0
            return true;
100
0
          }
101
0
          return false;
102
0
        }
103
59.7k
        if (user->GetCommonDebugOpcode() == CommonDebugInfoDebugValue) {
104
0
          if (ReplaceWholeDebugValue(user, replacements)) {
105
0
            dead.push_back(user);
106
0
            return true;
107
0
          }
108
0
          return false;
109
0
        }
110
59.7k
        if (!IsAnnotationInst(user->opcode())) {
111
59.6k
          switch (user->opcode()) {
112
9.60k
            case spv::Op::OpLoad:
113
9.60k
              if (ReplaceWholeLoad(user, replacements)) {
114
9.60k
                dead.push_back(user);
115
9.60k
              } else {
116
0
                return false;
117
0
              }
118
9.60k
              break;
119
12.3k
            case spv::Op::OpStore:
120
12.3k
              if (ReplaceWholeStore(user, replacements)) {
121
12.3k
                dead.push_back(user);
122
12.3k
              } else {
123
0
                return false;
124
0
              }
125
12.3k
              break;
126
36.2k
            case spv::Op::OpAccessChain:
127
36.4k
            case spv::Op::OpInBoundsAccessChain:
128
36.4k
              if (ReplaceAccessChain(user, replacements))
129
36.4k
                dead.push_back(user);
130
0
              else
131
0
                return false;
132
36.4k
              break;
133
36.4k
            case spv::Op::OpName:
134
1.21k
            case spv::Op::OpMemberName:
135
1.21k
              break;
136
0
            default:
137
0
              assert(false && "Unexpected opcode");
138
0
              break;
139
59.6k
          }
140
59.6k
        }
141
59.7k
        return true;
142
59.7k
      });
143
144
15.9k
  if (replaced_all_uses) {
145
15.9k
    dead.push_back(inst);
146
15.9k
  } else {
147
0
    return Status::Failure;
148
0
  }
149
150
  // If there are no dead instructions to clean up, return with no changes.
151
15.9k
  if (dead.empty()) return Status::SuccessWithoutChange;
152
153
  // Clean up some dead code.
154
90.3k
  while (!dead.empty()) {
155
74.3k
    Instruction* toKill = dead.back();
156
74.3k
    dead.pop_back();
157
74.3k
    context()->KillInst(toKill);
158
74.3k
  }
159
160
  // Attempt to further scalarize.
161
134k
  for (auto var : replacements) {
162
134k
    if (var->opcode() == spv::Op::OpVariable) {
163
79.0k
      if (get_def_use_mgr()->NumUsers(var) == 0) {
164
6
        context()->KillInst(var);
165
79.0k
      } else if (CanReplaceVariable(var)) {
166
116
        worklist->push(var);
167
116
      }
168
79.0k
    }
169
134k
  }
170
171
15.9k
  return Status::SuccessWithChange;
172
15.9k
}
173
174
bool ScalarReplacementPass::ReplaceWholeDebugDeclare(
175
0
    Instruction* dbg_decl, const std::vector<Instruction*>& replacements) {
176
  // Insert Deref operation to the front of the operation list of |dbg_decl|.
177
0
  Instruction* dbg_expr = context()->get_def_use_mgr()->GetDef(
178
0
      dbg_decl->GetSingleWordOperand(kDebugValueOperandExpressionIndex));
179
0
  auto* deref_expr =
180
0
      context()->get_debug_info_mgr()->DerefDebugExpression(dbg_expr);
181
0
  if (deref_expr == nullptr) {
182
0
    return false;
183
0
  }
184
185
  // Add DebugValue instruction with Indexes operand and Deref operation.
186
0
  int32_t idx = 0;
187
0
  for (const auto* var : replacements) {
188
0
    Instruction* insert_before = var->NextNode();
189
0
    while (insert_before->opcode() == spv::Op::OpVariable)
190
0
      insert_before = insert_before->NextNode();
191
0
    assert(insert_before != nullptr && "unexpected end of list");
192
0
    Instruction* added_dbg_value =
193
0
        context()->get_debug_info_mgr()->AddDebugValueForDecl(
194
0
            dbg_decl, /*value_id=*/var->result_id(),
195
0
            /*insert_before=*/insert_before, /*line=*/dbg_decl);
196
197
0
    if (added_dbg_value == nullptr) return false;
198
0
    uint32_t idx_id = context()->get_constant_mgr()->GetSIntConstId(idx);
199
0
    if (idx_id == 0) {
200
0
      return false;
201
0
    }
202
0
    added_dbg_value->AddOperand({SPV_OPERAND_TYPE_ID, {idx_id}});
203
0
    added_dbg_value->SetOperand(kDebugValueOperandExpressionIndex,
204
0
                                {deref_expr->result_id()});
205
0
    if (context()->AreAnalysesValid(IRContext::Analysis::kAnalysisDefUse)) {
206
0
      context()->get_def_use_mgr()->AnalyzeInstUse(added_dbg_value);
207
0
    }
208
0
    ++idx;
209
0
  }
210
0
  return true;
211
0
}
212
213
bool ScalarReplacementPass::ReplaceWholeDebugValue(
214
0
    Instruction* dbg_value, const std::vector<Instruction*>& replacements) {
215
0
  int32_t idx = 0;
216
0
  BasicBlock* block = context()->get_instr_block(dbg_value);
217
0
  for (auto var : replacements) {
218
    // Clone the DebugValue.
219
0
    std::unique_ptr<Instruction> new_dbg_value(dbg_value->Clone(context()));
220
0
    if (!new_dbg_value) {
221
0
      return false;
222
0
    }
223
0
    uint32_t new_id = TakeNextId();
224
0
    if (new_id == 0) {
225
0
      return false;
226
0
    }
227
0
    new_dbg_value->SetResultId(new_id);
228
    // Update 'Value' operand to the |replacements|.
229
0
    new_dbg_value->SetOperand(kDebugValueOperandValueIndex, {var->result_id()});
230
    // Append 'Indexes' operand.
231
0
    uint32_t idx_id = context()->get_constant_mgr()->GetSIntConstId(idx);
232
0
    if (idx_id == 0) {
233
0
      return false;
234
0
    }
235
0
    new_dbg_value->AddOperand({SPV_OPERAND_TYPE_ID, {idx_id}});
236
    // Insert the new DebugValue to the basic block.
237
0
    auto* added_instr = dbg_value->InsertBefore(std::move(new_dbg_value));
238
0
    get_def_use_mgr()->AnalyzeInstDefUse(added_instr);
239
0
    context()->set_instr_block(added_instr, block);
240
0
    ++idx;
241
0
  }
242
0
  return true;
243
0
}
244
245
bool ScalarReplacementPass::ReplaceWholeLoad(
246
9.60k
    Instruction* load, const std::vector<Instruction*>& replacements) {
247
  // Replaces the load of the entire composite with a load from each replacement
248
  // variable followed by a composite construction.
249
9.60k
  BasicBlock* block = context()->get_instr_block(load);
250
9.60k
  std::vector<Instruction*> loads;
251
9.60k
  loads.reserve(replacements.size());
252
9.60k
  BasicBlock::iterator where(load);
253
100k
  for (auto var : replacements) {
254
    // Create a load of each replacement variable.
255
100k
    if (var->opcode() != spv::Op::OpVariable) {
256
9.79k
      loads.push_back(var);
257
9.79k
      continue;
258
9.79k
    }
259
260
90.4k
    Instruction* type = GetStorageType(var);
261
90.4k
    uint32_t loadId = TakeNextId();
262
90.4k
    if (loadId == 0) {
263
0
      return false;
264
0
    }
265
90.4k
    std::unique_ptr<Instruction> newLoad(
266
90.4k
        new Instruction(context(), spv::Op::OpLoad, type->result_id(), loadId,
267
90.4k
                        std::initializer_list<Operand>{
268
90.4k
                            {SPV_OPERAND_TYPE_ID, {var->result_id()}}}));
269
    // Copy memory access attributes which start at index 1. Index 0 is the
270
    // pointer to load.
271
90.4k
    for (uint32_t i = 1; i < load->NumInOperands(); ++i) {
272
0
      Operand copy(load->GetInOperand(i));
273
0
      newLoad->AddOperand(std::move(copy));
274
0
    }
275
90.4k
    where = where.InsertBefore(std::move(newLoad));
276
90.4k
    get_def_use_mgr()->AnalyzeInstDefUse(&*where);
277
90.4k
    context()->set_instr_block(&*where, block);
278
90.4k
    where->UpdateDebugInfoFrom(load);
279
90.4k
    loads.push_back(&*where);
280
90.4k
  }
281
282
  // Construct a new composite.
283
9.60k
  uint32_t compositeId = TakeNextId();
284
9.60k
  if (compositeId == 0) {
285
0
    return false;
286
0
  }
287
9.60k
  where = load;
288
9.60k
  std::unique_ptr<Instruction> compositeConstruct(
289
9.60k
      new Instruction(context(), spv::Op::OpCompositeConstruct, load->type_id(),
290
9.60k
                      compositeId, {}));
291
100k
  for (auto l : loads) {
292
100k
    Operand op(SPV_OPERAND_TYPE_ID,
293
100k
               std::initializer_list<uint32_t>{l->result_id()});
294
100k
    compositeConstruct->AddOperand(std::move(op));
295
100k
  }
296
9.60k
  where = where.InsertBefore(std::move(compositeConstruct));
297
9.60k
  get_def_use_mgr()->AnalyzeInstDefUse(&*where);
298
9.60k
  where->UpdateDebugInfoFrom(load);
299
9.60k
  context()->set_instr_block(&*where, block);
300
9.60k
  context()->ReplaceAllUsesWith(load->result_id(), compositeId);
301
9.60k
  return true;
302
9.60k
}
303
304
bool ScalarReplacementPass::ReplaceWholeStore(
305
12.3k
    Instruction* store, const std::vector<Instruction*>& replacements) {
306
  // Replaces a store to the whole composite with a series of extract and stores
307
  // to each element.
308
12.3k
  uint32_t storeInput = store->GetSingleWordInOperand(1u);
309
12.3k
  BasicBlock* block = context()->get_instr_block(store);
310
12.3k
  BasicBlock::iterator where(store);
311
12.3k
  uint32_t elementIndex = 0;
312
104k
  for (auto var : replacements) {
313
    // Create the extract.
314
104k
    if (var->opcode() != spv::Op::OpVariable) {
315
30.9k
      elementIndex++;
316
30.9k
      continue;
317
30.9k
    }
318
319
73.6k
    Instruction* type = GetStorageType(var);
320
73.6k
    uint32_t extractId = TakeNextId();
321
73.6k
    if (extractId == 0) {
322
0
      return false;
323
0
    }
324
73.6k
    std::unique_ptr<Instruction> extract(new Instruction(
325
73.6k
        context(), spv::Op::OpCompositeExtract, type->result_id(), extractId,
326
73.6k
        std::initializer_list<Operand>{
327
73.6k
            {SPV_OPERAND_TYPE_ID, {storeInput}},
328
73.6k
            {SPV_OPERAND_TYPE_LITERAL_INTEGER, {elementIndex++}}}));
329
73.6k
    auto iter = where.InsertBefore(std::move(extract));
330
73.6k
    iter->UpdateDebugInfoFrom(store);
331
73.6k
    get_def_use_mgr()->AnalyzeInstDefUse(&*iter);
332
73.6k
    context()->set_instr_block(&*iter, block);
333
334
    // Create the store.
335
73.6k
    std::unique_ptr<Instruction> newStore(
336
73.6k
        new Instruction(context(), spv::Op::OpStore, 0, 0,
337
73.6k
                        std::initializer_list<Operand>{
338
73.6k
                            {SPV_OPERAND_TYPE_ID, {var->result_id()}},
339
73.6k
                            {SPV_OPERAND_TYPE_ID, {extractId}}}));
340
    // Copy memory access attributes which start at index 2. Index 0 is the
341
    // pointer and index 1 is the data.
342
73.6k
    for (uint32_t i = 2; i < store->NumInOperands(); ++i) {
343
2
      Operand copy(store->GetInOperand(i));
344
2
      newStore->AddOperand(std::move(copy));
345
2
    }
346
73.6k
    iter = where.InsertBefore(std::move(newStore));
347
73.6k
    iter->UpdateDebugInfoFrom(store);
348
73.6k
    get_def_use_mgr()->AnalyzeInstDefUse(&*iter);
349
73.6k
    context()->set_instr_block(&*iter, block);
350
73.6k
  }
351
12.3k
  return true;
352
12.3k
}
353
354
bool ScalarReplacementPass::ReplaceAccessChain(
355
36.4k
    Instruction* chain, const std::vector<Instruction*>& replacements) {
356
  // Replaces the access chain with either another access chain (with one fewer
357
  // indexes) or a direct use of the replacement variable.
358
36.4k
  uint32_t indexId = chain->GetSingleWordInOperand(1u);
359
36.4k
  const Instruction* index = get_def_use_mgr()->GetDef(indexId);
360
36.4k
  int64_t indexValue = context()
361
36.4k
                           ->get_constant_mgr()
362
36.4k
                           ->GetConstantFromInst(index)
363
36.4k
                           ->GetSignExtendedValue();
364
36.4k
  if (indexValue < 0 ||
365
36.4k
      indexValue >= static_cast<int64_t>(replacements.size())) {
366
    // Out of bounds access, this is illegal IR.  Notice that OpAccessChain
367
    // indexing is 0-based, so we should also reject index == size-of-array.
368
0
    return false;
369
36.4k
  } else {
370
36.4k
    const Instruction* var = replacements[static_cast<size_t>(indexValue)];
371
36.4k
    if (chain->NumInOperands() > 2) {
372
      // Replace input access chain with another access chain.
373
2.74k
      BasicBlock::iterator chainIter(chain);
374
2.74k
      uint32_t replacementId = TakeNextId();
375
2.74k
      if (replacementId == 0) {
376
0
        return false;
377
0
      }
378
2.74k
      std::unique_ptr<Instruction> replacementChain(new Instruction(
379
2.74k
          context(), chain->opcode(), chain->type_id(), replacementId,
380
2.74k
          std::initializer_list<Operand>{
381
2.74k
              {SPV_OPERAND_TYPE_ID, {var->result_id()}}}));
382
      // Add the remaining indexes.
383
5.48k
      for (uint32_t i = 2; i < chain->NumInOperands(); ++i) {
384
2.74k
        Operand copy(chain->GetInOperand(i));
385
2.74k
        replacementChain->AddOperand(std::move(copy));
386
2.74k
      }
387
2.74k
      replacementChain->UpdateDebugInfoFrom(chain);
388
2.74k
      auto iter = chainIter.InsertBefore(std::move(replacementChain));
389
2.74k
      get_def_use_mgr()->AnalyzeInstDefUse(&*iter);
390
2.74k
      context()->set_instr_block(&*iter, context()->get_instr_block(chain));
391
2.74k
      context()->ReplaceAllUsesWith(chain->result_id(), replacementId);
392
33.7k
    } else {
393
      // Replace with a use of the variable.
394
33.7k
      context()->ReplaceAllUsesWith(chain->result_id(), var->result_id());
395
33.7k
    }
396
36.4k
  }
397
398
36.4k
  return true;
399
36.4k
}
400
401
bool ScalarReplacementPass::CreateReplacementVariables(
402
15.9k
    Instruction* inst, std::vector<Instruction*>* replacements) {
403
15.9k
  Instruction* type = GetStorageType(inst);
404
405
15.9k
  std::unique_ptr<std::unordered_set<int64_t>> components_used =
406
15.9k
      GetUsedComponents(inst);
407
408
15.9k
  uint32_t elem = 0;
409
15.9k
  switch (type->opcode()) {
410
14.0k
    case spv::Op::OpTypeStruct:
411
14.0k
      type->ForEachInOperand(
412
40.4k
          [this, inst, &elem, replacements, &components_used](uint32_t* id) {
413
40.4k
            if (!components_used || components_used->count(elem)) {
414
30.0k
              CreateVariable(*id, inst, elem, replacements);
415
30.0k
            } else {
416
10.3k
              replacements->push_back(GetUndef(*id));
417
10.3k
            }
418
40.4k
            elem++;
419
40.4k
          });
420
14.0k
      break;
421
1.93k
    case spv::Op::OpTypeArray:
422
95.6k
      for (uint32_t i = 0; i != GetArrayLength(type); ++i) {
423
93.7k
        if (!components_used || components_used->count(i)) {
424
48.9k
          CreateVariable(type->GetSingleWordInOperand(0u), inst, i,
425
48.9k
                         replacements);
426
48.9k
        } else {
427
44.8k
          uint32_t element_type_id = type->GetSingleWordInOperand(0);
428
44.8k
          replacements->push_back(GetUndef(element_type_id));
429
44.8k
        }
430
93.7k
      }
431
1.93k
      break;
432
433
0
    case spv::Op::OpTypeMatrix:
434
0
    case spv::Op::OpTypeVector:
435
0
      for (uint32_t i = 0; i != GetNumElements(type); ++i) {
436
0
        CreateVariable(type->GetSingleWordInOperand(0u), inst, i, replacements);
437
0
      }
438
0
      break;
439
440
0
    default:
441
0
      assert(false && "Unexpected type.");
442
0
      break;
443
15.9k
  }
444
445
15.9k
  TransferAnnotations(inst, replacements);
446
15.9k
  return std::find(replacements->begin(), replacements->end(), nullptr) ==
447
15.9k
         replacements->end();
448
15.9k
}
449
450
55.1k
Instruction* ScalarReplacementPass::GetUndef(uint32_t type_id) {
451
55.1k
  return get_def_use_mgr()->GetDef(Type2Undef(type_id));
452
55.1k
}
453
454
void ScalarReplacementPass::TransferAnnotations(
455
15.9k
    const Instruction* source, std::vector<Instruction*>* replacements) {
456
  // Only transfer invariant and restrict decorations on the variable. There are
457
  // no type or member decorations that are necessary to transfer.
458
15.9k
  for (auto inst :
459
15.9k
       get_decoration_mgr()->GetDecorationsFor(source->result_id(), false)) {
460
64
    assert(inst->opcode() == spv::Op::OpDecorate);
461
64
    auto decoration = spv::Decoration(inst->GetSingleWordInOperand(1u));
462
64
    if (decoration == spv::Decoration::Invariant ||
463
64
        decoration == spv::Decoration::Restrict) {
464
514
      for (auto var : *replacements) {
465
514
        if (var == nullptr) {
466
0
          continue;
467
0
        }
468
469
514
        std::unique_ptr<Instruction> annotation(new Instruction(
470
514
            context(), spv::Op::OpDecorate, 0, 0,
471
514
            std::initializer_list<Operand>{
472
514
                {SPV_OPERAND_TYPE_ID, {var->result_id()}},
473
514
                {SPV_OPERAND_TYPE_DECORATION, {uint32_t(decoration)}}}));
474
514
        for (uint32_t i = 2; i < inst->NumInOperands(); ++i) {
475
0
          Operand copy(inst->GetInOperand(i));
476
0
          annotation->AddOperand(std::move(copy));
477
0
        }
478
514
        context()->AddAnnotationInst(std::move(annotation));
479
514
        get_def_use_mgr()->AnalyzeInstUse(&*--context()->annotation_end());
480
514
      }
481
64
    }
482
64
  }
483
15.9k
}
484
485
void ScalarReplacementPass::CreateVariable(
486
    uint32_t type_id, Instruction* var_inst, uint32_t index,
487
79.0k
    std::vector<Instruction*>* replacements) {
488
79.0k
  uint32_t ptr_id = GetOrCreatePointerType(type_id);
489
79.0k
  uint32_t id = TakeNextId();
490
491
79.0k
  if (id == 0) {
492
0
    replacements->push_back(nullptr);
493
0
    return;
494
0
  }
495
496
79.0k
  std::unique_ptr<Instruction> variable(
497
79.0k
      new Instruction(context(), spv::Op::OpVariable, ptr_id, id,
498
79.0k
                      std::initializer_list<Operand>{
499
79.0k
                          {SPV_OPERAND_TYPE_STORAGE_CLASS,
500
79.0k
                           {uint32_t(spv::StorageClass::Function)}}}));
501
502
79.0k
  BasicBlock* block = context()->get_instr_block(var_inst);
503
79.0k
  block->begin().InsertBefore(std::move(variable));
504
79.0k
  Instruction* inst = &*block->begin();
505
506
  // If varInst was initialized, make sure to initialize its replacement.
507
79.0k
  if (!GetOrCreateInitialValue(var_inst, index, inst)) {
508
0
    replacements->push_back(nullptr);
509
0
    return;
510
0
  }
511
79.0k
  get_def_use_mgr()->AnalyzeInstDefUse(inst);
512
79.0k
  context()->set_instr_block(inst, block);
513
514
79.0k
  CopyDecorationsToVariable(var_inst, inst, index);
515
79.0k
  inst->UpdateDebugInfoFrom(var_inst);
516
517
79.0k
  replacements->push_back(inst);
518
79.0k
}
519
520
79.0k
uint32_t ScalarReplacementPass::GetOrCreatePointerType(uint32_t id) {
521
79.0k
  auto iter = pointee_to_pointer_.find(id);
522
79.0k
  if (iter != pointee_to_pointer_.end()) return iter->second;
523
524
2.00k
  analysis::TypeManager* type_mgr = context()->get_type_mgr();
525
2.00k
  uint32_t ptr_type_id =
526
2.00k
      type_mgr->FindPointerToType(id, spv::StorageClass::Function);
527
2.00k
  pointee_to_pointer_[id] = ptr_type_id;
528
2.00k
  return ptr_type_id;
529
79.0k
}
530
531
bool ScalarReplacementPass::GetOrCreateInitialValue(Instruction* source,
532
                                                    uint32_t index,
533
79.0k
                                                    Instruction* newVar) {
534
79.0k
  assert(source->opcode() == spv::Op::OpVariable);
535
79.0k
  if (source->NumInOperands() < 2) return true;
536
537
0
  uint32_t initId = source->GetSingleWordInOperand(1u);
538
0
  uint32_t storageId = GetStorageType(newVar)->result_id();
539
0
  Instruction* init = get_def_use_mgr()->GetDef(initId);
540
0
  uint32_t newInitId = 0;
541
  // TODO(dnovillo): Refactor this with constant propagation.
542
0
  if (init->opcode() == spv::Op::OpConstantNull) {
543
    // Initialize to appropriate NULL.
544
0
    auto iter = type_to_null_.find(storageId);
545
0
    if (iter == type_to_null_.end()) {
546
0
      newInitId = TakeNextId();
547
0
      if (newInitId == 0) return false;
548
0
      type_to_null_[storageId] = newInitId;
549
0
      context()->AddGlobalValue(
550
0
          MakeUnique<Instruction>(context(), spv::Op::OpConstantNull, storageId,
551
0
                                  newInitId, std::initializer_list<Operand>{}));
552
0
      Instruction* newNull = &*--context()->types_values_end();
553
0
      get_def_use_mgr()->AnalyzeInstDefUse(newNull);
554
0
    } else {
555
0
      newInitId = iter->second;
556
0
    }
557
0
  } else if (IsSpecConstantInst(init->opcode())) {
558
    // Create a new constant extract.
559
0
    newInitId = TakeNextId();
560
0
    if (newInitId == 0) return false;
561
0
    context()->AddGlobalValue(MakeUnique<Instruction>(
562
0
        context(), spv::Op::OpSpecConstantOp, storageId, newInitId,
563
0
        std::initializer_list<Operand>{
564
0
            {SPV_OPERAND_TYPE_SPEC_CONSTANT_OP_NUMBER,
565
0
             {uint32_t(spv::Op::OpCompositeExtract)}},
566
0
            {SPV_OPERAND_TYPE_ID, {init->result_id()}},
567
0
            {SPV_OPERAND_TYPE_LITERAL_INTEGER, {index}}}));
568
0
    Instruction* newSpecConst = &*--context()->types_values_end();
569
0
    get_def_use_mgr()->AnalyzeInstDefUse(newSpecConst);
570
0
  } else if (init->opcode() == spv::Op::OpConstantComposite) {
571
    // Get the appropriate index constant.
572
0
    newInitId = init->GetSingleWordInOperand(index);
573
0
    Instruction* element = get_def_use_mgr()->GetDef(newInitId);
574
0
    if (element->opcode() == spv::Op::OpUndef) {
575
      // Undef is not a valid initializer for a variable.
576
0
      newInitId = 0;
577
0
    }
578
0
  } else {
579
0
    assert(false);
580
0
  }
581
582
0
  if (newInitId != 0) {
583
0
    newVar->AddOperand({SPV_OPERAND_TYPE_ID, {newInitId}});
584
0
  }
585
0
  return true;
586
0
}
587
588
uint64_t ScalarReplacementPass::GetArrayLength(
589
112k
    const Instruction* arrayType) const {
590
112k
  assert(arrayType->opcode() == spv::Op::OpTypeArray);
591
112k
  const Instruction* length =
592
112k
      get_def_use_mgr()->GetDef(arrayType->GetSingleWordInOperand(1u));
593
112k
  return context()
594
112k
      ->get_constant_mgr()
595
112k
      ->GetConstantFromInst(length)
596
112k
      ->GetZeroExtendedValue();
597
112k
}
598
599
0
uint64_t ScalarReplacementPass::GetNumElements(const Instruction* type) const {
600
0
  assert(type->opcode() == spv::Op::OpTypeVector ||
601
0
         type->opcode() == spv::Op::OpTypeMatrix);
602
0
  const Operand& op = type->GetInOperand(1u);
603
0
  assert(op.words.size() <= 2);
604
0
  uint64_t len = 0;
605
0
  for (size_t i = 0; i != op.words.size(); ++i) {
606
0
    len |= (static_cast<uint64_t>(op.words[i]) << (32ull * i));
607
0
  }
608
0
  return len;
609
0
}
610
611
10.7k
bool ScalarReplacementPass::IsSpecConstant(uint32_t id) const {
612
10.7k
  const Instruction* inst = get_def_use_mgr()->GetDef(id);
613
10.7k
  assert(inst);
614
10.7k
  return spvOpcodeIsSpecConstant(inst->opcode());
615
10.7k
}
616
617
Instruction* ScalarReplacementPass::GetStorageType(
618
589k
    const Instruction* inst) const {
619
589k
  assert(inst->opcode() == spv::Op::OpVariable);
620
621
589k
  uint32_t ptrTypeId = inst->type_id();
622
589k
  uint32_t typeId =
623
589k
      get_def_use_mgr()->GetDef(ptrTypeId)->GetSingleWordInOperand(1u);
624
589k
  return get_def_use_mgr()->GetDef(typeId);
625
589k
}
626
627
bool ScalarReplacementPass::CanReplaceVariable(
628
305k
    const Instruction* varInst) const {
629
305k
  assert(varInst->opcode() == spv::Op::OpVariable);
630
631
  // Can only replace function scope variables.
632
305k
  if (spv::StorageClass(varInst->GetSingleWordInOperand(0u)) !=
633
305k
      spv::StorageClass::Function) {
634
0
    return false;
635
0
  }
636
637
305k
  if (!CheckTypeAnnotations(get_def_use_mgr()->GetDef(varInst->type_id()))) {
638
343
    return false;
639
343
  }
640
641
304k
  const Instruction* typeInst = GetStorageType(varInst);
642
304k
  if (!CheckType(typeInst)) {
643
276k
    return false;
644
276k
  }
645
646
28.1k
  if (!CheckAnnotations(varInst)) {
647
1.99k
    return false;
648
1.99k
  }
649
650
26.1k
  if (!CheckUses(varInst)) {
651
10.1k
    return false;
652
10.1k
  }
653
654
15.9k
  return true;
655
26.1k
}
656
657
304k
bool ScalarReplacementPass::CheckType(const Instruction* typeInst) const {
658
304k
  if (!CheckTypeAnnotations(typeInst)) {
659
1.33k
    return false;
660
1.33k
  }
661
662
303k
  switch (typeInst->opcode()) {
663
20.1k
    case spv::Op::OpTypeStruct:
664
      // Don't bother with empty structs or very large structs.
665
20.1k
      if (typeInst->NumInOperands() == 0 ||
666
20.1k
          IsLargerThanSizeLimit(typeInst->NumInOperands())) {
667
0
        return false;
668
0
      }
669
20.1k
      return true;
670
10.7k
    case spv::Op::OpTypeArray:
671
10.7k
      if (IsSpecConstant(typeInst->GetSingleWordInOperand(1u))) {
672
229
        return false;
673
229
      }
674
10.5k
      if (IsLargerThanSizeLimit(GetArrayLength(typeInst))) {
675
2.53k
        return false;
676
2.53k
      }
677
7.98k
      return true;
678
      // TODO(alanbaker): Develop some heuristics for when this should be
679
      // re-enabled.
680
      //// Specifically including matrix and vector in an attempt to reduce the
681
      //// number of vector registers required.
682
      // case spv::Op::OpTypeMatrix:
683
      // case spv::Op::OpTypeVector:
684
      //  if (IsLargerThanSizeLimit(GetNumElements(typeInst))) return false;
685
      //  return true;
686
687
0
    case spv::Op::OpTypeRuntimeArray:
688
272k
    default:
689
272k
      return false;
690
303k
  }
691
303k
}
692
693
bool ScalarReplacementPass::CheckTypeAnnotations(
694
609k
    const Instruction* typeInst) const {
695
609k
  for (auto inst :
696
609k
       get_decoration_mgr()->GetDecorationsFor(typeInst->result_id(), false)) {
697
13.7k
    uint32_t decoration;
698
13.7k
    if (inst->opcode() == spv::Op::OpDecorate ||
699
12.7k
        inst->opcode() == spv::Op::OpDecorateId) {
700
975
      decoration = inst->GetSingleWordInOperand(1u);
701
12.7k
    } else {
702
12.7k
      assert(inst->opcode() == spv::Op::OpMemberDecorate);
703
12.7k
      decoration = inst->GetSingleWordInOperand(2u);
704
12.7k
    }
705
706
13.7k
    switch (spv::Decoration(decoration)) {
707
451
      case spv::Decoration::RowMajor:
708
573
      case spv::Decoration::ColMajor:
709
573
      case spv::Decoration::ArrayStride:
710
573
      case spv::Decoration::MatrixStride:
711
573
      case spv::Decoration::CPacked:
712
1.19k
      case spv::Decoration::Invariant:
713
1.68k
      case spv::Decoration::Restrict:
714
1.70k
      case spv::Decoration::Offset:
715
1.70k
      case spv::Decoration::Alignment:
716
1.70k
      case spv::Decoration::AlignmentId:
717
1.70k
      case spv::Decoration::MaxByteOffset:
718
12.0k
      case spv::Decoration::RelaxedPrecision:
719
12.0k
      case spv::Decoration::AliasedPointer:
720
12.0k
      case spv::Decoration::RestrictPointer:
721
12.0k
        break;
722
1.67k
      default:
723
1.67k
        return false;
724
13.7k
    }
725
13.7k
  }
726
727
608k
  return true;
728
609k
}
729
730
28.1k
bool ScalarReplacementPass::CheckAnnotations(const Instruction* varInst) const {
731
28.1k
  for (auto inst :
732
28.1k
       get_decoration_mgr()->GetDecorationsFor(varInst->result_id(), false)) {
733
2.14k
    assert(inst->opcode() == spv::Op::OpDecorate);
734
2.14k
    auto decoration = spv::Decoration(inst->GetSingleWordInOperand(1u));
735
2.14k
    switch (decoration) {
736
37
      case spv::Decoration::Invariant:
737
147
      case spv::Decoration::Restrict:
738
147
      case spv::Decoration::Alignment:
739
147
      case spv::Decoration::AlignmentId:
740
147
      case spv::Decoration::MaxByteOffset:
741
147
      case spv::Decoration::AliasedPointer:
742
147
      case spv::Decoration::RestrictPointer:
743
147
        break;
744
1.99k
      default:
745
1.99k
        return false;
746
2.14k
    }
747
2.14k
  }
748
749
26.1k
  return true;
750
28.1k
}
751
752
26.1k
bool ScalarReplacementPass::CheckUses(const Instruction* inst) const {
753
26.1k
  VariableStats stats = {0, 0};
754
26.1k
  bool ok = CheckUses(inst, &stats);
755
756
  // TODO(alanbaker/greg-lunarg): Add some meaningful heuristics about when
757
  // SRoA is costly, such as when the structure has many (unaccessed?)
758
  // members.
759
760
26.1k
  return ok;
761
26.1k
}
762
763
bool ScalarReplacementPass::CheckUses(const Instruction* inst,
764
26.1k
                                      VariableStats* stats) const {
765
26.1k
  uint64_t max_legal_index = GetMaxLegalIndex(inst);
766
767
26.1k
  bool ok = true;
768
26.1k
  get_def_use_mgr()->ForEachUse(inst, [this, max_legal_index, stats, &ok](
769
26.1k
                                          const Instruction* user,
770
149k
                                          uint32_t index) {
771
149k
    if (user->GetCommonDebugOpcode() == CommonDebugInfoDebugDeclare ||
772
149k
        user->GetCommonDebugOpcode() == CommonDebugInfoDebugValue) {
773
      // TODO: include num_partial_accesses if it uses Fragment operation or
774
      // DebugValue has Indexes operand.
775
0
      stats->num_full_accesses++;
776
0
      return;
777
0
    }
778
779
    // Annotations are check as a group separately.
780
149k
    if (!IsAnnotationInst(user->opcode())) {
781
149k
      switch (user->opcode()) {
782
105k
        case spv::Op::OpAccessChain:
783
106k
        case spv::Op::OpInBoundsAccessChain:
784
106k
          if (index == 2u && user->NumInOperands() > 1) {
785
106k
            uint32_t id = user->GetSingleWordInOperand(1u);
786
106k
            const Instruction* opInst = get_def_use_mgr()->GetDef(id);
787
106k
            const auto* constant =
788
106k
                context()->get_constant_mgr()->GetConstantFromInst(opInst);
789
106k
            if (!constant) {
790
47.9k
              ok = false;
791
58.0k
            } else if (constant->GetZeroExtendedValue() >= max_legal_index) {
792
3.14k
              ok = false;
793
54.9k
            } else {
794
54.9k
              if (!CheckUsesRelaxed(user)) ok = false;
795
54.9k
            }
796
106k
            stats->num_partial_accesses++;
797
106k
          } else {
798
0
            ok = false;
799
0
          }
800
106k
          break;
801
16.3k
        case spv::Op::OpLoad:
802
16.3k
          if (!CheckLoad(user, index)) ok = false;
803
16.3k
          stats->num_full_accesses++;
804
16.3k
          break;
805
19.6k
        case spv::Op::OpStore:
806
19.6k
          if (!CheckStore(user, index)) ok = false;
807
19.6k
          stats->num_full_accesses++;
808
19.6k
          break;
809
2.40k
        case spv::Op::OpName:
810
2.40k
        case spv::Op::OpMemberName:
811
2.40k
          break;
812
4.63k
        default:
813
4.63k
          ok = false;
814
4.63k
          break;
815
149k
      }
816
149k
    }
817
149k
  });
818
819
26.1k
  return ok;
820
26.1k
}
821
822
54.9k
bool ScalarReplacementPass::CheckUsesRelaxed(const Instruction* inst) const {
823
54.9k
  bool ok = true;
824
54.9k
  get_def_use_mgr()->ForEachUse(
825
59.5k
      inst, [this, &ok](const Instruction* user, uint32_t index) {
826
59.5k
        switch (user->opcode()) {
827
0
          case spv::Op::OpAccessChain:
828
0
          case spv::Op::OpInBoundsAccessChain:
829
0
            if (index != 2u) {
830
0
              ok = false;
831
0
            } else {
832
0
              if (!CheckUsesRelaxed(user)) ok = false;
833
0
            }
834
0
            break;
835
21.0k
          case spv::Op::OpLoad:
836
21.0k
            if (!CheckLoad(user, index)) ok = false;
837
21.0k
            break;
838
36.7k
          case spv::Op::OpStore:
839
36.7k
            if (!CheckStore(user, index)) ok = false;
840
36.7k
            break;
841
0
          case spv::Op::OpImageTexelPointer:
842
0
            if (!CheckImageTexelPointer(index)) ok = false;
843
0
            break;
844
0
          case spv::Op::OpExtInst:
845
0
            if (user->GetCommonDebugOpcode() != CommonDebugInfoDebugDeclare ||
846
0
                !CheckDebugDeclare(index))
847
0
              ok = false;
848
0
            break;
849
1.63k
          default:
850
1.63k
            ok = false;
851
1.63k
            break;
852
59.5k
        }
853
59.5k
      });
854
855
54.9k
  return ok;
856
54.9k
}
857
858
0
bool ScalarReplacementPass::CheckImageTexelPointer(uint32_t index) const {
859
0
  return index == 2u;
860
0
}
861
862
bool ScalarReplacementPass::CheckLoad(const Instruction* inst,
863
37.4k
                                      uint32_t index) const {
864
37.4k
  if (index != 2u) return false;
865
37.4k
  if (inst->NumInOperands() >= 2 &&
866
444
      inst->GetSingleWordInOperand(1u) &
867
444
          uint32_t(spv::MemoryAccessMask::Volatile))
868
415
    return false;
869
37.0k
  return true;
870
37.4k
}
871
872
bool ScalarReplacementPass::CheckStore(const Instruction* inst,
873
56.4k
                                       uint32_t index) const {
874
56.4k
  if (index != 0u) return false;
875
56.4k
  if (inst->NumInOperands() >= 3 &&
876
559
      inst->GetSingleWordInOperand(2u) &
877
559
          uint32_t(spv::MemoryAccessMask::Volatile))
878
391
    return false;
879
56.0k
  return true;
880
56.4k
}
881
882
0
bool ScalarReplacementPass::CheckDebugDeclare(uint32_t index) const {
883
0
  if (index != kDebugDeclareOperandVariableIndex) return false;
884
0
  return true;
885
0
}
886
887
30.6k
bool ScalarReplacementPass::IsLargerThanSizeLimit(uint64_t length) const {
888
30.6k
  if (max_num_elements_ == 0) {
889
0
    return false;
890
0
  }
891
30.6k
  return length > max_num_elements_;
892
30.6k
}
893
894
std::unique_ptr<std::unordered_set<int64_t>>
895
15.9k
ScalarReplacementPass::GetUsedComponents(Instruction* inst) {
896
15.9k
  std::unique_ptr<std::unordered_set<int64_t>> result(
897
15.9k
      new std::unordered_set<int64_t>());
898
899
15.9k
  analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr();
900
901
15.9k
  def_use_mgr->WhileEachUser(inst, [&result, def_use_mgr,
902
35.3k
                                    this](Instruction* use) {
903
35.3k
    switch (use->opcode()) {
904
9.02k
      case spv::Op::OpLoad: {
905
        // Look for extract from the load.
906
9.02k
        std::vector<uint32_t> t;
907
21.7k
        if (def_use_mgr->WhileEachUser(use, [&t](Instruction* use2) {
908
21.7k
              if (use2->opcode() != spv::Op::OpCompositeExtract ||
909
13.4k
                  use2->NumInOperands() <= 1) {
910
8.35k
                return false;
911
8.35k
              }
912
13.4k
              t.push_back(use2->GetSingleWordInOperand(1));
913
13.4k
              return true;
914
21.7k
            })) {
915
673
          result->insert(t.begin(), t.end());
916
673
          return true;
917
8.35k
        } else {
918
8.35k
          result.reset(nullptr);
919
8.35k
          return false;
920
8.35k
        }
921
9.02k
      }
922
1.21k
      case spv::Op::OpName:
923
1.21k
      case spv::Op::OpMemberName:
924
13.1k
      case spv::Op::OpStore:
925
        // No components are used.
926
13.1k
        return true;
927
13.0k
      case spv::Op::OpAccessChain:
928
13.1k
      case spv::Op::OpInBoundsAccessChain: {
929
        // Add the first index it if is a constant.
930
        // TODO: Could be improved by checking if the address is used in a load.
931
13.1k
        analysis::ConstantManager* const_mgr = context()->get_constant_mgr();
932
13.1k
        uint32_t index_id = use->GetSingleWordInOperand(1);
933
13.1k
        const analysis::Constant* index_const =
934
13.1k
            const_mgr->FindDeclaredConstant(index_id);
935
13.1k
        if (index_const) {
936
13.1k
          result->insert(index_const->GetSignExtendedValue());
937
13.1k
          return true;
938
13.1k
        } else {
939
          // Could be any element.  Assuming all are used.
940
0
          result.reset(nullptr);
941
0
          return false;
942
0
        }
943
13.1k
      }
944
61
      default:
945
        // We do not know what is happening.  Have to assume the worst.
946
61
        result.reset(nullptr);
947
61
        return false;
948
35.3k
    }
949
35.3k
  });
950
951
15.9k
  return result;
952
15.9k
}
953
954
uint64_t ScalarReplacementPass::GetMaxLegalIndex(
955
26.1k
    const Instruction* var_inst) const {
956
26.1k
  assert(var_inst->opcode() == spv::Op::OpVariable &&
957
26.1k
         "|var_inst| must be a variable instruction.");
958
26.1k
  Instruction* type = GetStorageType(var_inst);
959
26.1k
  switch (type->opcode()) {
960
19.3k
    case spv::Op::OpTypeStruct:
961
19.3k
      return type->NumInOperands();
962
6.77k
    case spv::Op::OpTypeArray:
963
6.77k
      return GetArrayLength(type);
964
0
    case spv::Op::OpTypeMatrix:
965
0
    case spv::Op::OpTypeVector:
966
0
      return GetNumElements(type);
967
0
    default:
968
0
      return 0;
969
26.1k
  }
970
0
  return 0;
971
26.1k
}
972
973
void ScalarReplacementPass::CopyDecorationsToVariable(Instruction* from,
974
                                                      Instruction* to,
975
79.0k
                                                      uint32_t member_index) {
976
79.0k
  CopyPointerDecorationsToVariable(from, to);
977
79.0k
  CopyNecessaryMemberDecorationsToVariable(from, to, member_index);
978
79.0k
}
979
980
void ScalarReplacementPass::CopyPointerDecorationsToVariable(Instruction* from,
981
79.0k
                                                             Instruction* to) {
982
  // The RestrictPointer and AliasedPointer decorations are copied to all
983
  // members even if the new variable does not contain a pointer. It does
984
  // not hurt to do so.
985
79.0k
  for (auto dec_inst :
986
79.0k
       get_decoration_mgr()->GetDecorationsFor(from->result_id(), false)) {
987
514
    uint32_t decoration;
988
514
    decoration = dec_inst->GetSingleWordInOperand(1u);
989
514
    switch (spv::Decoration(decoration)) {
990
0
      case spv::Decoration::AliasedPointer:
991
0
      case spv::Decoration::RestrictPointer: {
992
0
        std::unique_ptr<Instruction> new_dec_inst(dec_inst->Clone(context()));
993
0
        new_dec_inst->SetInOperand(0, {to->result_id()});
994
0
        context()->AddAnnotationInst(std::move(new_dec_inst));
995
0
      } break;
996
514
      default:
997
514
        break;
998
514
    }
999
514
  }
1000
79.0k
}
1001
1002
void ScalarReplacementPass::CopyNecessaryMemberDecorationsToVariable(
1003
79.0k
    Instruction* from, Instruction* to, uint32_t member_index) {
1004
79.0k
  Instruction* type_inst = GetStorageType(from);
1005
79.0k
  for (auto dec_inst :
1006
79.0k
       get_decoration_mgr()->GetDecorationsFor(type_inst->result_id(), false)) {
1007
20.7k
    uint32_t decoration;
1008
20.7k
    if (dec_inst->opcode() == spv::Op::OpMemberDecorate) {
1009
20.7k
      if (dec_inst->GetSingleWordInOperand(1) != member_index) {
1010
13.4k
        continue;
1011
13.4k
      }
1012
1013
7.29k
      decoration = dec_inst->GetSingleWordInOperand(2u);
1014
7.29k
      switch (spv::Decoration(decoration)) {
1015
0
        case spv::Decoration::ArrayStride:
1016
0
        case spv::Decoration::Alignment:
1017
0
        case spv::Decoration::AlignmentId:
1018
0
        case spv::Decoration::MaxByteOffset:
1019
0
        case spv::Decoration::MaxByteOffsetId:
1020
6.47k
        case spv::Decoration::RelaxedPrecision: {
1021
6.47k
          std::unique_ptr<Instruction> new_dec_inst(
1022
6.47k
              new Instruction(context(), spv::Op::OpDecorate, 0, 0, {}));
1023
6.47k
          new_dec_inst->AddOperand(
1024
6.47k
              Operand(SPV_OPERAND_TYPE_ID, {to->result_id()}));
1025
12.9k
          for (uint32_t i = 2; i < dec_inst->NumInOperandWords(); ++i) {
1026
6.47k
            new_dec_inst->AddOperand(Operand(dec_inst->GetInOperand(i)));
1027
6.47k
          }
1028
6.47k
          context()->AddAnnotationInst(std::move(new_dec_inst));
1029
6.47k
        } break;
1030
826
        default:
1031
826
          break;
1032
7.29k
      }
1033
7.29k
    }
1034
20.7k
  }
1035
79.0k
}
1036
1037
}  // namespace opt
1038
}  // namespace spvtools