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
AddPointerToType(uint32_t type_id,spv::StorageClass storage_class)37 uint32_t InlinePass::AddPointerToType(uint32_t type_id,
38 spv::StorageClass storage_class) {
39 uint32_t resultId = context()->TakeNextId();
40 if (resultId == 0) {
41 return resultId;
42 }
43
44 std::unique_ptr<Instruction> type_inst(
45 new Instruction(context(), spv::Op::OpTypePointer, 0, resultId,
46 {{spv_operand_type_t::SPV_OPERAND_TYPE_STORAGE_CLASS,
47 {uint32_t(storage_class)}},
48 {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {type_id}}}));
49 context()->AddType(std::move(type_inst));
50 analysis::Type* pointeeTy;
51 std::unique_ptr<analysis::Pointer> pointerTy;
52 std::tie(pointeeTy, pointerTy) =
53 context()->get_type_mgr()->GetTypeAndPointerType(
54 type_id, spv::StorageClass::Function);
55 context()->get_type_mgr()->RegisterType(resultId, *pointerTy);
56 return resultId;
57 }
58
AddBranch(uint32_t label_id,std::unique_ptr<BasicBlock> * block_ptr)59 void InlinePass::AddBranch(uint32_t label_id,
60 std::unique_ptr<BasicBlock>* block_ptr) {
61 std::unique_ptr<Instruction> newBranch(
62 new Instruction(context(), spv::Op::OpBranch, 0, 0,
63 {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {label_id}}}));
64 (*block_ptr)->AddInstruction(std::move(newBranch));
65 }
66
AddBranchCond(uint32_t cond_id,uint32_t true_id,uint32_t false_id,std::unique_ptr<BasicBlock> * block_ptr)67 void InlinePass::AddBranchCond(uint32_t cond_id, uint32_t true_id,
68 uint32_t false_id,
69 std::unique_ptr<BasicBlock>* block_ptr) {
70 std::unique_ptr<Instruction> newBranch(
71 new Instruction(context(), spv::Op::OpBranchConditional, 0, 0,
72 {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {cond_id}},
73 {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {true_id}},
74 {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {false_id}}}));
75 (*block_ptr)->AddInstruction(std::move(newBranch));
76 }
77
AddLoopMerge(uint32_t merge_id,uint32_t continue_id,std::unique_ptr<BasicBlock> * block_ptr)78 void InlinePass::AddLoopMerge(uint32_t merge_id, uint32_t continue_id,
79 std::unique_ptr<BasicBlock>* block_ptr) {
80 std::unique_ptr<Instruction> newLoopMerge(new Instruction(
81 context(), spv::Op::OpLoopMerge, 0, 0,
82 {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {merge_id}},
83 {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {continue_id}},
84 {spv_operand_type_t::SPV_OPERAND_TYPE_LOOP_CONTROL, {0}}}));
85 (*block_ptr)->AddInstruction(std::move(newLoopMerge));
86 }
87
AddStore(uint32_t ptr_id,uint32_t val_id,std::unique_ptr<BasicBlock> * block_ptr,const Instruction * line_inst,const DebugScope & dbg_scope)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 const DebugScope& dbg_scope) {
92 std::unique_ptr<Instruction> newStore(
93 new Instruction(context(), spv::Op::OpStore, 0, 0,
94 {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {ptr_id}},
95 {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {val_id}}}));
96 if (line_inst != nullptr) {
97 newStore->AddDebugLine(line_inst);
98 }
99 newStore->SetDebugScope(dbg_scope);
100 (*block_ptr)->AddInstruction(std::move(newStore));
101 }
102
AddLoad(uint32_t type_id,uint32_t resultId,uint32_t ptr_id,std::unique_ptr<BasicBlock> * block_ptr,const Instruction * line_inst,const DebugScope & dbg_scope)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 const DebugScope& dbg_scope) {
107 std::unique_ptr<Instruction> newLoad(
108 new Instruction(context(), spv::Op::OpLoad, type_id, resultId,
109 {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {ptr_id}}}));
110 if (line_inst != nullptr) {
111 newLoad->AddDebugLine(line_inst);
112 }
113 newLoad->SetDebugScope(dbg_scope);
114 (*block_ptr)->AddInstruction(std::move(newLoad));
115 }
116
NewLabel(uint32_t label_id)117 std::unique_ptr<Instruction> InlinePass::NewLabel(uint32_t label_id) {
118 std::unique_ptr<Instruction> newLabel(
119 new Instruction(context(), spv::Op::OpLabel, 0, label_id, {}));
120 return newLabel;
121 }
122
GetFalseId()123 uint32_t InlinePass::GetFalseId() {
124 if (false_id_ != 0) return false_id_;
125 false_id_ = get_module()->GetGlobalValue(spv::Op::OpConstantFalse);
126 if (false_id_ != 0) return false_id_;
127 uint32_t boolId = get_module()->GetGlobalValue(spv::Op::OpTypeBool);
128 if (boolId == 0) {
129 boolId = context()->TakeNextId();
130 if (boolId == 0) {
131 return 0;
132 }
133 get_module()->AddGlobalValue(spv::Op::OpTypeBool, boolId, 0);
134 }
135 false_id_ = context()->TakeNextId();
136 if (false_id_ == 0) {
137 return 0;
138 }
139 get_module()->AddGlobalValue(spv::Op::OpConstantFalse, false_id_, boolId);
140 return false_id_;
141 }
142
MapParams(Function * calleeFn,BasicBlock::iterator call_inst_itr,std::unordered_map<uint32_t,uint32_t> * callee2caller)143 void InlinePass::MapParams(
144 Function* calleeFn, BasicBlock::iterator call_inst_itr,
145 std::unordered_map<uint32_t, uint32_t>* callee2caller) {
146 int param_idx = 0;
147 calleeFn->ForEachParam(
148 [&call_inst_itr, ¶m_idx, &callee2caller](const Instruction* cpi) {
149 const uint32_t pid = cpi->result_id();
150 (*callee2caller)[pid] = call_inst_itr->GetSingleWordOperand(
151 kSpvFunctionCallArgumentId + param_idx);
152 ++param_idx;
153 });
154 }
155
CloneAndMapLocals(Function * calleeFn,std::vector<std::unique_ptr<Instruction>> * new_vars,std::unordered_map<uint32_t,uint32_t> * callee2caller,analysis::DebugInlinedAtContext * inlined_at_ctx)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 analysis::DebugInlinedAtContext* inlined_at_ctx) {
160 auto callee_block_itr = calleeFn->begin();
161 auto callee_var_itr = callee_block_itr->begin();
162 while (callee_var_itr->opcode() == spv::Op::OpVariable ||
163 callee_var_itr->GetCommonDebugOpcode() ==
164 CommonDebugInfoDebugDeclare) {
165 if (callee_var_itr->opcode() != spv::Op::OpVariable) {
166 ++callee_var_itr;
167 continue;
168 }
169
170 std::unique_ptr<Instruction> var_inst(callee_var_itr->Clone(context()));
171 uint32_t newId = context()->TakeNextId();
172 if (newId == 0) {
173 return false;
174 }
175 get_decoration_mgr()->CloneDecorations(callee_var_itr->result_id(), newId);
176 var_inst->SetResultId(newId);
177 var_inst->UpdateDebugInlinedAt(
178 context()->get_debug_info_mgr()->BuildDebugInlinedAtChain(
179 callee_var_itr->GetDebugInlinedAt(), inlined_at_ctx));
180 (*callee2caller)[callee_var_itr->result_id()] = newId;
181 new_vars->push_back(std::move(var_inst));
182 ++callee_var_itr;
183 }
184 return true;
185 }
186
CreateReturnVar(Function * calleeFn,std::vector<std::unique_ptr<Instruction>> * new_vars)187 uint32_t InlinePass::CreateReturnVar(
188 Function* calleeFn, std::vector<std::unique_ptr<Instruction>>* new_vars) {
189 uint32_t returnVarId = 0;
190 const uint32_t calleeTypeId = calleeFn->type_id();
191 analysis::TypeManager* type_mgr = context()->get_type_mgr();
192 assert(type_mgr->GetType(calleeTypeId)->AsVoid() == nullptr &&
193 "Cannot create a return variable of type void.");
194 // Find or create ptr to callee return type.
195 uint32_t returnVarTypeId =
196 type_mgr->FindPointerToType(calleeTypeId, spv::StorageClass::Function);
197
198 if (returnVarTypeId == 0) {
199 returnVarTypeId =
200 AddPointerToType(calleeTypeId, spv::StorageClass::Function);
201 if (returnVarTypeId == 0) {
202 return 0;
203 }
204 }
205
206 // Add return var to new function scope variables.
207 returnVarId = context()->TakeNextId();
208 if (returnVarId == 0) {
209 return 0;
210 }
211
212 std::unique_ptr<Instruction> var_inst(new Instruction(
213 context(), spv::Op::OpVariable, returnVarTypeId, returnVarId,
214 {{spv_operand_type_t::SPV_OPERAND_TYPE_STORAGE_CLASS,
215 {(uint32_t)spv::StorageClass::Function}}}));
216 new_vars->push_back(std::move(var_inst));
217 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 auto const pointee_type =
222 type_mgr->GetType(returnVarTypeId)->AsPointer()->pointee_type();
223 if (pointee_type->AsPointer() != nullptr) {
224 if (pointee_type->AsPointer()->storage_class() ==
225 spv::StorageClass::PhysicalStorageBuffer) {
226 get_decoration_mgr()->AddDecoration(
227 returnVarId, uint32_t(spv::Decoration::AliasedPointer));
228 }
229 }
230
231 return returnVarId;
232 }
233
IsSameBlockOp(const Instruction * inst) const234 bool InlinePass::IsSameBlockOp(const Instruction* inst) const {
235 return inst->opcode() == spv::Op::OpSampledImage ||
236 inst->opcode() == spv::Op::OpImage;
237 }
238
CloneSameBlockOps(std::unique_ptr<Instruction> * inst,std::unordered_map<uint32_t,uint32_t> * postCallSB,std::unordered_map<uint32_t,Instruction * > * preCallSB,std::unique_ptr<BasicBlock> * block_ptr)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 std::unique_ptr<BasicBlock>* block_ptr) {
244 return (*inst)->WhileEachInId([&postCallSB, &preCallSB, &block_ptr,
245 this](uint32_t* iid) {
246 const auto mapItr = (*postCallSB).find(*iid);
247 if (mapItr == (*postCallSB).end()) {
248 const auto mapItr2 = (*preCallSB).find(*iid);
249 if (mapItr2 != (*preCallSB).end()) {
250 // Clone pre-call same-block ops, map result id.
251 const Instruction* inInst = mapItr2->second;
252 std::unique_ptr<Instruction> sb_inst(inInst->Clone(context()));
253 if (!CloneSameBlockOps(&sb_inst, postCallSB, preCallSB, block_ptr)) {
254 return false;
255 }
256
257 const uint32_t rid = sb_inst->result_id();
258 const uint32_t nid = context()->TakeNextId();
259 if (nid == 0) {
260 return false;
261 }
262 get_decoration_mgr()->CloneDecorations(rid, nid);
263 sb_inst->SetResultId(nid);
264 (*postCallSB)[rid] = nid;
265 *iid = nid;
266 (*block_ptr)->AddInstruction(std::move(sb_inst));
267 }
268 } else {
269 // Reset same-block op operand.
270 *iid = mapItr->second;
271 }
272 return true;
273 });
274 }
275
MoveInstsBeforeEntryBlock(std::unordered_map<uint32_t,Instruction * > * preCallSB,BasicBlock * new_blk_ptr,BasicBlock::iterator call_inst_itr,UptrVectorIterator<BasicBlock> call_block_itr)276 void InlinePass::MoveInstsBeforeEntryBlock(
277 std::unordered_map<uint32_t, Instruction*>* preCallSB,
278 BasicBlock* new_blk_ptr, BasicBlock::iterator call_inst_itr,
279 UptrVectorIterator<BasicBlock> call_block_itr) {
280 for (auto cii = call_block_itr->begin(); cii != call_inst_itr;
281 cii = call_block_itr->begin()) {
282 Instruction* inst = &*cii;
283 inst->RemoveFromList();
284 std::unique_ptr<Instruction> cp_inst(inst);
285 // Remember same-block ops for possible regeneration.
286 if (IsSameBlockOp(&*cp_inst)) {
287 auto* sb_inst_ptr = cp_inst.get();
288 (*preCallSB)[cp_inst->result_id()] = sb_inst_ptr;
289 }
290 new_blk_ptr->AddInstruction(std::move(cp_inst));
291 }
292 }
293
AddGuardBlock(std::vector<std::unique_ptr<BasicBlock>> * new_blocks,std::unordered_map<uint32_t,uint32_t> * callee2caller,std::unique_ptr<BasicBlock> new_blk_ptr,uint32_t entry_blk_label_id)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 std::unique_ptr<BasicBlock> new_blk_ptr, uint32_t entry_blk_label_id) {
298 const auto guard_block_id = context()->TakeNextId();
299 if (guard_block_id == 0) {
300 return nullptr;
301 }
302 AddBranch(guard_block_id, &new_blk_ptr);
303 new_blocks->push_back(std::move(new_blk_ptr));
304 // Start the next block.
305 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 (*callee2caller)[entry_blk_label_id] = guard_block_id;
310 return new_blk_ptr;
311 }
312
AddStoresForVariableInitializers(const std::unordered_map<uint32_t,uint32_t> & callee2caller,analysis::DebugInlinedAtContext * inlined_at_ctx,std::unique_ptr<BasicBlock> * new_blk_ptr,UptrVectorIterator<BasicBlock> callee_first_block_itr)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 UptrVectorIterator<BasicBlock> callee_first_block_itr) {
318 auto callee_itr = callee_first_block_itr->begin();
319 while (callee_itr->opcode() == spv::Op::OpVariable ||
320 callee_itr->GetCommonDebugOpcode() == CommonDebugInfoDebugDeclare) {
321 if (callee_itr->opcode() == spv::Op::OpVariable &&
322 callee_itr->NumInOperands() == 2) {
323 assert(callee2caller.count(callee_itr->result_id()) &&
324 "Expected the variable to have already been mapped.");
325 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 uint32_t val_id = callee_itr->GetSingleWordInOperand(1);
330 AddStore(new_var_id, val_id, new_blk_ptr, callee_itr->dbg_line_inst(),
331 context()->get_debug_info_mgr()->BuildDebugScope(
332 callee_itr->GetDebugScope(), inlined_at_ctx));
333 }
334 if (callee_itr->GetCommonDebugOpcode() == CommonDebugInfoDebugDeclare) {
335 InlineSingleInstruction(
336 callee2caller, new_blk_ptr->get(), &*callee_itr,
337 context()->get_debug_info_mgr()->BuildDebugInlinedAtChain(
338 callee_itr->GetDebugScope().GetInlinedAt(), inlined_at_ctx));
339 }
340 ++callee_itr;
341 }
342 return callee_itr;
343 }
344
InlineSingleInstruction(const std::unordered_map<uint32_t,uint32_t> & callee2caller,BasicBlock * new_blk_ptr,const Instruction * inst,uint32_t dbg_inlined_at)345 bool InlinePass::InlineSingleInstruction(
346 const std::unordered_map<uint32_t, uint32_t>& callee2caller,
347 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 if (inst->opcode() == spv::Op::OpReturnValue ||
351 inst->opcode() == spv::Op::OpReturn)
352 return true;
353
354 // Copy callee instruction and remap all input Ids.
355 std::unique_ptr<Instruction> cp_inst(inst->Clone(context()));
356 cp_inst->ForEachInId([&callee2caller](uint32_t* iid) {
357 const auto mapItr = callee2caller.find(*iid);
358 if (mapItr != callee2caller.end()) {
359 *iid = mapItr->second;
360 }
361 });
362
363 // If result id is non-zero, remap it.
364 const uint32_t rid = cp_inst->result_id();
365 if (rid != 0) {
366 const auto mapItr = callee2caller.find(rid);
367 if (mapItr == callee2caller.end()) {
368 return false;
369 }
370 uint32_t nid = mapItr->second;
371 cp_inst->SetResultId(nid);
372 get_decoration_mgr()->CloneDecorations(rid, nid);
373 }
374
375 cp_inst->UpdateDebugInlinedAt(dbg_inlined_at);
376 new_blk_ptr->AddInstruction(std::move(cp_inst));
377 return true;
378 }
379
InlineReturn(const std::unordered_map<uint32_t,uint32_t> & callee2caller,std::vector<std::unique_ptr<BasicBlock>> * new_blocks,std::unique_ptr<BasicBlock> new_blk_ptr,analysis::DebugInlinedAtContext * inlined_at_ctx,Function * calleeFn,const Instruction * inst,uint32_t returnVarId)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 const Instruction* inst, uint32_t returnVarId) {
386 // Store return value to return variable.
387 if (inst->opcode() == spv::Op::OpReturnValue) {
388 assert(returnVarId != 0);
389 uint32_t valId = inst->GetInOperand(kSpvReturnValueId).words[0];
390 const auto mapItr = callee2caller.find(valId);
391 if (mapItr != callee2caller.end()) {
392 valId = mapItr->second;
393 }
394 AddStore(returnVarId, valId, &new_blk_ptr, inst->dbg_line_inst(),
395 context()->get_debug_info_mgr()->BuildDebugScope(
396 inst->GetDebugScope(), inlined_at_ctx));
397 }
398
399 uint32_t returnLabelId = 0;
400 for (auto callee_block_itr = calleeFn->begin();
401 callee_block_itr != calleeFn->end(); ++callee_block_itr) {
402 if (spvOpcodeIsAbort(callee_block_itr->tail()->opcode())) {
403 returnLabelId = context()->TakeNextId();
404 break;
405 }
406 }
407 if (returnLabelId == 0) return new_blk_ptr;
408
409 if (inst->opcode() == spv::Op::OpReturn ||
410 inst->opcode() == spv::Op::OpReturnValue)
411 AddBranch(returnLabelId, &new_blk_ptr);
412 new_blocks->push_back(std::move(new_blk_ptr));
413 return MakeUnique<BasicBlock>(NewLabel(returnLabelId));
414 }
415
InlineEntryBlock(const std::unordered_map<uint32_t,uint32_t> & callee2caller,std::unique_ptr<BasicBlock> * new_blk_ptr,UptrVectorIterator<BasicBlock> callee_first_block,analysis::DebugInlinedAtContext * inlined_at_ctx)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 analysis::DebugInlinedAtContext* inlined_at_ctx) {
421 auto callee_inst_itr = AddStoresForVariableInitializers(
422 callee2caller, inlined_at_ctx, new_blk_ptr, callee_first_block);
423
424 while (callee_inst_itr != callee_first_block->end()) {
425 // Don't inline function definition links, the calling function is not a
426 // definition.
427 if (callee_inst_itr->GetShader100DebugOpcode() ==
428 NonSemanticShaderDebugInfo100DebugFunctionDefinition) {
429 ++callee_inst_itr;
430 continue;
431 }
432
433 if (!InlineSingleInstruction(
434 callee2caller, new_blk_ptr->get(), &*callee_inst_itr,
435 context()->get_debug_info_mgr()->BuildDebugInlinedAtChain(
436 callee_inst_itr->GetDebugScope().GetInlinedAt(),
437 inlined_at_ctx))) {
438 return false;
439 }
440 ++callee_inst_itr;
441 }
442 return true;
443 }
444
InlineBasicBlocks(std::vector<std::unique_ptr<BasicBlock>> * new_blocks,const std::unordered_map<uint32_t,uint32_t> & callee2caller,std::unique_ptr<BasicBlock> new_blk_ptr,analysis::DebugInlinedAtContext * inlined_at_ctx,Function * calleeFn)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 analysis::DebugInlinedAtContext* inlined_at_ctx, Function* calleeFn) {
450 auto callee_block_itr = calleeFn->begin();
451 ++callee_block_itr;
452
453 while (callee_block_itr != calleeFn->end()) {
454 new_blocks->push_back(std::move(new_blk_ptr));
455 const auto mapItr =
456 callee2caller.find(callee_block_itr->GetLabelInst()->result_id());
457 if (mapItr == callee2caller.end()) return nullptr;
458 new_blk_ptr = MakeUnique<BasicBlock>(NewLabel(mapItr->second));
459
460 auto tail_inst_itr = callee_block_itr->end();
461 for (auto inst_itr = callee_block_itr->begin(); inst_itr != tail_inst_itr;
462 ++inst_itr) {
463 // Don't inline function definition links, the calling function is not a
464 // definition
465 if (inst_itr->GetShader100DebugOpcode() ==
466 NonSemanticShaderDebugInfo100DebugFunctionDefinition)
467 continue;
468 if (!InlineSingleInstruction(
469 callee2caller, new_blk_ptr.get(), &*inst_itr,
470 context()->get_debug_info_mgr()->BuildDebugInlinedAtChain(
471 inst_itr->GetDebugScope().GetInlinedAt(), inlined_at_ctx))) {
472 return nullptr;
473 }
474 }
475
476 ++callee_block_itr;
477 }
478 return new_blk_ptr;
479 }
480
MoveCallerInstsAfterFunctionCall(std::unordered_map<uint32_t,Instruction * > * preCallSB,std::unordered_map<uint32_t,uint32_t> * postCallSB,std::unique_ptr<BasicBlock> * new_blk_ptr,BasicBlock::iterator call_inst_itr,bool multiBlocks)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 BasicBlock::iterator call_inst_itr, bool multiBlocks) {
486 // Copy remaining instructions from caller block.
487 for (Instruction* inst = call_inst_itr->NextNode(); inst;
488 inst = call_inst_itr->NextNode()) {
489 inst->RemoveFromList();
490 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 if (multiBlocks) {
494 if (!CloneSameBlockOps(&cp_inst, postCallSB, preCallSB, new_blk_ptr)) {
495 return false;
496 }
497
498 // Remember same-block ops in this block.
499 if (IsSameBlockOp(&*cp_inst)) {
500 const uint32_t rid = cp_inst->result_id();
501 (*postCallSB)[rid] = rid;
502 }
503 }
504 new_blk_ptr->get()->AddInstruction(std::move(cp_inst));
505 }
506
507 return true;
508 }
509
MoveLoopMergeInstToFirstBlock(std::vector<std::unique_ptr<BasicBlock>> * new_blocks)510 void InlinePass::MoveLoopMergeInstToFirstBlock(
511 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 auto& first = new_blocks->front();
515 auto& last = new_blocks->back();
516 assert(first != last);
517
518 // Insert a modified copy of the loop merge into the first block.
519 auto loop_merge_itr = last->tail();
520 --loop_merge_itr;
521 assert(loop_merge_itr->opcode() == spv::Op::OpLoopMerge);
522 std::unique_ptr<Instruction> cp_inst(loop_merge_itr->Clone(context()));
523 first->tail().InsertBefore(std::move(cp_inst));
524
525 // Remove the loop merge from the last block.
526 loop_merge_itr->RemoveFromList();
527 delete &*loop_merge_itr;
528 }
529
UpdateSingleBlockLoopContinueTarget(uint32_t new_id,std::vector<std::unique_ptr<BasicBlock>> * new_blocks)530 void InlinePass::UpdateSingleBlockLoopContinueTarget(
531 uint32_t new_id, std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
532 auto& header = new_blocks->front();
533 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 std::unique_ptr<BasicBlock> new_block =
545 MakeUnique<BasicBlock>(NewLabel(new_id));
546 auto& old_backedge = new_blocks->back();
547 auto old_branch = old_backedge->tail();
548
549 // Move the old back edge into the new block.
550 std::unique_ptr<Instruction> br(&*old_branch);
551 new_block->AddInstruction(std::move(br));
552
553 // Add a branch to the new block from the old back-edge block.
554 AddBranch(new_id, &old_backedge);
555 new_blocks->push_back(std::move(new_block));
556
557 // Update the loop's continue target to the new block.
558 merge_inst->SetInOperand(1u, {new_id});
559 }
560
GenInlineCode(std::vector<std::unique_ptr<BasicBlock>> * new_blocks,std::vector<std::unique_ptr<Instruction>> * new_vars,BasicBlock::iterator call_inst_itr,UptrVectorIterator<BasicBlock> call_block_itr)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 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 std::unordered_map<uint32_t, uint32_t> callee2caller;
569 // Pre-call same-block insts
570 std::unordered_map<uint32_t, Instruction*> preCallSB;
571 // Post-call same-block op ids
572 std::unordered_map<uint32_t, uint32_t> postCallSB;
573
574 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 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 bool caller_is_loop_header = call_block_itr->GetLoopMergeInst() != nullptr;
587
588 // Single-trip loop continue block
589 std::unique_ptr<BasicBlock> single_trip_loop_cont_blk;
590
591 Function* calleeFn = id2function_[call_inst_itr->GetSingleWordOperand(
592 kSpvFunctionCallFunctionId)];
593
594 // Map parameters to actual arguments.
595 MapParams(calleeFn, call_inst_itr, &callee2caller);
596
597 // Define caller local variables for all callee variables and create map to
598 // them.
599 if (!CloneAndMapLocals(calleeFn, new_vars, &callee2caller, &inlined_at_ctx)) {
600 return false;
601 }
602
603 // First block needs to use label of original block
604 // but map callee label in case of phi reference.
605 uint32_t entry_blk_label_id = calleeFn->begin()->GetLabelInst()->result_id();
606 callee2caller[entry_blk_label_id] = call_block_itr->id();
607 std::unique_ptr<BasicBlock> new_blk_ptr =
608 MakeUnique<BasicBlock>(NewLabel(call_block_itr->id()));
609
610 // Move instructions of original caller block up to call instruction.
611 MoveInstsBeforeEntryBlock(&preCallSB, new_blk_ptr.get(), call_inst_itr,
612 call_block_itr);
613
614 if (caller_is_loop_header &&
615 (*(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 new_blk_ptr = AddGuardBlock(new_blocks, &callee2caller,
624 std::move(new_blk_ptr), entry_blk_label_id);
625 if (new_blk_ptr == nullptr) return false;
626 }
627
628 // Create return var if needed.
629 const uint32_t calleeTypeId = calleeFn->type_id();
630 uint32_t returnVarId = 0;
631 analysis::Type* calleeType = context()->get_type_mgr()->GetType(calleeTypeId);
632 if (calleeType->AsVoid() == nullptr) {
633 returnVarId = CreateReturnVar(calleeFn, new_vars);
634 if (returnVarId == 0) {
635 return false;
636 }
637 }
638
639 calleeFn->WhileEachInst([&callee2caller, this](const Instruction* cpi) {
640 // Create set of callee result ids. Used to detect forward references
641 const uint32_t rid = cpi->result_id();
642 if (rid != 0 && callee2caller.find(rid) == callee2caller.end()) {
643 const uint32_t nid = context()->TakeNextId();
644 if (nid == 0) return false;
645 callee2caller[rid] = nid;
646 }
647 return true;
648 });
649
650 // Inline DebugClare instructions in the callee's header.
651 calleeFn->ForEachDebugInstructionsInHeader(
652 [&new_blk_ptr, &callee2caller, &inlined_at_ctx, this](Instruction* inst) {
653 InlineSingleInstruction(
654 callee2caller, new_blk_ptr.get(), inst,
655 context()->get_debug_info_mgr()->BuildDebugInlinedAtChain(
656 inst->GetDebugScope().GetInlinedAt(), &inlined_at_ctx));
657 });
658
659 // Inline the entry block of the callee function.
660 if (!InlineEntryBlock(callee2caller, &new_blk_ptr, calleeFn->begin(),
661 &inlined_at_ctx)) {
662 return false;
663 }
664
665 // Inline blocks of the callee function other than the entry block.
666 new_blk_ptr =
667 InlineBasicBlocks(new_blocks, callee2caller, std::move(new_blk_ptr),
668 &inlined_at_ctx, calleeFn);
669 if (new_blk_ptr == nullptr) return false;
670
671 new_blk_ptr = InlineReturn(callee2caller, new_blocks, std::move(new_blk_ptr),
672 &inlined_at_ctx, calleeFn,
673 &*(calleeFn->tail()->tail()), returnVarId);
674
675 // Load return value into result id of call, if it exists.
676 if (returnVarId != 0) {
677 const uint32_t resId = call_inst_itr->result_id();
678 assert(resId != 0);
679 AddLoad(calleeTypeId, resId, returnVarId, &new_blk_ptr,
680 call_inst_itr->dbg_line_inst(), call_inst_itr->GetDebugScope());
681 }
682
683 // Move instructions of original caller block after call instruction.
684 if (!MoveCallerInstsAfterFunctionCall(&preCallSB, &postCallSB, &new_blk_ptr,
685 call_inst_itr,
686 calleeFn->begin() != calleeFn->end()))
687 return false;
688
689 // Finalize inline code.
690 new_blocks->push_back(std::move(new_blk_ptr));
691
692 if (caller_is_loop_header && (new_blocks->size() > 1)) {
693 MoveLoopMergeInstToFirstBlock(new_blocks);
694
695 // If the loop was a single basic block previously, update it's structure.
696 auto& header = new_blocks->front();
697 auto* merge_inst = header->GetLoopMergeInst();
698 if (merge_inst->GetSingleWordInOperand(1u) == header->id()) {
699 auto new_id = context()->TakeNextId();
700 if (new_id == 0) return false;
701 UpdateSingleBlockLoopContinueTarget(new_id, new_blocks);
702 }
703 }
704
705 // Update block map given replacement blocks.
706 for (auto& blk : *new_blocks) {
707 id2block_[blk->id()] = &*blk;
708 }
709
710 // We need to kill the name and decorations for the call, which will be
711 // deleted.
712 context()->KillNamesAndDecorates(&*call_inst_itr);
713
714 return true;
715 }
716
IsInlinableFunctionCall(const Instruction * inst)717 bool InlinePass::IsInlinableFunctionCall(const Instruction* inst) {
718 if (inst->opcode() != spv::Op::OpFunctionCall) return false;
719 const uint32_t calleeFnId =
720 inst->GetSingleWordOperand(kSpvFunctionCallFunctionId);
721 const auto ci = inlinable_.find(calleeFnId);
722 if (ci == inlinable_.cend()) return false;
723
724 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 std::string message =
728 "The function '" + id2function_[calleeFnId]->DefInst().PrettyPrint() +
729 "' could not be inlined because the return instruction "
730 "is not at the end of the function. This could be fixed by "
731 "running merge-return before inlining.";
732 consumer()(SPV_MSG_WARNING, "", {0, 0, 0}, message.c_str());
733 return false;
734 }
735
736 return true;
737 }
738
UpdateSucceedingPhis(std::vector<std::unique_ptr<BasicBlock>> & new_blocks)739 void InlinePass::UpdateSucceedingPhis(
740 std::vector<std::unique_ptr<BasicBlock>>& new_blocks) {
741 const auto firstBlk = new_blocks.begin();
742 const auto lastBlk = new_blocks.end() - 1;
743 const uint32_t firstId = (*firstBlk)->id();
744 const uint32_t lastId = (*lastBlk)->id();
745 const BasicBlock& const_last_block = *lastBlk->get();
746 const_last_block.ForEachSuccessorLabel(
747 [&firstId, &lastId, this](const uint32_t succ) {
748 BasicBlock* sbp = this->id2block_[succ];
749 sbp->ForEachPhiInst([&firstId, &lastId](Instruction* phi) {
750 phi->ForEachInId([&firstId, &lastId](uint32_t* id) {
751 if (*id == firstId) *id = lastId;
752 });
753 });
754 });
755 }
756
HasNoReturnInLoop(Function * func)757 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 if (!context()->get_feature_mgr()->HasCapability(spv::Capability::Shader))
761 return false;
762 const auto structured_analysis = context()->GetStructuredCFGAnalysis();
763 // Search for returns in structured construct.
764 bool return_in_loop = false;
765 for (auto& blk : *func) {
766 auto terminal_ii = blk.cend();
767 --terminal_ii;
768 if (spvOpcodeIsReturn(terminal_ii->opcode()) &&
769 structured_analysis->ContainingLoop(blk.id()) != 0) {
770 return_in_loop = true;
771 break;
772 }
773 }
774 return !return_in_loop;
775 }
776
AnalyzeReturns(Function * func)777 void InlinePass::AnalyzeReturns(Function* func) {
778 // Analyze functions without a return in loop.
779 if (HasNoReturnInLoop(func)) {
780 no_return_in_loop_.insert(func->result_id());
781 }
782 // Analyze functions with a return before its tail basic block.
783 for (auto& blk : *func) {
784 auto terminal_ii = blk.cend();
785 --terminal_ii;
786 if (spvOpcodeIsReturn(terminal_ii->opcode()) && &blk != func->tail()) {
787 early_return_funcs_.insert(func->result_id());
788 break;
789 }
790 }
791 }
792
IsInlinableFunction(Function * func)793 bool InlinePass::IsInlinableFunction(Function* func) {
794 // We can only inline a function if it has blocks.
795 if (func->cbegin() == func->cend()) return false;
796
797 // Do not inline functions with DontInline flag.
798 if (func->control_mask() & uint32_t(spv::FunctionControlMask::DontInline)) {
799 return false;
800 }
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 AnalyzeReturns(func);
808 if (no_return_in_loop_.find(func->result_id()) == no_return_in_loop_.cend()) {
809 return false;
810 }
811
812 if (func->IsRecursive()) {
813 return false;
814 }
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 bool func_is_called_from_continue =
822 funcs_called_from_continue_.count(func->result_id()) != 0;
823
824 if (func_is_called_from_continue && ContainsAbortOtherThanUnreachable(func)) {
825 return false;
826 }
827
828 return true;
829 }
830
ContainsAbortOtherThanUnreachable(Function * func) const831 bool InlinePass::ContainsAbortOtherThanUnreachable(Function* func) const {
832 return !func->WhileEachInst([](Instruction* inst) {
833 return inst->opcode() == spv::Op::OpUnreachable ||
834 !spvOpcodeIsAbort(inst->opcode());
835 });
836 }
837
InitializeInline()838 void InlinePass::InitializeInline() {
839 false_id_ = 0;
840
841 // clear collections
842 id2function_.clear();
843 id2block_.clear();
844 inlinable_.clear();
845 no_return_in_loop_.clear();
846 early_return_funcs_.clear();
847 funcs_called_from_continue_ =
848 context()->GetStructuredCFGAnalysis()->FindFuncsCalledFromContinue();
849
850 for (auto& fn : *get_module()) {
851 // Initialize function and block maps.
852 id2function_[fn.result_id()] = &fn;
853 for (auto& blk : fn) {
854 id2block_[blk.id()] = &blk;
855 }
856 // Compute inlinability
857 if (IsInlinableFunction(&fn)) inlinable_.insert(fn.result_id());
858 }
859 }
860
InlinePass()861 InlinePass::InlinePass() {}
862
FixDebugDeclares(Function * func)863 void InlinePass::FixDebugDeclares(Function* func) {
864 std::map<uint32_t, Instruction*> access_chains;
865 std::vector<Instruction*> debug_declare_insts;
866
867 func->ForEachInst([&access_chains, &debug_declare_insts](Instruction* inst) {
868 if (inst->opcode() == spv::Op::OpAccessChain) {
869 access_chains[inst->result_id()] = inst;
870 }
871 if (inst->GetCommonDebugOpcode() == CommonDebugInfoDebugDeclare) {
872 debug_declare_insts.push_back(inst);
873 }
874 });
875
876 for (auto& inst : debug_declare_insts) {
877 FixDebugDeclare(inst, access_chains);
878 }
879 }
880
FixDebugDeclare(Instruction * dbg_declare_inst,const std::map<uint32_t,Instruction * > & access_chains)881 void InlinePass::FixDebugDeclare(
882 Instruction* dbg_declare_inst,
883 const std::map<uint32_t, Instruction*>& access_chains) {
884 do {
885 uint32_t var_id =
886 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 auto it = access_chains.find(var_id);
891 if (it == access_chains.end()) {
892 return;
893 }
894 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 std::vector<Operand> operands;
902 for (int i = 0; i < kSpvDebugDeclareVarInIdx; i++) {
903 operands.push_back(dbg_declare_inst->GetInOperand(i));
904 }
905
906 uint32_t access_chain_base =
907 access_chain->GetSingleWordInOperand(kSpvAccessChainBaseInIdx);
908 operands.push_back(Operand(SPV_OPERAND_TYPE_ID, {access_chain_base}));
909 operands.push_back(
910 dbg_declare_inst->GetInOperand(kSpvDebugDeclareVarInIdx + 1));
911
912 for (uint32_t i = kSpvAccessChainBaseInIdx + 1;
913 i < access_chain->NumInOperands(); ++i) {
914 operands.push_back(access_chain->GetInOperand(i));
915 }
916
917 for (uint32_t i = kSpvDebugDeclareVarInIdx + 2;
918 i < dbg_declare_inst->NumInOperands(); ++i) {
919 operands.push_back(dbg_declare_inst->GetInOperand(i));
920 }
921
922 dbg_declare_inst->SetInOperands(std::move(operands));
923 } while (true);
924 }
925
926 } // namespace opt
927 } // namespace spvtools
928