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