Coverage Report

Created: 2026-08-14 06:34

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/spirv-tools/source/opt/inline_pass.cpp
Line
Count
Source
1
// Copyright (c) 2017 The Khronos Group Inc.
2
// Copyright (c) 2017 Valve Corporation
3
// Copyright (c) 2017 LunarG Inc.
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/inline_pass.h"
18
19
#include <unordered_set>
20
#include <utility>
21
22
#include "source/cfa.h"
23
#include "source/opt/reflect.h"
24
#include "source/util/make_unique.h"
25
26
namespace spvtools {
27
namespace opt {
28
namespace {
29
// Indices of operands in SPIR-V instructions
30
constexpr int kSpvFunctionCallFunctionId = 2;
31
constexpr int kSpvFunctionCallArgumentId = 3;
32
constexpr int kSpvReturnValueId = 0;
33
constexpr int kSpvDebugDeclareVarInIdx = 3;
34
constexpr int kSpvAccessChainBaseInIdx = 0;
35
}  // namespace
36
37
uint32_t InlinePass::AddPointerToType(uint32_t type_id,
38
0
                                      spv::StorageClass storage_class) {
39
0
  uint32_t resultId = context()->TakeNextId();
40
0
  if (resultId == 0) {
41
0
    return resultId;
42
0
  }
43
44
0
  std::unique_ptr<Instruction> type_inst(
45
0
      new Instruction(context(), spv::Op::OpTypePointer, 0, resultId,
46
0
                      {{spv_operand_type_t::SPV_OPERAND_TYPE_STORAGE_CLASS,
47
0
                        {uint32_t(storage_class)}},
48
0
                       {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {type_id}}}));
49
0
  context()->AddType(std::move(type_inst));
50
0
  analysis::Type* pointeeTy;
51
0
  std::unique_ptr<analysis::Pointer> pointerTy;
52
0
  std::tie(pointeeTy, pointerTy) =
53
0
      context()->get_type_mgr()->GetTypeAndPointerType(
54
0
          type_id, spv::StorageClass::Function);
55
0
  context()->get_type_mgr()->RegisterType(resultId, *pointerTy);
56
0
  return resultId;
57
0
}
58
59
void InlinePass::AddBranch(uint32_t label_id,
60
2.71k
                           std::unique_ptr<BasicBlock>* block_ptr) {
61
2.71k
  std::unique_ptr<Instruction> newBranch(
62
2.71k
      new Instruction(context(), spv::Op::OpBranch, 0, 0,
63
2.71k
                      {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {label_id}}}));
64
2.71k
  (*block_ptr)->AddInstruction(std::move(newBranch));
65
2.71k
}
66
67
void InlinePass::AddBranchCond(uint32_t cond_id, uint32_t true_id,
68
                               uint32_t false_id,
69
0
                               std::unique_ptr<BasicBlock>* block_ptr) {
70
0
  std::unique_ptr<Instruction> newBranch(
71
0
      new Instruction(context(), spv::Op::OpBranchConditional, 0, 0,
72
0
                      {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {cond_id}},
73
0
                       {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {true_id}},
74
0
                       {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {false_id}}}));
75
0
  (*block_ptr)->AddInstruction(std::move(newBranch));
76
0
}
77
78
void InlinePass::AddLoopMerge(uint32_t merge_id, uint32_t continue_id,
79
0
                              std::unique_ptr<BasicBlock>* block_ptr) {
80
0
  std::unique_ptr<Instruction> newLoopMerge(new Instruction(
81
0
      context(), spv::Op::OpLoopMerge, 0, 0,
82
0
      {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {merge_id}},
83
0
       {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {continue_id}},
84
0
       {spv_operand_type_t::SPV_OPERAND_TYPE_LOOP_CONTROL, {0}}}));
85
0
  (*block_ptr)->AddInstruction(std::move(newLoopMerge));
86
0
}
87
88
void InlinePass::AddStore(uint32_t ptr_id, uint32_t val_id,
89
                          std::unique_ptr<BasicBlock>* block_ptr,
90
                          const Instruction* line_inst,
91
36.3k
                          const DebugScope& dbg_scope) {
92
36.3k
  std::unique_ptr<Instruction> newStore(
93
36.3k
      new Instruction(context(), spv::Op::OpStore, 0, 0,
94
36.3k
                      {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {ptr_id}},
95
36.3k
                       {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {val_id}}}));
96
36.3k
  if (line_inst != nullptr) {
97
1
    newStore->AddDebugLine(line_inst);
98
1
  }
99
36.3k
  newStore->SetDebugScope(dbg_scope);
100
36.3k
  (*block_ptr)->AddInstruction(std::move(newStore));
101
36.3k
}
102
103
void InlinePass::AddLoad(uint32_t type_id, uint32_t resultId, uint32_t ptr_id,
104
                         std::unique_ptr<BasicBlock>* block_ptr,
105
                         const Instruction* line_inst,
106
20.9k
                         const DebugScope& dbg_scope) {
107
20.9k
  std::unique_ptr<Instruction> newLoad(
108
20.9k
      new Instruction(context(), spv::Op::OpLoad, type_id, resultId,
109
20.9k
                      {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {ptr_id}}}));
110
20.9k
  if (line_inst != nullptr) {
111
0
    newLoad->AddDebugLine(line_inst);
112
0
  }
113
20.9k
  newLoad->SetDebugScope(dbg_scope);
114
20.9k
  (*block_ptr)->AddInstruction(std::move(newLoad));
