/src/spirv-tools/source/opt/desc_sroa.cpp
Line | Count | Source |
1 | | // Copyright (c) 2019 Google LLC |
2 | | // |
3 | | // Licensed under the Apache License, Version 2.0 (the "License"); |
4 | | // you may not use this file except in compliance with the License. |
5 | | // You may obtain a copy of the License at |
6 | | // |
7 | | // http://www.apache.org/licenses/LICENSE-2.0 |
8 | | // |
9 | | // Unless required by applicable law or agreed to in writing, software |
10 | | // distributed under the License is distributed on an "AS IS" BASIS, |
11 | | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 | | // See the License for the specific language governing permissions and |
13 | | // limitations under the License. |
14 | | |
15 | | #include "source/opt/desc_sroa.h" |
16 | | |
17 | | #include "source/opt/desc_sroa_util.h" |
18 | | #include "source/util/string_utils.h" |
19 | | |
20 | | namespace spvtools { |
21 | | namespace opt { |
22 | | namespace { |
23 | | |
24 | 0 | bool IsDecorationBinding(Instruction* inst) { |
25 | 0 | if (inst->opcode() != spv::Op::OpDecorate) return false; |
26 | 0 | return spv::Decoration(inst->GetSingleWordInOperand(1u)) == |
27 | 0 | spv::Decoration::Binding; |
28 | 0 | } |
29 | | |
30 | | } // namespace |
31 | | |
32 | 0 | Pass::Status DescriptorScalarReplacement::Process() { |
33 | 0 | bool modified = false; |
34 | 0 | std::vector<Instruction*> vars_to_kill; |
35 | |
|
36 | 0 | for (Instruction& var : context()->types_values()) { |
37 | 0 | bool is_candidate = |
38 | 0 | flatten_arrays_ && descsroautil::IsDescriptorArray(context(), &var); |
39 | 0 | is_candidate |= flatten_composites_ && |
40 | 0 | descsroautil::IsDescriptorStruct(context(), &var); |
41 | 0 | if (is_candidate) { |
42 | 0 | modified = true; |
43 | 0 | if (!ReplaceCandidate(&var)) { |
44 | 0 | return Status::Failure; |
45 | 0 | } |
46 | 0 | vars_to_kill.push_back(&var); |
47 | 0 | } |
48 | 0 | } |
49 | | |
50 | 0 | for (Instruction* var : vars_to_kill) { |
51 | 0 | context()->KillInst(var); |
52 | 0 | } |
53 | |
|
54 | 0 | return (modified ? Status::SuccessWithChange : Status::SuccessWithoutChange); |
55 | 0 | } |
56 | | |
57 | 0 | bool DescriptorScalarReplacement::ReplaceCandidate(Instruction* var) { |
58 | 0 | std::vector<Instruction*> access_chain_work_list; |
59 | 0 | std::vector<Instruction*> load_work_list; |
60 | 0 | std::vector<Instruction*> entry_point_work_list; |
61 | 0 | bool ok = get_def_use_mgr()->WhileEachUser( |
62 | 0 | var->result_id(), [this, &access_chain_work_list, &load_work_list, |
63 | 0 | &entry_point_work_list](Instruction* use) { |
64 | 0 | if (use->opcode() == spv::Op::OpName) { |
65 | 0 | return true; |
66 | 0 | } |
67 | | |
68 | 0 | if (use->IsDecoration()) { |
69 | 0 | return true; |
70 | 0 | } |
71 | | |
72 | 0 | switch (use->opcode()) { |
73 | 0 | case spv::Op::OpAccessChain: |
74 | 0 | case spv::Op::OpInBoundsAccessChain: |
75 | 0 | access_chain_work_list.push_back(use); |
76 | 0 | return true; |
77 | 0 | case spv::Op::OpLoad: |
78 | 0 | load_work_list.push_back(use); |
79 | 0 | return true; |
80 | 0 | case spv::Op::OpEntryPoint: |
81 | 0 | entry_point_work_list.push_back(use); |
82 | 0 | return true; |
83 | 0 | default: |
84 | 0 | context()->EmitErrorMessage( |
85 | 0 | "Variable cannot be replaced: invalid instruction", use); |
86 | 0 | return false; |
87 | 0 | } |
88 | 0 | return true; |
89 | 0 | }); |
90 | |
|
91 | 0 | if (!ok) { |
92 | 0 | return false; |
93 | 0 | } |
94 | | |
95 | 0 | for (Instruction* use : access_chain_work_list) { |
96 | 0 | if (!ReplaceAccessChain(var, use)) { |
97 | 0 | return false; |
98 | 0 | } |
99 | 0 | } |
100 | 0 | for (Instruction* use : load_work_list) { |
101 | 0 | if (!ReplaceLoadedValue(var, use)) { |
102 | 0 | return false; |
103 | 0 | } |
104 | 0 | } |
105 | 0 | for (Instruction* use : entry_point_work_list) { |
106 | 0 | if (!ReplaceEntryPoint(var, use)) { |
107 | 0 | return false; |
108 | 0 | } |
109 | 0 | } |
110 | 0 | return true; |
111 | 0 | } |
112 | | |
113 | | bool DescriptorScalarReplacement::ReplaceAccessChain(Instruction* var, |
114 | 0 | Instruction* use) { |
115 | 0 | if (use->NumInOperands() <= 1) { |
116 | 0 | context()->EmitErrorMessage( |
117 | 0 | "Variable cannot be replaced: invalid instruction", use); |
118 | 0 | return false; |
119 | 0 | } |
120 | | |
121 | 0 | const analysis::Constant* const_index = |
122 | 0 | descsroautil::GetAccessChainIndexAsConst(context(), use); |
123 | 0 | if (const_index == nullptr) { |
124 | 0 | context()->EmitErrorMessage("Variable cannot be replaced: invalid index", |
125 | 0 | use); |
126 | 0 | return false; |
127 | 0 | } |
128 | | |
129 | 0 | uint32_t idx = const_index->GetU32(); |
130 | 0 | uint32_t replacement_var = GetReplacementVariable(var, idx); |
131 | 0 | if (replacement_var == 0) { |
132 | 0 | return false; |
133 | 0 | } |
134 | | |
135 | 0 | if (use->NumInOperands() == 2) { |
136 | | // We are not indexing into the replacement variable. We can replaces the |
137 | | // access chain with the replacement variable itself. |
138 | 0 | context()->ReplaceAllUsesWith(use->result_id(), replacement_var); |
139 | 0 | context()->KillInst(use); |
140 | 0 | return true; |
141 | 0 | } |
142 | | |
143 | | // We need to build a new access chain with the replacement variable as the |
144 | | // base address. |
145 | 0 | Instruction::OperandList new_operands; |
146 | | |
147 | | // Same result id and result type. |
148 | 0 | new_operands.emplace_back(use->GetOperand(0)); |
149 | 0 | new_operands.emplace_back(use->GetOperand(1)); |
150 | | |
151 | | // Use the replacement variable as the base address. |
152 | 0 | new_operands.push_back({SPV_OPERAND_TYPE_ID, {replacement_var}}); |
153 | | |
154 | | // Drop the first index because it is consumed by the replacement, and copy |
155 | | // the rest. |
156 | 0 | for (uint32_t i = 4; i < use->NumOperands(); i++) { |
157 | 0 | new_operands.emplace_back(use->GetOperand(i)); |
158 | 0 | } |
159 | |
|
160 | 0 | use->ReplaceOperands(new_operands); |
161 | 0 | context()->UpdateDefUse(use); |
162 | 0 | return true; |
163 | 0 | } |
164 | | |
165 | | bool DescriptorScalarReplacement::ReplaceEntryPoint(Instruction* var, |
166 | 0 | Instruction* use) { |
167 | | // Build a new |OperandList| for |use| that removes |var| and adds its |
168 | | // replacement variables. |
169 | 0 | Instruction::OperandList new_operands; |
170 | | |
171 | | // Copy all operands except |var|. |
172 | 0 | bool found = false; |
173 | 0 | for (uint32_t idx = 0; idx < use->NumOperands(); idx++) { |
174 | 0 | Operand& op = use->GetOperand(idx); |
175 | 0 | if (op.type == SPV_OPERAND_TYPE_ID && op.words[0] == var->result_id()) { |
176 | 0 | found = true; |
177 | 0 | } else { |
178 | 0 | new_operands.emplace_back(op); |
179 | 0 | } |
180 | 0 | } |
181 | |
|
182 | 0 | if (!found) { |
183 | 0 | context()->EmitErrorMessage( |
184 | 0 | "Variable cannot be replaced: invalid instruction", use); |
185 | 0 | return false; |
186 | 0 | } |
187 | | |
188 | | // Add all new replacement variables. |
189 | 0 | uint32_t num_replacement_vars = |
190 | 0 | descsroautil::GetNumberOfElementsForArrayOrStruct(context(), var); |
191 | 0 | for (uint32_t i = 0; i < num_replacement_vars; i++) { |
192 | 0 | uint32_t replacement_var_id = GetReplacementVariable(var, i); |
193 | 0 | if (replacement_var_id == 0) { |
194 | 0 | return false; |
195 | 0 | } |
196 | 0 | new_operands.push_back({SPV_OPERAND_TYPE_ID, {replacement_var_id}}); |
197 | 0 | } |
198 | | |
199 | 0 | use->ReplaceOperands(new_operands); |
200 | 0 | context()->UpdateDefUse(use); |
201 | 0 | return true; |
202 | 0 | } |
203 | | |
204 | | uint32_t DescriptorScalarReplacement::GetReplacementVariable(Instruction* var, |
205 | 0 | uint32_t idx) { |
206 | 0 | auto replacement_vars = replacement_variables_.find(var); |
207 | 0 | if (replacement_vars == replacement_variables_.end()) { |
208 | 0 | uint32_t number_of_elements = |
209 | 0 | descsroautil::GetNumberOfElementsForArrayOrStruct(context(), var); |
210 | 0 | replacement_vars = |
211 | 0 | replacement_variables_ |
212 | 0 | .insert({var, std::vector<uint32_t>(number_of_elements, 0)}) |
213 | 0 | .first; |
214 | 0 | } |
215 | |
|
216 | 0 | if (replacement_vars->second[idx] == 0) { |
217 | 0 | replacement_vars->second[idx] = CreateReplacementVariable(var, idx); |
218 | 0 | } |
219 | |
|
220 | 0 | return replacement_vars->second[idx]; |
221 | 0 | } |
222 | | |
223 | | void DescriptorScalarReplacement::CopyDecorationsForNewVariable( |
224 | | Instruction* old_var, uint32_t index, uint32_t new_var_id, |
225 | | uint32_t new_var_ptr_type_id, const bool is_old_var_array, |
226 | 0 | const bool is_old_var_struct, Instruction* old_var_type) { |
227 | | // Handle OpDecorate and OpDecorateString instructions. |
228 | 0 | for (auto old_decoration : |
229 | 0 | get_decoration_mgr()->GetDecorationsFor(old_var->result_id(), true)) { |
230 | 0 | uint32_t new_binding = 0; |
231 | 0 | if (IsDecorationBinding(old_decoration)) { |
232 | 0 | new_binding = GetNewBindingForElement( |
233 | 0 | old_decoration->GetSingleWordInOperand(2), index, new_var_ptr_type_id, |
234 | 0 | is_old_var_array, is_old_var_struct, old_var_type); |
235 | 0 | } |
236 | 0 | CreateNewDecorationForNewVariable(old_decoration, new_var_id, new_binding); |
237 | 0 | } |
238 | | |
239 | | // Handle OpMemberDecorate instructions. |
240 | 0 | for (auto old_decoration : get_decoration_mgr()->GetDecorationsFor( |
241 | 0 | old_var_type->result_id(), true)) { |
242 | 0 | assert(old_decoration->opcode() == spv::Op::OpMemberDecorate); |
243 | 0 | if (old_decoration->GetSingleWordInOperand(1u) != index) continue; |
244 | 0 | CreateNewDecorationForMemberDecorate(old_decoration, new_var_id); |
245 | 0 | } |
246 | 0 | } |
247 | | |
248 | | uint32_t DescriptorScalarReplacement::GetNewBindingForElement( |
249 | | uint32_t old_binding, uint32_t index, uint32_t new_var_ptr_type_id, |
250 | | const bool is_old_var_array, const bool is_old_var_struct, |
251 | 0 | Instruction* old_var_type) { |
252 | 0 | if (is_old_var_array) { |
253 | 0 | return old_binding + index * GetNumBindingsUsedByType(new_var_ptr_type_id); |
254 | 0 | } |
255 | 0 | if (is_old_var_struct) { |
256 | | // The binding offset that should be added is the sum of binding |
257 | | // numbers used by previous members of the current struct. |
258 | 0 | uint32_t new_binding = old_binding; |
259 | 0 | for (uint32_t i = 0; i < index; ++i) { |
260 | 0 | new_binding += |
261 | 0 | GetNumBindingsUsedByType(old_var_type->GetSingleWordInOperand(i)); |
262 | 0 | } |
263 | 0 | return new_binding; |
264 | 0 | } |
265 | 0 | return old_binding; |
266 | 0 | } |
267 | | |
268 | | void DescriptorScalarReplacement::CreateNewDecorationForNewVariable( |
269 | 0 | Instruction* old_decoration, uint32_t new_var_id, uint32_t new_binding) { |
270 | 0 | assert(old_decoration->opcode() == spv::Op::OpDecorate || |
271 | 0 | old_decoration->opcode() == spv::Op::OpDecorateString); |
272 | 0 | std::unique_ptr<Instruction> new_decoration(old_decoration->Clone(context())); |
273 | 0 | new_decoration->SetInOperand(0, {new_var_id}); |
274 | |
|
275 | 0 | if (IsDecorationBinding(new_decoration.get())) { |
276 | 0 | new_decoration->SetInOperand(2, {new_binding}); |
277 | 0 | } |
278 | 0 | context()->AddAnnotationInst(std::move(new_decoration)); |
279 | 0 | } |
280 | | |
281 | | void DescriptorScalarReplacement::CreateNewDecorationForMemberDecorate( |
282 | 0 | Instruction* old_member_decoration, uint32_t new_var_id) { |
283 | 0 | std::vector<Operand> operands( |
284 | 0 | {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {new_var_id}}}); |
285 | 0 | auto new_decorate_operand_begin = old_member_decoration->begin() + 2u; |
286 | 0 | auto new_decorate_operand_end = old_member_decoration->end(); |
287 | 0 | operands.insert(operands.end(), new_decorate_operand_begin, |
288 | 0 | new_decorate_operand_end); |
289 | 0 | get_decoration_mgr()->AddDecoration(spv::Op::OpDecorate, std::move(operands)); |
290 | 0 | } |
291 | | |
292 | | uint32_t DescriptorScalarReplacement::CreateReplacementVariable( |
293 | 0 | Instruction* var, uint32_t idx) { |
294 | | // The storage class for the new variable is the same as the original. |
295 | 0 | spv::StorageClass storage_class = |
296 | 0 | static_cast<spv::StorageClass>(var->GetSingleWordInOperand(0)); |
297 | | |
298 | | // The type for the new variable will be a pointer to type of the elements of |
299 | | // the array. |
300 | 0 | uint32_t ptr_type_id = var->type_id(); |
301 | 0 | Instruction* ptr_type_inst = get_def_use_mgr()->GetDef(ptr_type_id); |
302 | 0 | assert(ptr_type_inst->opcode() == spv::Op::OpTypePointer && |
303 | 0 | "Variable should be a pointer to an array or structure."); |
304 | 0 | uint32_t pointee_type_id = ptr_type_inst->GetSingleWordInOperand(1); |
305 | 0 | Instruction* pointee_type_inst = get_def_use_mgr()->GetDef(pointee_type_id); |
306 | 0 | const bool is_array = pointee_type_inst->opcode() == spv::Op::OpTypeArray; |
307 | 0 | const bool is_struct = pointee_type_inst->opcode() == spv::Op::OpTypeStruct; |
308 | 0 | assert((is_array || is_struct) && |
309 | 0 | "Variable should be a pointer to an array or structure."); |
310 | | |
311 | 0 | uint32_t element_type_id = |
312 | 0 | is_array ? pointee_type_inst->GetSingleWordInOperand(0) |
313 | 0 | : pointee_type_inst->GetSingleWordInOperand(idx); |
314 | |
|
315 | 0 | uint32_t ptr_element_type_id = context()->get_type_mgr()->FindPointerToType( |
316 | 0 | element_type_id, storage_class); |
317 | | |
318 | | // Create the variable. |
319 | 0 | uint32_t id = context()->TakeNextId(); |
320 | 0 | if (id == 0) { |
321 | 0 | return 0; |
322 | 0 | } |
323 | 0 | std::unique_ptr<Instruction> variable( |
324 | 0 | new Instruction(context(), spv::Op::OpVariable, ptr_element_type_id, id, |
325 | 0 | std::initializer_list<Operand>{ |
326 | 0 | {SPV_OPERAND_TYPE_STORAGE_CLASS, |
327 | 0 | {static_cast<uint32_t>(storage_class)}}})); |
328 | 0 | context()->AddGlobalValue(std::move(variable)); |
329 | |
|
330 | 0 | CopyDecorationsForNewVariable(var, idx, id, ptr_element_type_id, is_array, |
331 | 0 | is_struct, pointee_type_inst); |
332 | | |
333 | | // Create a new OpName for the replacement variable. |
334 | 0 | std::vector<std::unique_ptr<Instruction>> names_to_add; |
335 | 0 | for (auto p : context()->GetNames(var->result_id())) { |
336 | 0 | Instruction* name_inst = p.second; |
337 | 0 | std::string name_str = utils::MakeString(name_inst->GetOperand(1).words); |
338 | 0 | if (is_array) { |
339 | 0 | name_str += "[" + utils::ToString(idx) + "]"; |
340 | 0 | } |
341 | 0 | if (is_struct) { |
342 | 0 | Instruction* member_name_inst = |
343 | 0 | context()->GetMemberName(pointee_type_inst->result_id(), idx); |
344 | 0 | name_str += "."; |
345 | 0 | if (member_name_inst) |
346 | 0 | name_str += utils::MakeString(member_name_inst->GetOperand(2).words); |
347 | 0 | else |
348 | | // In case the member does not have a name assigned to it, use the |
349 | | // member index. |
350 | 0 | name_str += utils::ToString(idx); |
351 | 0 | } |
352 | |
|
353 | 0 | std::unique_ptr<Instruction> new_name(new Instruction( |
354 | 0 | context(), spv::Op::OpName, 0, 0, |
355 | 0 | std::initializer_list<Operand>{ |
356 | 0 | {SPV_OPERAND_TYPE_ID, {id}}, |
357 | 0 | {SPV_OPERAND_TYPE_LITERAL_STRING, utils::MakeVector(name_str)}})); |
358 | 0 | Instruction* new_name_inst = new_name.get(); |
359 | 0 | get_def_use_mgr()->AnalyzeInstDefUse(new_name_inst); |
360 | 0 | names_to_add.push_back(std::move(new_name)); |
361 | 0 | } |
362 | | |
363 | | // We shouldn't add the new names when we are iterating over name ranges |
364 | | // above. We can add all the new names now. |
365 | 0 | for (auto& new_name : names_to_add) |
366 | 0 | context()->AddDebug2Inst(std::move(new_name)); |
367 | |
|
368 | 0 | return id; |
369 | 0 | } |
370 | | |
371 | | uint32_t DescriptorScalarReplacement::GetNumBindingsUsedByType( |
372 | 0 | uint32_t type_id) { |
373 | 0 | Instruction* type_inst = get_def_use_mgr()->GetDef(type_id); |
374 | | |
375 | | // If it's a pointer, look at the underlying type. |
376 | 0 | if (type_inst->opcode() == spv::Op::OpTypePointer) { |
377 | 0 | type_id = type_inst->GetSingleWordInOperand(1); |
378 | 0 | type_inst = get_def_use_mgr()->GetDef(type_id); |
379 | 0 | } |
380 | | |
381 | | // Arrays consume N*M binding numbers where N is the array length, and M is |
382 | | // the number of bindings used by each array element. |
383 | 0 | if (type_inst->opcode() == spv::Op::OpTypeArray) { |
384 | 0 | uint32_t element_type_id = type_inst->GetSingleWordInOperand(0); |
385 | 0 | uint32_t length_id = type_inst->GetSingleWordInOperand(1); |
386 | 0 | const analysis::Constant* length_const = |
387 | 0 | context()->get_constant_mgr()->FindDeclaredConstant(length_id); |
388 | | // OpTypeArray's length must always be a constant |
389 | 0 | assert(length_const != nullptr); |
390 | 0 | uint32_t num_elems = length_const->GetU32(); |
391 | 0 | return num_elems * GetNumBindingsUsedByType(element_type_id); |
392 | 0 | } |
393 | | |
394 | | // The number of bindings consumed by a structure is the sum of the bindings |
395 | | // used by its members. |
396 | 0 | if (type_inst->opcode() == spv::Op::OpTypeStruct && |
397 | 0 | !descsroautil::IsTypeOfStructuredBuffer(context(), type_inst)) { |
398 | 0 | uint32_t sum = 0; |
399 | 0 | for (uint32_t i = 0; i < type_inst->NumInOperands(); i++) |
400 | 0 | sum += GetNumBindingsUsedByType(type_inst->GetSingleWordInOperand(i)); |
401 | 0 | return sum; |
402 | 0 | } |
403 | | |
404 | | // All other types are considered to take up 1 binding number. |
405 | 0 | return 1; |
406 | 0 | } |
407 | | |
408 | | bool DescriptorScalarReplacement::ReplaceLoadedValue(Instruction* var, |
409 | 0 | Instruction* value) { |
410 | | // |var| is the global variable that has to be eliminated (OpVariable). |
411 | | // |value| is the OpLoad instruction that has loaded |var|. |
412 | | // The function expects all users of |value| to be OpCompositeExtract |
413 | | // instructions. Otherwise the function returns false with an error message. |
414 | 0 | assert(value->opcode() == spv::Op::OpLoad); |
415 | 0 | assert(value->GetSingleWordInOperand(0) == var->result_id()); |
416 | 0 | std::vector<Instruction*> work_list; |
417 | 0 | bool failed = !get_def_use_mgr()->WhileEachUser( |
418 | 0 | value->result_id(), [this, &work_list](Instruction* use) { |
419 | 0 | if (use->opcode() != spv::Op::OpCompositeExtract) { |
420 | 0 | context()->EmitErrorMessage( |
421 | 0 | "Variable cannot be replaced: invalid instruction", use); |
422 | 0 | return false; |
423 | 0 | } |
424 | 0 | work_list.push_back(use); |
425 | 0 | return true; |
426 | 0 | }); |
427 | |
|
428 | 0 | if (failed) { |
429 | 0 | return false; |
430 | 0 | } |
431 | | |
432 | 0 | for (Instruction* use : work_list) { |
433 | 0 | if (!ReplaceCompositeExtract(var, use)) { |
434 | 0 | return false; |
435 | 0 | } |
436 | 0 | } |
437 | | |
438 | | // All usages of the loaded value have been killed. We can kill the OpLoad. |
439 | 0 | context()->KillInst(value); |
440 | 0 | return true; |
441 | 0 | } |
442 | | |
443 | | bool DescriptorScalarReplacement::ReplaceCompositeExtract( |
444 | 0 | Instruction* var, Instruction* extract) { |
445 | 0 | assert(extract->opcode() == spv::Op::OpCompositeExtract); |
446 | | // We're currently only supporting extractions of one index at a time. If we |
447 | | // need to, we can handle cases with multiple indexes in the future. |
448 | 0 | if (extract->NumInOperands() != 2) { |
449 | 0 | context()->EmitErrorMessage( |
450 | 0 | "Variable cannot be replaced: invalid instruction", extract); |
451 | 0 | return false; |
452 | 0 | } |
453 | | |
454 | 0 | uint32_t replacement_var = |
455 | 0 | GetReplacementVariable(var, extract->GetSingleWordInOperand(1)); |
456 | 0 | if (replacement_var == 0) { |
457 | 0 | return false; |
458 | 0 | } |
459 | | |
460 | | // The result type of the OpLoad is the same as the result type of the |
461 | | // OpCompositeExtract. |
462 | 0 | uint32_t load_id = context()->TakeNextId(); |
463 | 0 | if (load_id == 0) { |
464 | 0 | return false; |
465 | 0 | } |
466 | 0 | std::unique_ptr<Instruction> load( |
467 | 0 | new Instruction(context(), spv::Op::OpLoad, extract->type_id(), load_id, |
468 | 0 | std::initializer_list<Operand>{ |
469 | 0 | {SPV_OPERAND_TYPE_ID, {replacement_var}}})); |
470 | 0 | Instruction* load_instr = load.get(); |
471 | 0 | get_def_use_mgr()->AnalyzeInstDefUse(load_instr); |
472 | 0 | context()->set_instr_block(load_instr, context()->get_instr_block(extract)); |
473 | 0 | extract->InsertBefore(std::move(load)); |
474 | 0 | context()->ReplaceAllUsesWith(extract->result_id(), load_id); |
475 | 0 | context()->KillInst(extract); |
476 | 0 | return true; |
477 | 0 | } |
478 | | |
479 | | } // namespace opt |
480 | | } // namespace spvtools |