Coverage Report

Created: 2026-01-16 06:48

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.01k
                           std::unique_ptr<BasicBlock>* block_ptr) {
61
2.01k
  std::unique_ptr<Instruction> newBranch(
62
2.01k
      new Instruction(context(), spv::Op::OpBranch, 0, 0,
63
2.01k
                      {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {label_id}}}));
64
2.01k
  (*block_ptr)->AddInstruction(std::move(newBranch));
65
2.01k
}
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
29.2k
                          const DebugScope& dbg_scope) {
92
29.2k
  std::unique_ptr<Instruction> newStore(
93
29.2k
      new Instruction(context(), spv::Op::OpStore, 0, 0,
94
29.2k
                      {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {ptr_id}},
95
29.2k
                       {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {val_id}}}));
96
29.2k
  if (line_inst != nullptr) {
97
0
    newStore->AddDebugLine(line_inst);
98
0
  }
99
29.2k
  newStore->SetDebugScope(dbg_scope);
100
29.2k
  (*block_ptr)->AddInstruction(std::move(newStore));
101
29.2k
}
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
16.2k
                         const DebugScope& dbg_scope) {
107
16.2k
  std::unique_ptr<Instruction> newLoad(
108
16.2k
      new Instruction(context(), spv::Op::OpLoad, type_id, resultId,
109
16.2k
                      {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {ptr_id}}}));
110
16.2k
  if (line_inst != nullptr) {
111
0
    newLoad->AddDebugLine(line_inst);
112
0
  }
113
16.2k
  newLoad->SetDebugScope(dbg_scope);
114
16.2k
  (*block_ptr)->AddInstruction(std::move(newLoad));