115
20.9k
}
116
117
283k
std::unique_ptr<Instruction> InlinePass::NewLabel(uint32_t label_id) {
118
283k
  std::unique_ptr<Instruction> newLabel(
119
283k
      new Instruction(context(), spv::Op::OpLabel, 0, label_id, {}));
120
283k
  return newLabel;
121
283k
}
122
123
0
uint32_t InlinePass::GetFalseId() {
124
0
  if (false_id_ != 0) return false_id_;
125
0
  false_id_ = get_module()->GetGlobalValue(spv::Op::OpConstantFalse);
126
0
  if (false_id_ != 0) return false_id_;
127
0
  uint32_t boolId = get_module()->GetGlobalValue(spv::Op::OpTypeBool);
128
0
  if (boolId == 0) {
129
0
    boolId = context()->TakeNextId();
130
0
    if (boolId == 0) {
131
0
      return 0;
132
0
    }
133
0
    get_module()->AddGlobalValue(spv::Op::OpTypeBool, boolId, 0);
134
0
  }
135
0
  false_id_ = context()->TakeNextId();
136
0
  if (false_id_ == 0) {
137
0
    return 0;
138
0
  }
139
0
  get_module()->AddGlobalValue(spv::Op::OpConstantFalse, false_id_, boolId);
140
0
  return false_id_;
141
0
}
142
143
void InlinePass::MapParams(
144
    Function* calleeFn, BasicBlock::iterator call_inst_itr,
145
43.1k
    std::unordered_map<uint32_t, uint32_t>* callee2caller) {
146
43.1k
  int param_idx = 0;
147
43.1k
  calleeFn->ForEachParam(
148
60.9k
      [&call_inst_itr, &param_idx, &callee2caller](const Instruction* cpi) {
149
60.9k
        const uint32_t pid = cpi->result_id();
150
60.9k
        (*callee2caller)[pid] = call_inst_itr->GetSingleWordOperand(
151
60.9k
            kSpvFunctionCallArgumentId + param_idx);
152
60.9k
        ++param_idx;
153
60.9k
      });
154
43.1k
}
155
156
bool InlinePass::CloneAndMapLocals(
157
    Function* calleeFn, std::vector<std::unique_ptr<Instruction>>* new_vars,
158
    std::unordered_map<uint32_t, uint32_t>* callee2caller,
159
43.1k
    analysis::DebugInlinedAtContext* inlined_at_ctx) {
160
43.1k
  auto callee_block_itr = calleeFn->begin();
161
43.1k
  auto callee_var_itr = callee_block_itr->begin();
162
169k
  while (callee_var_itr->opcode() == spv::Op::OpVariable ||
163
43.1k
         callee_var_itr->GetCommonDebugOpcode() ==
164
126k
             CommonDebugInfoDebugDeclare) {
165
126k
    if (callee_var_itr->opcode() != spv::Op::OpVariable) {
166
0
      ++callee_var_itr;
167
0
      continue;
168
0
    }
169
170
126k
    std::unique_ptr<Instruction> var_inst(callee_var_itr->Clone(context()));
171
126k
    uint32_t newId = context()->TakeNextId();
172
126k
    if (newId == 0) {
173
0
      return false;
174
0
    }
175
126k
    get_decoration_mgr()->CloneDecorations(callee_var_itr->result_id(), newId);
176
126k
    var_inst->SetResultId(newId);
177
126k
    var_inst->UpdateDebugInlinedAt(
178
126k
        context()->get_debug_info_mgr()->BuildDebugInlinedAtChain(
179
126k
            callee_var_itr->GetDebugInlinedAt(), inlined_at_ctx));
180
126k
    (*callee2caller)[callee_var_itr->result_id()] = newId;
181
126k
    new_vars->push_back(std::move(var_inst));
182
126k
    ++callee_var_itr;
183
126k
  }
184
43.1k
  return true;
185
43.1k
}
186
187
uint32_t InlinePass::CreateReturnVar(
188
20.9k
    Function* calleeFn, std::vector<std::unique_ptr<Instruction>>* new_vars) {
189
20.9k
  uint32_t returnVarId = 0;
190
20.9k
  const uint32_t calleeTypeId = calleeFn->type_id();
191
20.9k
  analysis::TypeManager* type_mgr = context()->get_type_mgr();
192
20.9k
  assert(type_mgr->GetType(calleeTypeId)->AsVoid() == nullptr &&
193
20.9k
         "Cannot create a return variable of type void.");
194
  // Find or create ptr to callee return type.
195
20.9k
  uint32_t returnVarTypeId =
196
20.9k
      type_mgr->FindPointerToType(calleeTypeId, spv::StorageClass::Function);
197
198
20.9k
  if (returnVarTypeId == 0) {
199
0
    returnVarTypeId =
200
0
        AddPointerToType(calleeTypeId, spv::StorageClass::Function);
201
0
    if (returnVarTypeId == 0) {
202
0
      return 0;
203
0
    }
204
0
  }
205
206
  // Add return var to new function scope variables.
207
20.9k
  returnVarId = context()->TakeNextId();
208
20.9k
  if (returnVarId == 0) {
209
0
    return 0;
210
0
  }
211
212
20.9k
  std::unique_ptr<Instruction> var_inst(new Instruction(
213
20.9k
      context(), spv::Op::OpVariable, returnVarTypeId, returnVarId,
214
20.9k
      {{spv_operand_type_t::SPV_OPERAND_TYPE_STORAGE_CLASS,
215
20.9k
        {(uint32_t)spv::StorageClass::Function}}}));
216
20.9k
  new_vars->push_back(std::move(var_inst));
217
20.9k
  get_decoration_mgr()->CloneDecorations(calleeFn->result_id(), returnVarId);
218
219
  // Decorate the return var with AliasedPointer if the storage class of the
220
  // pointee type is PhysicalStorageBuffer.
221
20.9k
  auto const pointee_type =
222
20.9k
      type_mgr->GetType(returnVarTypeId)->AsPointer()->pointee_type();
223
20.9k
  if (pointee_type->AsPointer() != nullptr) {
224
0
    if (pointee_type->AsPointer()->storage_class() ==
225
0
        spv::StorageClass::PhysicalStorageBuffer) {
226
0
      get_decoration_mgr()->AddDecoration(
227
0
          returnVarId, uint32_t(spv::Decoration::AliasedPointer));
228
0
    }
229
0
  }
230
231
20.9k
  return returnVarId;
232
20.9k
}
233
234
1.03M
bool InlinePass::IsSameBlockOp(const Instruction* inst) const {
235
1.03M
  return inst->opcode() == spv::Op::OpSampledImage ||
236
1.03M
         inst->opcode() == spv::Op::OpImage;
237
1.03M
}
238
239
bool InlinePass::CloneSameBlockOps(
240
    std::unique_ptr<Instruction>* inst,
241
    std::unordered_map<uint32_t, uint32_t>* postCallSB,
242
    std::unordered_map<uint32_t, Instruction*>* preCallSB,
243
630k
    std::unique_ptr<BasicBlock>* block_ptr) {
244
630k
  return (*inst)->WhileEachInId([&postCallSB, &preCallSB, &block_ptr,
245
1.10M
                                 this](uint32_t* iid) {
246
1.10M
    const auto mapItr = (*postCallSB).find(*iid);
247
1.10M
    if (mapItr == (*postCallSB).end()) {
248
1.10M
      const auto mapItr2 = (*preCallSB).find(*iid);
249
1.10M
      if (mapItr2 != (*preCallSB).end()) {
250
        // Clone pre-call same-block ops, map result id.
251
0
        const Instruction* inInst = mapItr2->second;
252
0
        std::unique_ptr<Instruction> sb_inst(inInst->Clone(context()));
253
0
        if (!CloneSameBlockOps(&sb_inst, postCallSB, preCallSB, block_ptr)) {
254
0
          return false;
255
0
        }
256
257
0
        const uint32_t rid = sb_inst->result_id();
258
0
        const uint32_t nid = context()->TakeNextId();
259
0
        if (nid == 0) {
260
0
          return false;
261
0
        }
262
0
        get_decoration_mgr()->CloneDecorations(rid, nid);
263
0
        sb_inst->SetResultId(nid);
264
0
        (*postCallSB)[rid] = nid;
265
0
        *iid = nid;
266
0
        (*block_ptr)->AddInstruction(std::move(sb_inst));
267
0
      }
268
1.10M
    } else {
269
      // Reset same-block op operand.
270
0
      *iid = mapItr->second;
271
0
    }
272
1.10M
    return true;
273
1.10M
  });
274
630k
}
275
276
void InlinePass::MoveInstsBeforeEntryBlock(
277
    std::unordered_map<uint32_t, Instruction*>* preCallSB,
278
    BasicBlock* new_blk_ptr, BasicBlock::iterator call_inst_itr,
279
43.1k
    UptrVectorIterator<BasicBlock> call_block_itr) {
280
452k
  for (auto cii = call_block_itr->begin(); cii != call_inst_itr;
281
409k
       cii = call_block_itr->begin()) {
282
409k
    Instruction* inst = &*cii;
283
409k
    inst->RemoveFromList();
284
409k
    std::unique_ptr<Instruction> cp_inst(inst);
285
    // Remember same-block ops for possible regeneration.
286
409k
    if (IsSameBlockOp(&*cp_inst)) {
287
0
      auto* sb_inst_ptr = cp_inst.get();
288
0
      (*preCallSB)[cp_inst->result_id()] = sb_inst_ptr;
289
0
    }
290
409k
    new_blk_ptr->AddInstruction(std::move(cp_inst));
291
409k
  }
292
43.1k
}
293
294
std::unique_ptr<BasicBlock> InlinePass::AddGuardBlock(
295
    std::vector<std::unique_ptr<BasicBlock>>* new_blocks,
296
    std::unordered_map<uint32_t, uint32_t>* callee2caller,
297
9
    std::unique_ptr<BasicBlock> new_blk_ptr, uint32_t entry_blk_label_id) {
298
9
  const auto guard_block_id = context()->TakeNextId();
299
9
  if (guard_block_id == 0) {
300
0
    return nullptr;
301
0
  }
302
9
  AddBranch(guard_block_id, &new_blk_ptr);
303
9
  new_blocks->push_back(std::move(new_blk_ptr));
304
  // Start the next block.
305
9
  new_blk_ptr = MakeUnique<BasicBlock>(NewLabel(guard_block_id));
306
  // Reset the mapping of the callee's entry block to point to
307
  // the guard block.  Do this so we can fix up phis later on to
308
  // satisfy dominance.
309
9
  (*callee2caller)[entry_blk_label_id] = guard_block_id;
310
9
  return new_blk_ptr;
311
9
}
312
313
InstructionList::iterator InlinePass::AddStoresForVariableInitializers(
314
    const std::unordered_map<uint32_t, uint32_t>& callee2caller,
315
    analysis::DebugInlinedAtContext* inlined_at_ctx,
316
    std::unique_ptr<BasicBlock>* new_blk_ptr,
317
43.1k
    UptrVectorIterator<BasicBlock> callee_first_block_itr) {
318
43.1k
  auto callee_itr = callee_first_block_itr->begin();
319
169k
  while (callee_itr->opcode() == spv::Op::OpVariable ||
320
126k
         callee_itr->GetCommonDebugOpcode() == CommonDebugInfoDebugDeclare) {
321
126k
    if (callee_itr->opcode() == spv::Op::OpVariable &&
322
126k
        callee_itr->NumInOperands() == 2) {
323
15.6k
      assert(callee2caller.count(callee_itr->result_id()) &&
324
15.6k
             "Expected the variable to have already been mapped.");
325
15.6k
      uint32_t new_var_id = callee2caller.at(callee_itr->result_id());
326
327
      // The initializer must be a constant or global value.  No mapped
328
      // should be used.
329
15.6k
      uint32_t val_id = callee_itr->GetSingleWordInOperand(1);
330
15.6k
      AddStore(new_var_id, val_id, new_blk_ptr, callee_itr->dbg_line_inst(),
331
15.6k
               context()->get_debug_info_mgr()->BuildDebugScope(
332
15.6k
                   callee_itr->GetDebugScope(), inlined_at_ctx));
333
15.6k
    }
334
126k
    if (callee_itr->GetCommonDebugOpcode() == CommonDebugInfoDebugDeclare) {
335
0
      InlineSingleInstruction(
336
0
          callee2caller, new_blk_ptr->get(), &*callee_itr,
337
0
          context()->get_debug_info_mgr()->BuildDebugInlinedAtChain(
338
0
              callee_itr->GetDebugScope().GetInlinedAt(), inlined_at_ctx));
339
0
    }
340
126k
    ++callee_itr;
341
126k
  }
342
43.1k
  return callee_itr;
343
43.1k
}
344
345
bool InlinePass::InlineSingleInstruction(
346
    const std::unordered_map<uint32_t, uint32_t>& callee2caller,
347
1.55M
    BasicBlock* new_blk_ptr, const Instruction* inst, uint32_t dbg_inlined_at) {
348
  // If we have return, it must be at the end of the callee. We will handle
349
  // it at the end.
350
1.55M
  if (inst->opcode() == spv::Op::OpReturnValue ||
351
1.53M
      inst->opcode() == spv::Op::OpReturn)
352
41.2k
    return true;
353
354
  // Copy callee instruction and remap all input Ids.
355
1.51M
  std::unique_ptr<Instruction> cp_inst(inst->Clone(context()));
356
2.53M
  cp_inst->ForEachInId([&callee2caller](uint32_t* iid) {
357
2.53M
    const auto mapItr = callee2caller.find(*iid);
358
2.53M
    if (mapItr != callee2caller.end()) {
359
1.88M
      *iid = mapItr->second;
360
1.88M
    }
361
2.53M
  });
362
363
  // If result id is non-zero, remap it.
364
1.51M
  const uint32_t rid = cp_inst->result_id();
365
1.51M
  if (rid != 0) {
366
926k
    const auto mapItr = callee2caller.find(rid);
367
926k
    if (mapItr == callee2caller.end()) {
368
0
      return false;
369
0
    }
370
926k
    uint32_t nid = mapItr->second;
371
926k
    cp_inst->SetResultId(nid);
372
926k
    get_decoration_mgr()->CloneDecorations(rid, nid);
373
926k
  }
374
375
1.51M
  cp_inst->UpdateDebugInlinedAt(dbg_inlined_at);
376
1.51M
  new_blk_ptr->AddInstruction(std::move(cp_inst));
377
1.51M
  return true;
378
1.51M
}
379
380
std::unique_ptr<BasicBlock> InlinePass::InlineReturn(
381
    const std::unordered_map<uint32_t, uint32_t>& callee2caller,
382
    std::vector<std::unique_ptr<BasicBlock>>* new_blocks,
383
    std::unique_ptr<BasicBlock> new_blk_ptr,
384
    analysis::DebugInlinedAtContext* inlined_at_ctx, Function* calleeFn,
385
43.1k
    const Instruction* inst, uint32_t returnVarId) {
386
  // Store return value to return variable.
387
43.1k
  if (inst->opcode() == spv::Op::OpReturnValue) {
388
20.6k
    assert(returnVarId != 0);
389
20.6k
    uint32_t valId = inst->GetInOperand(kSpvReturnValueId).words[0];
390
20.6k
    const auto mapItr = callee2caller.find(valId);
391
20.6k
    if (mapItr != callee2caller.end()) {
392
15.5k
      valId = mapItr->second;
393
15.5k
    }
394
20.6k
    AddStore(returnVarId, valId, &new_blk_ptr, inst->dbg_line_inst(),
395
20.6k
             context()->get_debug_info_mgr()->BuildDebugScope(
396
20.6k
                 inst->GetDebugScope(), inlined_at_ctx));
397
20.6k
  }
398
399
43.1k
  uint32_t returnLabelId = 0;
400
43.1k
  for (auto callee_block_itr = calleeFn->begin();
401
309k
       callee_block_itr != calleeFn->end(); ++callee_block_itr) {
402
270k
    if (spvOpcodeIsAbort(callee_block_itr->tail()->opcode())) {
403
4.54k
      returnLabelId = context()->TakeNextId();
404
4.54k
      break;
405
4.54k
    }
406
270k
  }
407
43.1k
  if (returnLabelId == 0) return new_blk_ptr;
408
409
4.54k
  if (inst->opcode() == spv::Op::OpReturn ||
410
2.05k
      inst->opcode() == spv::Op::OpReturnValue)
411
2.69k
    AddBranch(returnLabelId, &new_blk_ptr);
412
4.54k
  new_blocks->push_back(std::move(new_blk_ptr));
413
4.54k
  return MakeUnique<BasicBlock>(NewLabel(returnLabelId));
414
43.1k
}
415
416
bool InlinePass::InlineEntryBlock(
417
    const std::unordered_map<uint32_t, uint32_t>& callee2caller,
418
    std::unique_ptr<BasicBlock>* new_blk_ptr,
419
    UptrVectorIterator<BasicBlock> callee_first_block,
420
43.1k
    analysis::DebugInlinedAtContext* inlined_at_ctx) {
421
43.1k
  auto callee_inst_itr = AddStoresForVariableInitializers(
422
43.1k
      callee2caller, inlined_at_ctx, new_blk_ptr, callee_first_block);
423
424
468k
  while (callee_inst_itr != callee_first_block->end()) {
425
    // Don't inline function definition links, the calling function is not a
426
    // definition.
427
425k
    if (callee_inst_itr->GetShaderDebugOpcode() ==
428
425k
        NonSemanticShaderDebugInfoDebugFunctionDefinition) {
429
0
      ++callee_inst_itr;
430
0
      continue;
431
0
    }
432
433
425k
    if (!InlineSingleInstruction(
434
425k
            callee2caller, new_blk_ptr->get(), &*callee_inst_itr,
435
425k
            context()->get_debug_info_mgr()->BuildDebugInlinedAtChain(
436
425k
                callee_inst_itr->GetDebugScope().GetInlinedAt(),
437
425k
                inlined_at_ctx))) {
438
0
      return false;
439
0
    }
440
425k
    ++callee_inst_itr;
441
425k
  }
442
43.1k
  return true;
443
43.1k
}
444
445
std::unique_ptr<BasicBlock> InlinePass::InlineBasicBlocks(
446
    std::vector<std::unique_ptr<BasicBlock>>* new_blocks,
447
    const std::unordered_map<uint32_t, uint32_t>& callee2caller,
448
    std::unique_ptr<BasicBlock> new_blk_ptr,
449
43.1k
    analysis::DebugInlinedAtContext* inlined_at_ctx, Function* calleeFn) {
450
43.1k
  auto callee_block_itr = calleeFn->begin();
451
43.1k
  ++callee_block_itr;
452
453
279k
  while (callee_block_itr != calleeFn->end()) {
454
236k
    new_blocks->push_back(std::move(new_blk_ptr));
455
236k
    const auto mapItr =
456
236k
        callee2caller.find(callee_block_itr->GetLabelInst()->result_id());
457
236k
    if (mapItr == callee2caller.end()) return nullptr;
458
236k
    new_blk_ptr = MakeUnique<BasicBlock>(NewLabel(mapItr->second));
459
460
236k
    auto tail_inst_itr = callee_block_itr->end();
461
1.36M
    for (auto inst_itr = callee_block_itr->begin(); inst_itr != tail_inst_itr;
462
1.13M
         ++inst_itr) {
463
      // Don't inline function definition links, the calling function is not a
464
      // definition
465
1.13M
      if (inst_itr->GetShaderDebugOpcode() ==
466
1.13M
          NonSemanticShaderDebugInfoDebugFunctionDefinition)
467
0
        continue;
468
1.13M
      if (!InlineSingleInstruction(
469
1.13M
              callee2caller, new_blk_ptr.get(), &*inst_itr,
470
1.13M
              context()->get_debug_info_mgr()->BuildDebugInlinedAtChain(
471
1.13M
                  inst_itr->GetDebugScope().GetInlinedAt(), inlined_at_ctx))) {
472
0
        return nullptr;
473
0
      }
474
1.13M
    }
475
476
236k
    ++callee_block_itr;
477
236k
  }
478
43.1k
  return new_blk_ptr;
479
43.1k
}
480
481
bool InlinePass::MoveCallerInstsAfterFunctionCall(
482
    std::unordered_map<uint32_t, Instruction*>* preCallSB,
483
    std::unordered_map<uint32_t, uint32_t>* postCallSB,
484
    std::unique_ptr<BasicBlock>* new_blk_ptr,
485
43.1k
    BasicBlock::iterator call_inst_itr, bool multiBlocks) {
486
  // Copy remaining instructions from caller block.
487
673k
  for (Instruction* inst = call_inst_itr->NextNode(); inst;
488
630k
       inst = call_inst_itr->NextNode()) {
489
630k
    inst->RemoveFromList();
490
630k
    std::unique_ptr<Instruction> cp_inst(inst);
491
    // If multiple blocks generated, regenerate any same-block
492
    // instruction that has not been seen in this last block.
493
630k
    if (multiBlocks) {
494
630k
      if (!CloneSameBlockOps(&cp_inst, postCallSB, preCallSB, new_blk_ptr)) {
495
0
        return false;
496
0
      }
497
498
      // Remember same-block ops in this block.
499
630k
      if (IsSameBlockOp(&*cp_inst)) {
500
0
        const uint32_t rid = cp_inst->result_id();
501
0
        (*postCallSB)[rid] = rid;
502
0
      }
503
630k
    }
504
630k
    new_blk_ptr->get()->AddInstruction(std::move(cp_inst));
505
630k
  }
506
507
43.1k
  return true;
508
43.1k
}
509
510
void InlinePass::MoveLoopMergeInstToFirstBlock(
511
9
    std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
512
  // Move the OpLoopMerge from the last block back to the first, where
513
  // it belongs.
514
9
  auto& first = new_blocks->front();
515
9
  auto& last = new_blocks->back();
516
9
  assert(first != last);
517
518
  // Insert a modified copy of the loop merge into the first block.
519
9
  auto loop_merge_itr = last->tail();
520
9
  --loop_merge_itr;
521
9
  assert(loop_merge_itr->opcode() == spv::Op::OpLoopMerge);
522
9
  std::unique_ptr<Instruction> cp_inst(loop_merge_itr->Clone(context()));
523
9
  first->tail().InsertBefore(std::move(cp_inst));
524
525
  // Remove the loop merge from the last block.
526
9
  loop_merge_itr->RemoveFromList();
527
9
  delete &*loop_merge_itr;
528
9
}
529
530
void InlinePass::UpdateSingleBlockLoopContinueTarget(
531
9
    uint32_t new_id, std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
532
9
  auto& header = new_blocks->front();
533
9
  auto* merge_inst = header->GetLoopMergeInst();
534
535
  // The back-edge block is split at the branch to create a new back-edge
536
  // block. The old block is modified to branch to the new block. The loop
537
  // merge instruction is updated to declare the new block as the continue
538
  // target. This has the effect of changing the loop from being a large
539
  // continue construct and an empty loop construct to being a loop with a loop
540
  // construct and a trivial continue construct. This change is made to satisfy
541
  // structural dominance.
542
543
  // Add the new basic block.
544
9
  std::unique_ptr<BasicBlock> new_block =
545
9
      MakeUnique<BasicBlock>(NewLabel(new_id));
546
9
  auto& old_backedge = new_blocks->back();
547
9
  auto old_branch = old_backedge->tail();
548
549
  // Move the old back edge into the new block.
550
9
  std::unique_ptr<Instruction> br(&*old_branch);
551
9
  new_block->AddInstruction(std::move(br));
552
553
  // Add a branch to the new block from the old back-edge block.
554
9
  AddBranch(new_id, &old_backedge);
555
9
  new_blocks->push_back(std::move(new_block));
556
557
  // Update the loop's continue target to the new block.
558
9
  merge_inst->SetInOperand(1u, {new_id});
559
9
}
560
561
bool InlinePass::GenInlineCode(
562
    std::vector<std::unique_ptr<BasicBlock>>* new_blocks,
563
    std::vector<std::unique_ptr<Instruction>>* new_vars,
564
    BasicBlock::iterator call_inst_itr,
565
43.1k
    UptrVectorIterator<BasicBlock> call_block_itr) {
566
  // Map from all ids in the callee to their equivalent id in the caller
567
  // as callee instructions are copied into caller.
568
43.1k
  std::unordered_map<uint32_t, uint32_t> callee2caller;
569
  // Pre-call same-block insts
570
43.1k
  std::unordered_map<uint32_t, Instruction*> preCallSB;
571
  // Post-call same-block op ids
572
43.1k
  std::unordered_map<uint32_t, uint32_t> postCallSB;
573
574
43.1k
  analysis::DebugInlinedAtContext inlined_at_ctx(&*call_inst_itr);
575
576
  // Invalidate the def-use chains.  They are not kept up to date while
577
  // inlining.  However, certain calls try to keep them up-to-date if they are
578
  // valid.  These operations can fail.
579
43.1k
  context()->InvalidateAnalyses(IRContext::kAnalysisDefUse);
580
581
  // If the caller is a loop header and the callee has multiple blocks, then the
582
  // normal inlining logic will place the OpLoopMerge in the last of several
583
  // blocks in the loop.  Instead, it should be placed at the end of the first
584
  // block.  We'll wait to move the OpLoopMerge until the end of the regular
585
  // inlining logic, and only if necessary.
586
43.1k
  bool caller_is_loop_header = call_block_itr->GetLoopMergeInst() != nullptr;
587
588
  // Single-trip loop continue block
589
43.1k
  std::unique_ptr<BasicBlock> single_trip_loop_cont_blk;
590
591
43.1k
  Function* calleeFn = id2function_[call_inst_itr->GetSingleWordOperand(
592
43.1k
      kSpvFunctionCallFunctionId)];
593
594
  // Map parameters to actual arguments.
595
43.1k
  MapParams(calleeFn, call_inst_itr, &callee2caller);
596
597
  // Define caller local variables for all callee variables and create map to
598
  // them.
599
43.1k
  if (!CloneAndMapLocals(calleeFn, new_vars, &callee2caller, &inlined_at_ctx)) {
600
0
    return false;
601
0
  }
602
603
  // First block needs to use label of original block
604
  // but map callee label in case of phi reference.
605
43.1k
  uint32_t entry_blk_label_id = calleeFn->begin()->GetLabelInst()->result_id();
606
43.1k
  callee2caller[entry_blk_label_id] = call_block_itr->id();
607
43.1k
  std::unique_ptr<BasicBlock> new_blk_ptr =
608
43.1k
      MakeUnique<BasicBlock>(NewLabel(call_block_itr->id()));
609
610
  // Move instructions of original caller block up to call instruction.
611
43.1k
  MoveInstsBeforeEntryBlock(&preCallSB, new_blk_ptr.get(), call_inst_itr,
612
43.1k
                            call_block_itr);
613
614
43.1k
  if (caller_is_loop_header &&
615
16
      (*(calleeFn->begin())).GetMergeInst() != nullptr) {
616
    // We can't place both the caller's merge instruction and
617
    // another merge instruction in the same block.  So split the
618
    // calling block. Insert an unconditional branch to a new guard
619
    // block.  Later, once we know the ID of the last block,  we
620
    // will move the caller's OpLoopMerge from the last generated
621
    // block into the first block. We also wait to avoid
622
    // invalidating various iterators.
623
9
    new_blk_ptr = AddGuardBlock(new_blocks, &callee2caller,
624
9
                                std::move(new_blk_ptr), entry_blk_label_id);
625
9
    if (new_blk_ptr == nullptr) return false;
626
9
  }
627
628
  // Create return var if needed.
629
43.1k
  const uint32_t calleeTypeId = calleeFn->type_id();
630
43.1k
  uint32_t returnVarId = 0;
631
43.1k
  analysis::Type* calleeType = context()->get_type_mgr()->GetType(calleeTypeId);
632
43.1k
  if (calleeType->AsVoid() == nullptr) {
633
20.9k
    returnVarId = CreateReturnVar(calleeFn, new_vars);
634
20.9k
    if (returnVarId == 0) {
635
0
      return false;
636
0
    }
637
20.9k
  }
638
639
2.10M
  calleeFn->WhileEachInst([&callee2caller, this](const Instruction* cpi) {
640
    // Create set of callee result ids. Used to detect forward references
641
2.10M
    const uint32_t rid = cpi->result_id();
642
2.10M
    if (rid != 0 && callee2caller.find(rid) == callee2caller.end()) {
643
1.20M
      const uint32_t nid = context()->TakeNextId();
644
1.20M
      if (nid == 0) return false;
645
1.20M
      callee2caller[rid] = nid;
646
1.20M
    }
647
2.10M
    return true;
648
2.10M
  });
649
650
  // Inline DebugClare instructions in the callee's header.
651
43.1k
  calleeFn->ForEachDebugInstructionsInHeader(
652
43.1k
      [&new_blk_ptr, &callee2caller, &inlined_at_ctx, this](Instruction* inst) {
653
0
        InlineSingleInstruction(
654
0
            callee2caller, new_blk_ptr.get(), inst,
655
0
            context()->get_debug_info_mgr()->BuildDebugInlinedAtChain(
656
0
                inst->GetDebugScope().GetInlinedAt(), &inlined_at_ctx));
657
0
      });
658
659
  // Inline the entry block of the callee function.
660
43.1k
  if (!InlineEntryBlock(callee2caller, &new_blk_ptr, calleeFn->begin(),
661
43.1k
                        &inlined_at_ctx)) {
662
0
    return false;
663
0
  }
664
665
  // Inline blocks of the callee function other than the entry block.
666
43.1k
  new_blk_ptr =
667
43.1k
      InlineBasicBlocks(new_blocks, callee2caller, std::move(new_blk_ptr),
668
43.1k
                        &inlined_at_ctx, calleeFn);
669
43.1k
  if (new_blk_ptr == nullptr) return false;
670
671
43.1k
  new_blk_ptr = InlineReturn(callee2caller, new_blocks, std::move(new_blk_ptr),
672
43.1k
                             &inlined_at_ctx, calleeFn,
673
43.1k
                             &*(calleeFn->tail()->tail()), returnVarId);
674
675
  // Load return value into result id of call, if it exists.
676
43.1k
  if (returnVarId != 0) {
677
20.9k
    const uint32_t resId = call_inst_itr->result_id();
678
20.9k
    assert(resId != 0);
679
20.9k
    AddLoad(calleeTypeId, resId, returnVarId, &new_blk_ptr,
680
20.9k
            call_inst_itr->dbg_line_inst(), call_inst_itr->GetDebugScope());
681
20.9k
  }
682
683
  // Move instructions of original caller block after call instruction.
684
43.1k
  if (!MoveCallerInstsAfterFunctionCall(&preCallSB, &postCallSB, &new_blk_ptr,
685
43.1k
                                        call_inst_itr,
686
43.1k
                                        calleeFn->begin() != calleeFn->end()))
687
0
    return false;
688
689
  // Finalize inline code.
690
43.1k
  new_blocks->push_back(std::move(new_blk_ptr));
691
692
43.1k
  if (caller_is_loop_header && (new_blocks->size() > 1)) {
693
9
    MoveLoopMergeInstToFirstBlock(new_blocks);
694
695
    // If the loop was a single basic block previously, update it's structure.
696
9
    auto& header = new_blocks->front();
697
9
    auto* merge_inst = header->GetLoopMergeInst();
698
9
    if (merge_inst->GetSingleWordInOperand(1u) == header->id()) {
699
9
      auto new_id = context()->TakeNextId();
700
9
      if (new_id == 0) return false;
701
9
      UpdateSingleBlockLoopContinueTarget(new_id, new_blocks);
702
9
    }
703
9
  }
704
705
  // Update block map given replacement blocks.
706
283k
  for (auto& blk : *new_blocks) {
707
283k
    id2block_[blk->id()] = &*blk;
708
283k
  }
709
710
  // We need to kill the name and decorations for the call, which will be
711
  // deleted.
712
43.1k
  context()->KillNamesAndDecorates(&*call_inst_itr);
713
714
43.1k
  return true;
715
43.1k
}
716
717
2.86M
bool InlinePass::IsInlinableFunctionCall(const Instruction* inst) {
718
2.86M
  if (inst->opcode() != spv::Op::OpFunctionCall) return false;
719
49.9k
  const uint32_t calleeFnId =
720
49.9k
      inst->GetSingleWordOperand(kSpvFunctionCallFunctionId);
721
49.9k
  const auto ci = inlinable_.find(calleeFnId);
722
49.9k
  if (ci == inlinable_.cend()) return false;
723
724
43.1k
  if (early_return_funcs_.find(calleeFnId) != early_return_funcs_.end()) {
725
    // We rely on the merge-return pass to handle the early return case
726
    // in advance.
727
0
    std::string message =
728
0
        "The function '" + id2function_[calleeFnId]->DefInst().PrettyPrint() +
729
0
        "' could not be inlined because the return instruction "
730
0
        "is not at the end of the function. This could be fixed by "
731
0
        "running merge-return before inlining.";
732
0
    consumer()(SPV_MSG_WARNING, "", {0, 0, 0}, message.c_str());
733
0
    return false;
734
0
  }
735
736
43.1k
  return true;
737
43.1k
}
738
739
void InlinePass::UpdateSucceedingPhis(
740
23.7k
    std::vector<std::unique_ptr<BasicBlock>>& new_blocks) {
741
23.7k
  const auto firstBlk = new_blocks.begin();
742
23.7k
  const auto lastBlk = new_blocks.end() - 1;
743
23.7k
  const uint32_t firstId = (*firstBlk)->id();
744
23.7k
  const uint32_t lastId = (*lastBlk)->id();
745
23.7k
  const BasicBlock& const_last_block = *lastBlk->get();
746
23.7k
  const_last_block.ForEachSuccessorLabel(
747
27.5k
      [&firstId, &lastId, this](const uint32_t succ) {
748
27.5k
        BasicBlock* sbp = this->id2block_[succ];
749
27.5k
        sbp->ForEachPhiInst([&firstId, &lastId](Instruction* phi) {
750
5.28k
          phi->ForEachInId([&firstId, &lastId](uint32_t* id) {
751
5.28k
            if (*id == firstId) *id = lastId;
752
5.28k
          });
753
1.32k
        });
754
27.5k
      });
755
23.7k
}
756
757
25.2k
bool InlinePass::HasNoReturnInLoop(Function* func) {
758
  // If control not structured, do not do loop/return analysis
759
  // TODO: Analyze returns in non-structured control flow
760
25.2k
  if (!context()->get_feature_mgr()->HasCapability(spv::Capability::Shader))
761
42
    return false;
762
25.1k
  const auto structured_analysis = context()->GetStructuredCFGAnalysis();
763
  // Search for returns in structured construct.
764
25.1k
  bool return_in_loop = false;
765
288k
  for (auto& blk : *func) {
766
288k
    auto terminal_ii = blk.cend();
767
288k
    --terminal_ii;
768
288k
    if (spvOpcodeIsReturn(terminal_ii->opcode()) &&
769
23.1k
        structured_analysis->ContainingLoop(blk.id()) != 0) {
770
201
      return_in_loop = true;
771
201
      break;
772
201
    }
773
288k
  }
774
25.1k
  return !return_in_loop;
775
25.2k
}
776
777
25.2k
void InlinePass::AnalyzeReturns(Function* func) {
778
  // Analyze functions without a return in loop.
779
25.2k
  if (HasNoReturnInLoop(func)) {
780
24.9k
    no_return_in_loop_.insert(func->result_id());
781
24.9k
  }
782
  // Analyze functions with a return before its tail basic block.
783
286k
  for (auto& blk : *func) {
784
286k
    auto terminal_ii = blk.cend();
785
286k
    --terminal_ii;
786
286k
    if (spvOpcodeIsReturn(terminal_ii->opcode()) && &blk != func->tail()) {
787
509
      early_return_funcs_.insert(func->result_id());
788
509
      break;
789
509
    }
790
286k
  }
791
25.2k
}
792
793
30.1k
bool InlinePass::IsInlinableFunction(Function* func) {
794
  // We can only inline a function if it has blocks.
795
30.1k
  if (func->cbegin() == func->cend()) return false;
796
797
  // Do not inline functions with DontInline flag.
798
30.1k
  if (func->control_mask() & uint32_t(spv::FunctionControlMask::DontInline)) {
799
4.94k
    return false;
800
4.94k
  }
801
802
  // Do not inline functions with returns in loops. Currently early return
803
  // functions are inlined by wrapping them in a one trip loop and implementing
804
  // the returns as a branch to the loop's merge block. However, this can only
805
  // done validly if the return was not in a loop in the original function.
806
  // Also remember functions with multiple (early) returns.
807
25.2k
  AnalyzeReturns(func);
808
25.2k
  if (no_return_in_loop_.find(func->result_id()) == no_return_in_loop_.cend()) {
809
243
    return false;
810
243
  }
811
812
24.9k
  if (func->IsRecursive()) {
813
55
    return false;
814
55
  }
815
816
  // Do not inline functions with an abort instruction if they are called from a
817
  // continue construct. If it is inlined into a continue construct the backedge
818
  // will no longer post-dominate the continue target, which is invalid.  An
819
  // `OpUnreachable` is acceptable because it will not change post-dominance if
820
  // it is statically unreachable.
821
24.9k
  bool func_is_called_from_continue =
822
24.9k
      funcs_called_from_continue_.count(func->result_id()) != 0;
823
824
24.9k
  if (func_is_called_from_continue && ContainsAbortOtherThanUnreachable(func)) {
825
8
    return false;
826
8
  }
827
828
24.9k
  return true;
829
24.9k
}
830
831
158
bool InlinePass::ContainsAbortOtherThanUnreachable(Function* func) const {
832
8.26k
  return !func->WhileEachInst([](Instruction* inst) {
833
8.26k
    return inst->opcode() == spv::Op::OpUnreachable ||
834
8.22k
           !spvOpcodeIsAbort(inst->opcode());
835
8.26k
  });
836
158
}
837
838
20.3k
void InlinePass::InitializeInline() {
839
20.3k
  false_id_ = 0;
840
841
  // clear collections
842
20.3k
  id2function_.clear();
843
20.3k
  id2block_.clear();
844
20.3k
  inlinable_.clear();
845
20.3k
  no_return_in_loop_.clear();
846
20.3k
  early_return_funcs_.clear();
847
20.3k
  funcs_called_from_continue_ =
848
20.3k
      context()->GetStructuredCFGAnalysis()->FindFuncsCalledFromContinue();
849
850
30.1k
  for (auto& fn : *get_module()) {
851
    // Initialize function and block maps.
852
30.1k
    id2function_[fn.result_id()] = &fn;
853
309k
    for (auto& blk : fn) {
854
309k
      id2block_[blk.id()] = &blk;
855
309k
    }
856
    // Compute inlinability
857
30.1k
    if (IsInlinableFunction(&fn)) inlinable_.insert(fn.result_id());
858
30.1k
  }
859
20.3k
}
860
861
36.5k
InlinePass::InlinePass() {}
862
863
4.41k
void InlinePass::FixDebugDeclares(Function* func) {
864
4.41k
  std::map<uint32_t, Instruction*> access_chains;
865
4.41k
  std::vector<Instruction*> debug_declare_insts;
866
867
2.32M
  func->ForEachInst([&access_chains, &debug_declare_insts](Instruction* inst) {
868
2.32M
    if (inst->opcode() == spv::Op::OpAccessChain) {
869
213k
      access_chains[inst->result_id()] = inst;
870
213k
    }
871
2.32M
    if (inst->GetCommonDebugOpcode() == CommonDebugInfoDebugDeclare) {
872
0
      debug_declare_insts.push_back(inst);
873
0
    }
874
2.32M
  });
875
876
4.41k
  for (auto& inst : debug_declare_insts) {
877
0
    FixDebugDeclare(inst, access_chains);
878
0
  }
879
4.41k
}
880
881
void InlinePass::FixDebugDeclare(
882
    Instruction* dbg_declare_inst,
883
0
    const std::map<uint32_t, Instruction*>& access_chains) {
884
0
  do {
885
0
    uint32_t var_id =
886
0
        dbg_declare_inst->GetSingleWordInOperand(kSpvDebugDeclareVarInIdx);
887
888
    // The def-use chains are not kept up to date while inlining, so we need to
889
    // get the variable by traversing the functions.
890
0
    auto it = access_chains.find(var_id);
891
0
    if (it == access_chains.end()) {
892
0
      return;
893
0
    }
894
0
    Instruction* access_chain = it->second;
895
896
    // If the variable id in the debug declare is an access chain, it is
897
    // invalid. it needs to be fixed up. The debug declare will be updated so
898
    // that its Var operand becomes the base of the access chain. The indexes of
899
    // the access chain are prepended before the indexes of the debug declare.
900
901
    // DebugDeclare Indexes must be constant integers. If any access chain
902
    // index is non-constant (e.g. the result of an OpLoad), we cannot
903
    // produce a valid DebugDeclare. Kill it rather than emit invalid SPIR-V.
904
0
    bool has_non_constant_index = false;
905
0
    for (uint32_t i = kSpvAccessChainBaseInIdx + 1;
906
0
         i < access_chain->NumInOperands(); ++i) {
907
0
      uint32_t idx_id = access_chain->GetSingleWordInOperand(i);
908
0
      bool found_constant = false;
909
0
      for (auto& inst : context()->module()->types_values()) {
910
0
        if (inst.result_id() == idx_id) {
911
0
          found_constant = spvOpcodeIsConstant(inst.opcode());
912
0
          break;
913
0
        }
914
0
      }
915
0
      if (!found_constant) {
916
0
        has_non_constant_index = true;
917
0
        break;
918
0
      }
919
0
    }
920
0
    if (has_non_constant_index) {
921
0
      context()->KillInst(dbg_declare_inst);
922
0
      return;
923
0
    }
924
925
0
    std::vector<Operand> operands;
926
0
    for (int i = 0; i < kSpvDebugDeclareVarInIdx; i++) {
927
0
      operands.push_back(dbg_declare_inst->GetInOperand(i));
928
0
    }
929
930
0
    uint32_t access_chain_base =
931
0
        access_chain->GetSingleWordInOperand(kSpvAccessChainBaseInIdx);
932
0
    operands.push_back(Operand(SPV_OPERAND_TYPE_ID, {access_chain_base}));
933
0
    operands.push_back(
934
0
        dbg_declare_inst->GetInOperand(kSpvDebugDeclareVarInIdx + 1));
935
936
0
    for (uint32_t i = kSpvAccessChainBaseInIdx + 1;
937
0
         i < access_chain->NumInOperands(); ++i) {
938
0
      operands.push_back(access_chain->GetInOperand(i));
939
0
    }
940
941
0
    for (uint32_t i = kSpvDebugDeclareVarInIdx + 2;
942
0
         i < dbg_declare_inst->NumInOperands(); ++i) {
943
0
      operands.push_back(dbg_declare_inst->GetInOperand(i));
944
0
    }
945
946
0
    dbg_declare_inst->SetInOperands(std::move(operands));
947
0
  } while (true);
948
0
}
949
950
}  // namespace opt
951
}  // namespace spvtools