115
16.2k
}
116
117
238k
std::unique_ptr<Instruction> InlinePass::NewLabel(uint32_t label_id) {
118
238k
  std::unique_ptr<Instruction> newLabel(
119
238k
      new Instruction(context(), spv::Op::OpLabel, 0, label_id, {}));
120
238k
  return newLabel;
121
238k
}
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
35.4k
    std::unordered_map<uint32_t, uint32_t>* callee2caller) {
146
35.4k
  int param_idx = 0;
147
35.4k
  calleeFn->ForEachParam(
148
51.4k
      [&call_inst_itr, &param_idx, &callee2caller](const Instruction* cpi) {
149
51.4k
        const uint32_t pid = cpi->result_id();
150
51.4k
        (*callee2caller)[pid] = call_inst_itr->GetSingleWordOperand(
151
51.4k
            kSpvFunctionCallArgumentId + param_idx);
152
51.4k
        ++param_idx;
153
51.4k
      });
154
35.4k
}
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
35.4k
    analysis::DebugInlinedAtContext* inlined_at_ctx) {
160
35.4k
  auto callee_block_itr = calleeFn->begin();
161
35.4k
  auto callee_var_itr = callee_block_itr->begin();
162
137k
  while (callee_var_itr->opcode() == spv::Op::OpVariable ||
163
35.4k
         callee_var_itr->GetCommonDebugOpcode() ==
164
102k
             CommonDebugInfoDebugDeclare) {
165
102k
    if (callee_var_itr->opcode() != spv::Op::OpVariable) {
166
0
      ++callee_var_itr;
167
0
      continue;
168
0
    }
169
170
102k
    std::unique_ptr<Instruction> var_inst(callee_var_itr->Clone(context()));
171
102k
    uint32_t newId = context()->TakeNextId();
172
102k
    if (newId == 0) {
173
0
      return false;
174
0
    }
175
102k
    get_decoration_mgr()->CloneDecorations(callee_var_itr->result_id(), newId);
176
102k
    var_inst->SetResultId(newId);
177
102k
    var_inst->UpdateDebugInlinedAt(
178
102k
        context()->get_debug_info_mgr()->BuildDebugInlinedAtChain(
179
102k
            callee_var_itr->GetDebugInlinedAt(), inlined_at_ctx));
180
102k
    (*callee2caller)[callee_var_itr->result_id()] = newId;
181
102k
    new_vars->push_back(std::move(var_inst));
182
102k
    ++callee_var_itr;
183
102k
  }
184
35.4k
  return true;
185
35.4k
}
186
187
uint32_t InlinePass::CreateReturnVar(
188
16.2k
    Function* calleeFn, std::vector<std::unique_ptr<Instruction>>* new_vars) {
189
16.2k
  uint32_t returnVarId = 0;
190
16.2k
  const uint32_t calleeTypeId = calleeFn->type_id();
191
16.2k
  analysis::TypeManager* type_mgr = context()->get_type_mgr();
192
16.2k
  assert(type_mgr->GetType(calleeTypeId)->AsVoid() == nullptr &&
193
16.2k
         "Cannot create a return variable of type void.");
194
  // Find or create ptr to callee return type.
195
16.2k
  uint32_t returnVarTypeId =
196
16.2k
      type_mgr->FindPointerToType(calleeTypeId, spv::StorageClass::Function);
197
198
16.2k
  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
16.2k
  returnVarId = context()->TakeNextId();
208
16.2k
  if (returnVarId == 0) {
209
0
    return 0;
210
0
  }
211
212
16.2k
  std::unique_ptr<Instruction> var_inst(new Instruction(
213
16.2k
      context(), spv::Op::OpVariable, returnVarTypeId, returnVarId,
214
16.2k
      {{spv_operand_type_t::SPV_OPERAND_TYPE_STORAGE_CLASS,
215
16.2k
        {(uint32_t)spv::StorageClass::Function}}}));
216
16.2k
  new_vars->push_back(std::move(var_inst));
217
16.2k
  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
16.2k
  auto const pointee_type =
222
16.2k
      type_mgr->GetType(returnVarTypeId)->AsPointer()->pointee_type();
223
16.2k
  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
16.2k
  return returnVarId;
232
16.2k
}
233
234
859k
bool InlinePass::IsSameBlockOp(const Instruction* inst) const {
235
859k
  return inst->opcode() == spv::Op::OpSampledImage ||
236
859k
         inst->opcode() == spv::Op::OpImage;
237
859k
}
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
523k
    std::unique_ptr<BasicBlock>* block_ptr) {
244
523k
  return (*inst)->WhileEachInId([&postCallSB, &preCallSB, &block_ptr,
245
923k
                                 this](uint32_t* iid) {
246
923k
    const auto mapItr = (*postCallSB).find(*iid);
247
923k
    if (mapItr == (*postCallSB).end()) {
248
923k
      const auto mapItr2 = (*preCallSB).find(*iid);
249
923k
      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
923k
    } else {
269
      // Reset same-block op operand.
270
0
      *iid = mapItr->second;
271
0
    }
272
923k
    return true;
273
923k
  });
274
523k
}
275
276
void InlinePass::MoveInstsBeforeEntryBlock(
277
    std::unordered_map<uint32_t, Instruction*>* preCallSB,
278
    BasicBlock* new_blk_ptr, BasicBlock::iterator call_inst_itr,
279
35.4k
    UptrVectorIterator<BasicBlock> call_block_itr) {
280
371k
  for (auto cii = call_block_itr->begin(); cii != call_inst_itr;
281
336k
       cii = call_block_itr->begin()) {
282
336k
    Instruction* inst = &*cii;
283
336k
    inst->RemoveFromList();
284
336k
    std::unique_ptr<Instruction> cp_inst(inst);
285
    // Remember same-block ops for possible regeneration.
286
336k
    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
336k
    new_blk_ptr->AddInstruction(std::move(cp_inst));
291
336k
  }
292
35.4k
}
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
8
    std::unique_ptr<BasicBlock> new_blk_ptr, uint32_t entry_blk_label_id) {
298
8
  const auto guard_block_id = context()->TakeNextId();
299
8
  if (guard_block_id == 0) {
300
0
    return nullptr;
301
0
  }
302
8
  AddBranch(guard_block_id, &new_blk_ptr);
303
8
  new_blocks->push_back(std::move(new_blk_ptr));
304
  // Start the next block.
305
8
  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
8
  (*callee2caller)[entry_blk_label_id] = guard_block_id;
310
8
  return new_blk_ptr;
311
8
}
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
35.4k
    UptrVectorIterator<BasicBlock> callee_first_block_itr) {
318
35.4k
  auto callee_itr = callee_first_block_itr->begin();
319
137k
  while (callee_itr->opcode() == spv::Op::OpVariable ||
320
102k
         callee_itr->GetCommonDebugOpcode() == CommonDebugInfoDebugDeclare) {
321
102k
    if (callee_itr->opcode() == spv::Op::OpVariable &&
322
102k
        callee_itr->NumInOperands() == 2) {
323
13.2k
      assert(callee2caller.count(callee_itr->result_id()) &&
324
13.2k
             "Expected the variable to have already been mapped.");
325
13.2k
      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
13.2k
      uint32_t val_id = callee_itr->GetSingleWordInOperand(1);
330
13.2k
      AddStore(new_var_id, val_id, new_blk_ptr, callee_itr->dbg_line_inst(),
331
13.2k
               context()->get_debug_info_mgr()->BuildDebugScope(
332
13.2k
                   callee_itr->GetDebugScope(), inlined_at_ctx));
333
13.2k
    }
334
102k
    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
102k
    ++callee_itr;
341
102k
  }
342
35.4k
  return callee_itr;
343
35.4k
}
344
345
bool InlinePass::InlineSingleInstruction(
346
    const std::unordered_map<uint32_t, uint32_t>& callee2caller,
347
1.29M
    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.29M
  if (inst->opcode() == spv::Op::OpReturnValue ||
351
1.27M
      inst->opcode() == spv::Op::OpReturn)
352
34.0k
    return true;
353
354
  // Copy callee instruction and remap all input Ids.
355
1.26M
  std::unique_ptr<Instruction> cp_inst(inst->Clone(context()));
356
2.10M
  cp_inst->ForEachInId([&callee2caller](uint32_t* iid) {
357
2.10M
    const auto mapItr = callee2caller.find(*iid);
358
2.10M
    if (mapItr != callee2caller.end()) {
359
1.57M
      *iid = mapItr->second;
360
1.57M
    }
361
2.10M
  });
362
363
  // If result id is non-zero, remap it.
364
1.26M
  const uint32_t rid = cp_inst->result_id();
365
1.26M
  if (rid != 0) {
366
762k
    const auto mapItr = callee2caller.find(rid);
367
762k
    if (mapItr == callee2caller.end()) {
368
0
      return false;
369
0
    }
370
762k
    uint32_t nid = mapItr->second;
371
762k
    cp_inst->SetResultId(nid);
372
762k
    get_decoration_mgr()->CloneDecorations(rid, nid);
373
762k
  }
374
375
1.26M
  cp_inst->UpdateDebugInlinedAt(dbg_inlined_at);
376
1.26M
  new_blk_ptr->AddInstruction(std::move(cp_inst));
377
1.26M
  return true;
378
1.26M
}
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
35.4k
    const Instruction* inst, uint32_t returnVarId) {
386
  // Store return value to return variable.
387
35.4k
  if (inst->opcode() == spv::Op::OpReturnValue) {
388
15.9k
    assert(returnVarId != 0);
389
15.9k
    uint32_t valId = inst->GetInOperand(kSpvReturnValueId).words[0];
390
15.9k
    const auto mapItr = callee2caller.find(valId);
391
15.9k
    if (mapItr != callee2caller.end()) {
392
12.5k
      valId = mapItr->second;
393
12.5k
    }
394
15.9k
    AddStore(returnVarId, valId, &new_blk_ptr, inst->dbg_line_inst(),
395
15.9k
             context()->get_debug_info_mgr()->BuildDebugScope(
396
15.9k
                 inst->GetDebugScope(), inlined_at_ctx));
397
15.9k
  }
398
399
35.4k
  uint32_t returnLabelId = 0;
400
35.4k
  for (auto callee_block_itr = calleeFn->begin();
401
260k
       callee_block_itr != calleeFn->end(); ++callee_block_itr) {
402
228k
    if (spvOpcodeIsAbort(callee_block_itr->tail()->opcode())) {
403
3.34k
      returnLabelId = context()->TakeNextId();
404
3.34k
      break;
405
3.34k
    }
406
228k
  }
407
35.4k
  if (returnLabelId == 0) return new_blk_ptr;
408
409
3.34k
  if (inst->opcode() == spv::Op::OpReturn ||
410
1.46k
      inst->opcode() == spv::Op::OpReturnValue)
411
1.99k
    AddBranch(returnLabelId, &new_blk_ptr);
412
3.34k
  new_blocks->push_back(std::move(new_blk_ptr));
413
3.34k
  return MakeUnique<BasicBlock>(NewLabel(returnLabelId));
414
35.4k
}
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
35.4k
    analysis::DebugInlinedAtContext* inlined_at_ctx) {
421
35.4k
  auto callee_inst_itr = AddStoresForVariableInitializers(
422
35.4k
      callee2caller, inlined_at_ctx, new_blk_ptr, callee_first_block);
423
424
382k
  while (callee_inst_itr != callee_first_block->end()) {
425
    // Don't inline function definition links, the calling function is not a
426
    // definition.
427
347k
    if (callee_inst_itr->GetShader100DebugOpcode() ==
428
347k
        NonSemanticShaderDebugInfo100DebugFunctionDefinition) {
429
0
      ++callee_inst_itr;
430
0
      continue;
431
0
    }
432
433
347k
    if (!InlineSingleInstruction(
434
347k
            callee2caller, new_blk_ptr->get(), &*callee_inst_itr,
435
347k
            context()->get_debug_info_mgr()->BuildDebugInlinedAtChain(
436
347k
                callee_inst_itr->GetDebugScope().GetInlinedAt(),
437
347k
                inlined_at_ctx))) {
438
0
      return false;
439
0
    }
440
347k
    ++callee_inst_itr;
441
347k
  }
442
35.4k
  return true;
443
35.4k
}
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
35.4k
    analysis::DebugInlinedAtContext* inlined_at_ctx, Function* calleeFn) {
450
35.4k
  auto callee_block_itr = calleeFn->begin();
451
35.4k
  ++callee_block_itr;
452
453
235k
  while (callee_block_itr != calleeFn->end()) {
454
200k
    new_blocks->push_back(std::move(new_blk_ptr));
455
200k
    const auto mapItr =
456
200k
        callee2caller.find(callee_block_itr->GetLabelInst()->result_id());
457
200k
    if (mapItr == callee2caller.end()) return nullptr;
458
200k
    new_blk_ptr = MakeUnique<BasicBlock>(NewLabel(mapItr->second));
459
460
200k
    auto tail_inst_itr = callee_block_itr->end();
461
1.14M
    for (auto inst_itr = callee_block_itr->begin(); inst_itr != tail_inst_itr;
462
946k
         ++inst_itr) {
463
      // Don't inline function definition links, the calling function is not a
464
      // definition
465
946k
      if (inst_itr->GetShader100DebugOpcode() ==
466
946k
          NonSemanticShaderDebugInfo100DebugFunctionDefinition)
467
0
        continue;
468
946k
      if (!InlineSingleInstruction(
469
946k
              callee2caller, new_blk_ptr.get(), &*inst_itr,
470
946k
              context()->get_debug_info_mgr()->BuildDebugInlinedAtChain(
471
946k
                  inst_itr->GetDebugScope().GetInlinedAt(), inlined_at_ctx))) {
472
0
        return nullptr;
473
0
      }
474
946k
    }
475
476
200k
    ++callee_block_itr;
477
200k
  }
478
35.4k
  return new_blk_ptr;
479
35.4k
}
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
35.4k
    BasicBlock::iterator call_inst_itr, bool multiBlocks) {
486
  // Copy remaining instructions from caller block.
487
559k
  for (Instruction* inst = call_inst_itr->NextNode(); inst;
488
523k
       inst = call_inst_itr->NextNode()) {
489
523k
    inst->RemoveFromList();
490
523k
    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
523k
    if (multiBlocks) {
494
523k
      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
523k
      if (IsSameBlockOp(&*cp_inst)) {
500
0
        const uint32_t rid = cp_inst->result_id();
501
0
        (*postCallSB)[rid] = rid;
502
0
      }
503
523k
    }
504
523k
    new_blk_ptr->get()->AddInstruction(std::move(cp_inst));
505
523k
  }
506
507
35.4k
  return true;
508
35.4k
}
509
510
void InlinePass::MoveLoopMergeInstToFirstBlock(
511
8
    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
8
  auto& first = new_blocks->front();
515
8
  auto& last = new_blocks->back();
516
8
  assert(first != last);
517
518
  // Insert a modified copy of the loop merge into the first block.
519
8
  auto loop_merge_itr = last->tail();
520
8
  --loop_merge_itr;
521
8
  assert(loop_merge_itr->opcode() == spv::Op::OpLoopMerge);
522
8
  std::unique_ptr<Instruction> cp_inst(loop_merge_itr->Clone(context()));
523
8
  first->tail().InsertBefore(std::move(cp_inst));
524
525
  // Remove the loop merge from the last block.
526
8
  loop_merge_itr->RemoveFromList();
527
8
  delete &*loop_merge_itr;
528
8
}
529
530
void InlinePass::UpdateSingleBlockLoopContinueTarget(
531
8
    uint32_t new_id, std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
532
8
  auto& header = new_blocks->front();
533
8
  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
8
  std::unique_ptr<BasicBlock> new_block =
545
8
      MakeUnique<BasicBlock>(NewLabel(new_id));
546
8
  auto& old_backedge = new_blocks->back();
547
8
  auto old_branch = old_backedge->tail();
548
549
  // Move the old back edge into the new block.
550
8
  std::unique_ptr<Instruction> br(&*old_branch);
551
8
  new_block->AddInstruction(std::move(br));
552
553
  // Add a branch to the new block from the old back-edge block.
554
8
  AddBranch(new_id, &old_backedge);
555
8
  new_blocks->push_back(std::move(new_block));
556
557
  // Update the loop's continue target to the new block.
558
8
  merge_inst->SetInOperand(1u, {new_id});
559
8
}
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
35.4k
    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
35.4k
  std::unordered_map<uint32_t, uint32_t> callee2caller;
569
  // Pre-call same-block insts
570
35.4k
  std::unordered_map<uint32_t, Instruction*> preCallSB;
571
  // Post-call same-block op ids
572
35.4k
  std::unordered_map<uint32_t, uint32_t> postCallSB;
573
574
35.4k
  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
35.4k
  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
35.4k
  bool caller_is_loop_header = call_block_itr->GetLoopMergeInst() != nullptr;
587
588
  // Single-trip loop continue block
589
35.4k
  std::unique_ptr<BasicBlock> single_trip_loop_cont_blk;
590
591
35.4k
  Function* calleeFn = id2function_[call_inst_itr->GetSingleWordOperand(
592
35.4k
      kSpvFunctionCallFunctionId)];
593
594
  // Map parameters to actual arguments.
595
35.4k
  MapParams(calleeFn, call_inst_itr, &callee2caller);
596
597
  // Define caller local variables for all callee variables and create map to
598
  // them.
599
35.4k
  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
35.4k
  uint32_t entry_blk_label_id = calleeFn->begin()->GetLabelInst()->result_id();
606
35.4k
  callee2caller[entry_blk_label_id] = call_block_itr->id();
607
35.4k
  std::unique_ptr<BasicBlock> new_blk_ptr =
608
35.4k
      MakeUnique<BasicBlock>(NewLabel(call_block_itr->id()));
609
610
  // Move instructions of original caller block up to call instruction.
611
35.4k
  MoveInstsBeforeEntryBlock(&preCallSB, new_blk_ptr.get(), call_inst_itr,
612
35.4k
                            call_block_itr);
613
614
35.4k
  if (caller_is_loop_header &&
615
9
      (*(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
8
    new_blk_ptr = AddGuardBlock(new_blocks, &callee2caller,
624
8
                                std::move(new_blk_ptr), entry_blk_label_id);
625
8
    if (new_blk_ptr == nullptr) return false;
626
8
  }
627
628
  // Create return var if needed.
629
35.4k
  const uint32_t calleeTypeId = calleeFn->type_id();
630
35.4k
  uint32_t returnVarId = 0;
631
35.4k
  analysis::Type* calleeType = context()->get_type_mgr()->GetType(calleeTypeId);
632
35.4k
  if (calleeType->AsVoid() == nullptr) {
633
16.2k
    returnVarId = CreateReturnVar(calleeFn, new_vars);
634
16.2k
    if (returnVarId == 0) {
635
0
      return false;
636
0
    }
637
16.2k
  }
638
639
1.75M
  calleeFn->WhileEachInst([&callee2caller, this](const Instruction* cpi) {
640
    // Create set of callee result ids. Used to detect forward references
641
1.75M
    const uint32_t rid = cpi->result_id();
642
1.75M
    if (rid != 0 && callee2caller.find(rid) == callee2caller.end()) {
643
997k
      const uint32_t nid = context()->TakeNextId();
644
997k
      if (nid == 0) return false;
645
997k
      callee2caller[rid] = nid;
646
997k
    }
647
1.75M
    return true;
648
1.75M
  });
649
650
  // Inline DebugClare instructions in the callee's header.
651
35.4k
  calleeFn->ForEachDebugInstructionsInHeader(
652
35.4k
      [&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
35.4k
  if (!InlineEntryBlock(callee2caller, &new_blk_ptr, calleeFn->begin(),
661
35.4k
                        &inlined_at_ctx)) {
662
0
    return false;
663
0
  }
664
665
  // Inline blocks of the callee function other than the entry block.
666
35.4k
  new_blk_ptr =
667
35.4k
      InlineBasicBlocks(new_blocks, callee2caller, std::move(new_blk_ptr),
668
35.4k
                        &inlined_at_ctx, calleeFn);
669
35.4k
  if (new_blk_ptr == nullptr) return false;
670
671
35.4k
  new_blk_ptr = InlineReturn(callee2caller, new_blocks, std::move(new_blk_ptr),
672
35.4k
                             &inlined_at_ctx, calleeFn,
673
35.4k
                             &*(calleeFn->tail()->tail()), returnVarId);
674
675
  // Load return value into result id of call, if it exists.
676
35.4k
  if (returnVarId != 0) {
677
16.2k
    const uint32_t resId = call_inst_itr->result_id();
678
16.2k
    assert(resId != 0);
679
16.2k
    AddLoad(calleeTypeId, resId, returnVarId, &new_blk_ptr,
680
16.2k
            call_inst_itr->dbg_line_inst(), call_inst_itr->GetDebugScope());
681
16.2k
  }
682
683
  // Move instructions of original caller block after call instruction.
684
35.4k
  if (!MoveCallerInstsAfterFunctionCall(&preCallSB, &postCallSB, &new_blk_ptr,
685
35.4k
                                        call_inst_itr,
686
35.4k
                                        calleeFn->begin() != calleeFn->end()))
687
0
    return false;
688
689
  // Finalize inline code.
690
35.4k
  new_blocks->push_back(std::move(new_blk_ptr));
691
692
35.4k
  if (caller_is_loop_header && (new_blocks->size() > 1)) {
693
8
    MoveLoopMergeInstToFirstBlock(new_blocks);
694
695
    // If the loop was a single basic block previously, update it's structure.
696
8
    auto& header = new_blocks->front();
697
8
    auto* merge_inst = header->GetLoopMergeInst();
698
8
    if (merge_inst->GetSingleWordInOperand(1u) == header->id()) {
699
8
      auto new_id = context()->TakeNextId();
700
8
      if (new_id == 0) return false;
701
8
      UpdateSingleBlockLoopContinueTarget(new_id, new_blocks);
702
8
    }
703
8
  }
704
705
  // Update block map given replacement blocks.
706
238k
  for (auto& blk : *new_blocks) {
707
238k
    id2block_[blk->id()] = &*blk;
708
238k
  }
709
710
  // We need to kill the name and decorations for the call, which will be
711
  // deleted.
712
35.4k
  context()->KillNamesAndDecorates(&*call_inst_itr);
713
714
35.4k
  return true;
715
35.4k
}
716
717
2.65M
bool InlinePass::IsInlinableFunctionCall(const Instruction* inst) {
718
2.65M
  if (inst->opcode() != spv::Op::OpFunctionCall) return false;
719
41.4k
  const uint32_t calleeFnId =
720
41.4k
      inst->GetSingleWordOperand(kSpvFunctionCallFunctionId);
721
41.4k
  const auto ci = inlinable_.find(calleeFnId);
722
41.4k
  if (ci == inlinable_.cend()) return false;
723
724
35.4k
  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
35.4k
  return true;
737
35.4k
}
738
739
void InlinePass::UpdateSucceedingPhis(
740
19.2k
    std::vector<std::unique_ptr<BasicBlock>>& new_blocks) {
741
19.2k
  const auto firstBlk = new_blocks.begin();
742
19.2k
  const auto lastBlk = new_blocks.end() - 1;
743
19.2k
  const uint32_t firstId = (*firstBlk)->id();
744
19.2k
  const uint32_t lastId = (*lastBlk)->id();
745
19.2k
  const BasicBlock& const_last_block = *lastBlk->get();
746
19.2k
  const_last_block.ForEachSuccessorLabel(
747
22.8k
      [&firstId, &lastId, this](const uint32_t succ) {
748
22.8k
        BasicBlock* sbp = this->id2block_[succ];
749
22.8k
        sbp->ForEachPhiInst([&firstId, &lastId](Instruction* phi) {
750
3.93k
          phi->ForEachInId([&firstId, &lastId](uint32_t* id) {
751
3.93k
            if (*id == firstId) *id = lastId;
752
3.93k
          });
753
983
        });
754
22.8k
      });
755
19.2k
}
756
757
19.7k
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
19.7k
  if (!context()->get_feature_mgr()->HasCapability(spv::Capability::Shader))
761
19
    return false;
762
19.7k
  const auto structured_analysis = context()->GetStructuredCFGAnalysis();
763
  // Search for returns in structured construct.
764
19.7k
  bool return_in_loop = false;
765
233k
  for (auto& blk : *func) {
766
233k
    auto terminal_ii = blk.cend();
767
233k
    --terminal_ii;
768
233k
    if (spvOpcodeIsReturn(terminal_ii->opcode()) &&
769
18.1k
        structured_analysis->ContainingLoop(blk.id()) != 0) {
770
157
      return_in_loop = true;
771
157
      break;
772
157
    }
773
233k
  }
774
19.7k
  return !return_in_loop;
775
19.7k
}
776
777
19.7k
void InlinePass::AnalyzeReturns(Function* func) {
778
  // Analyze functions without a return in loop.
779
19.7k
  if (HasNoReturnInLoop(func)) {
780
19.5k
    no_return_in_loop_.insert(func->result_id());
781
19.5k
  }
782
  // Analyze functions with a return before its tail basic block.
783
231k
  for (auto& blk : *func) {
784
231k
    auto terminal_ii = blk.cend();
785
231k
    --terminal_ii;
786
231k
    if (spvOpcodeIsReturn(terminal_ii->opcode()) && &blk != func->tail()) {
787
431
      early_return_funcs_.insert(func->result_id());
788
431
      break;
789
431
    }
790
231k
  }
791
19.7k
}
792
793
23.7k
bool InlinePass::IsInlinableFunction(Function* func) {
794
  // We can only inline a function if it has blocks.
795
23.7k
  if (func->cbegin() == func->cend()) return false;
796
797
  // Do not inline functions with DontInline flag.
798
23.7k
  if (func->control_mask() & uint32_t(spv::FunctionControlMask::DontInline)) {
799
3.99k
    return false;
800
3.99k
  }
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
19.7k
  AnalyzeReturns(func);
808
19.7k
  if (no_return_in_loop_.find(func->result_id()) == no_return_in_loop_.cend()) {
809
176
    return false;
810
176
  }
811
812
19.5k
  if (func->IsRecursive()) {
813
35
    return false;
814
35
  }
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
19.5k
  bool func_is_called_from_continue =
822
19.5k
      funcs_called_from_continue_.count(func->result_id()) != 0;
823
824
19.5k
  if (func_is_called_from_continue && ContainsAbortOtherThanUnreachable(func)) {
825
10
    return false;
826
10
  }
827
828
19.5k
  return true;
829
19.5k
}
830
831
80
bool InlinePass::ContainsAbortOtherThanUnreachable(Function* func) const {
832
2.02k
  return !func->WhileEachInst([](Instruction* inst) {
833
2.02k
    return inst->opcode() == spv::Op::OpUnreachable ||
834
2.00k
           !spvOpcodeIsAbort(inst->opcode());
835
2.02k
  });
836
80
}
837
838
16.1k
void InlinePass::InitializeInline() {
839
16.1k
  false_id_ = 0;
840
841
  // clear collections
842
16.1k
  id2function_.clear();
843
16.1k
  id2block_.clear();
844
16.1k
  inlinable_.clear();
845
16.1k
  no_return_in_loop_.clear();
846
16.1k
  early_return_funcs_.clear();
847
16.1k
  funcs_called_from_continue_ =
848
16.1k
      context()->GetStructuredCFGAnalysis()->FindFuncsCalledFromContinue();
849
850
23.7k
  for (auto& fn : *get_module()) {
851
    // Initialize function and block maps.
852
23.7k
    id2function_[fn.result_id()] = &fn;
853
251k
    for (auto& blk : fn) {
854
251k
      id2block_[blk.id()] = &blk;
855
251k
    }
856
    // Compute inlinability
857
23.7k
    if (IsInlinableFunction(&fn)) inlinable_.insert(fn.result_id());
858
23.7k
  }
859
16.1k
}
860
861
29.9k
InlinePass::InlinePass() {}
862
863
3.35k
void InlinePass::FixDebugDeclares(Function* func) {
864
3.35k
  std::map<uint32_t, Instruction*> access_chains;
865
3.35k
  std::vector<Instruction*> debug_declare_insts;
866
867
1.92M
  func->ForEachInst([&access_chains, &debug_declare_insts](Instruction* inst) {
868
1.92M
    if (inst->opcode() == spv::Op::OpAccessChain) {
869
179k
      access_chains[inst->result_id()] = inst;
870
179k
    }
871
1.92M
    if (inst->GetCommonDebugOpcode() == CommonDebugInfoDebugDeclare) {
872
0
      debug_declare_insts.push_back(inst);
873
0
    }
874
1.92M
  });
875
876
3.35k
  for (auto& inst : debug_declare_insts) {
877
0
    FixDebugDeclare(inst, access_chains);
878
0
  }
879
3.35k
}
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
0
    std::vector<Operand> operands;
902
0
    for (int i = 0; i < kSpvDebugDeclareVarInIdx; i++) {
903
0
      operands.push_back(dbg_declare_inst->GetInOperand(i));
904
0
    }
905
906
0
    uint32_t access_chain_base =
907
0
        access_chain->GetSingleWordInOperand(kSpvAccessChainBaseInIdx);
908
0
    operands.push_back(Operand(SPV_OPERAND_TYPE_ID, {access_chain_base}));
909
0
    operands.push_back(
910
0
        dbg_declare_inst->GetInOperand(kSpvDebugDeclareVarInIdx + 1));
911
912
0
    for (uint32_t i = kSpvAccessChainBaseInIdx + 1;
913
0
         i < access_chain->NumInOperands(); ++i) {
914
0
      operands.push_back(access_chain->GetInOperand(i));
915
0
    }
916
917
0
    for (uint32_t i = kSpvDebugDeclareVarInIdx + 2;
918
0
         i < dbg_declare_inst->NumInOperands(); ++i) {
919
0
      operands.push_back(dbg_declare_inst->GetInOperand(i));
920
0
    }
921
922
0
    dbg_declare_inst->SetInOperands(std::move(operands));
923
0
  } while (true);
924
0
}
925
926
}  // namespace opt
927
}  // namespace spvtools