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/eliminate_dead_members_pass.h"
16
17#include "ir_builder.h"
18#include "source/opt/ir_context.h"
19
20namespace {
21const uint32_t kRemovedMember = 0xFFFFFFFF;
22}
23
24namespace spvtools {
25namespace opt {
26
27Pass::Status EliminateDeadMembersPass::Process() {
28 if (!context()->get_feature_mgr()->HasCapability(SpvCapabilityShader))
29 return Status::SuccessWithoutChange;
30
31 FindLiveMembers();
32 if (RemoveDeadMembers()) {
33 return Status::SuccessWithChange;
34 }
35 return Status::SuccessWithoutChange;
36}
37
38void EliminateDeadMembersPass::FindLiveMembers() {
39 // Until we have implemented the rewritting of OpSpecConsantOp instructions,
40 // we have to mark them as fully used just to be safe.
41 for (auto& inst : get_module()->types_values()) {
42 if (inst.opcode() == SpvOpSpecConstantOp) {
43 MarkTypeAsFullyUsed(inst.type_id());
44 } else if (inst.opcode() == SpvOpVariable) {
45 switch (inst.GetSingleWordInOperand(0)) {
46 case SpvStorageClassInput:
47 case SpvStorageClassOutput:
48 MarkPointeeTypeAsFullUsed(inst.type_id());
49 break;
50 default:
51 break;
52 }
53 }
54 }
55
56 for (const Function& func : *get_module()) {
57 FindLiveMembers(func);
58 }
59}
60
61void EliminateDeadMembersPass::FindLiveMembers(const Function& function) {
62 function.ForEachInst(
63 [this](const Instruction* inst) { FindLiveMembers(inst); });
64}
65
66void EliminateDeadMembersPass::FindLiveMembers(const Instruction* inst) {
67 switch (inst->opcode()) {
68 case SpvOpStore:
69 MarkMembersAsLiveForStore(inst);
70 break;
71 case SpvOpCopyMemory:
72 case SpvOpCopyMemorySized:
73 MarkMembersAsLiveForCopyMemory(inst);
74 break;
75 case SpvOpCompositeExtract:
76 MarkMembersAsLiveForExtract(inst);
77 break;
78 case SpvOpAccessChain:
79 case SpvOpInBoundsAccessChain:
80 case SpvOpPtrAccessChain:
81 case SpvOpInBoundsPtrAccessChain:
82 MarkMembersAsLiveForAccessChain(inst);
83 break;
84 case SpvOpReturnValue:
85 // This should be an issue only if we are returning from the entry point.
86 // However, for now I will keep it more conservative because functions are
87 // often inlined leaving only the entry points.
88 MarkOperandTypeAsFullyUsed(inst, 0);
89 break;
90 case SpvOpArrayLength:
91 MarkMembersAsLiveForArrayLength(inst);
92 break;
93 case SpvOpLoad:
94 case SpvOpCompositeInsert:
95 case SpvOpCompositeConstruct:
96 break;
97 default:
98 // This path is here for safety. All instructions that can reference
99 // structs in a function body should be handled above. However, this will
100 // keep the pass valid, but not optimal, as new instructions get added
101 // or if something was missed.
102 MarkStructOperandsAsFullyUsed(inst);
103 break;
104 }
105}
106
107void EliminateDeadMembersPass::MarkMembersAsLiveForStore(
108 const Instruction* inst) {
109 // We should only have to mark the members as live if the store is to
110 // memory that is read outside of the shader. Other passes can remove all
111 // store to memory that is not visible outside of the shader, so we do not
112 // complicate the code for now.
113 assert(inst->opcode() == SpvOpStore);
114 uint32_t object_id = inst->GetSingleWordInOperand(1);
115 Instruction* object_inst = context()->get_def_use_mgr()->GetDef(object_id);
116 uint32_t object_type_id = object_inst->type_id();
117 MarkTypeAsFullyUsed(object_type_id);
118}
119
120void EliminateDeadMembersPass::MarkTypeAsFullyUsed(uint32_t type_id) {
121 Instruction* type_inst = get_def_use_mgr()->GetDef(type_id);
122 assert(type_inst != nullptr);
123 if (type_inst->opcode() != SpvOpTypeStruct) {
124 return;
125 }
126
127 // Mark every member of the current struct as used.
128 for (uint32_t i = 0; i < type_inst->NumInOperands(); ++i) {
129 used_members_[type_id].insert(i);
130 }
131
132 // Mark any sub struct as fully used.
133 for (uint32_t i = 0; i < type_inst->NumInOperands(); ++i) {
134 MarkTypeAsFullyUsed(type_inst->GetSingleWordInOperand(i));
135 }
136}
137
138void EliminateDeadMembersPass::MarkPointeeTypeAsFullUsed(uint32_t ptr_type_id) {
139 Instruction* ptr_type_inst = get_def_use_mgr()->GetDef(ptr_type_id);
140 assert(ptr_type_inst->opcode() == SpvOpTypePointer);
141 MarkTypeAsFullyUsed(ptr_type_inst->GetSingleWordInOperand(1));
142}
143
144void EliminateDeadMembersPass::MarkMembersAsLiveForCopyMemory(
145 const Instruction* inst) {
146 uint32_t target_id = inst->GetSingleWordInOperand(0);
147 Instruction* target_inst = get_def_use_mgr()->GetDef(target_id);
148 uint32_t pointer_type_id = target_inst->type_id();
149 Instruction* pointer_type_inst = get_def_use_mgr()->GetDef(pointer_type_id);
150 uint32_t type_id = pointer_type_inst->GetSingleWordInOperand(1);
151 MarkTypeAsFullyUsed(type_id);
152}
153
154void EliminateDeadMembersPass::MarkMembersAsLiveForExtract(
155 const Instruction* inst) {
156 assert(inst->opcode() == SpvOpCompositeExtract);
157
158 uint32_t composite_id = inst->GetSingleWordInOperand(0);
159 Instruction* composite_inst = get_def_use_mgr()->GetDef(composite_id);
160 uint32_t type_id = composite_inst->type_id();
161
162 for (uint32_t i = 1; i < inst->NumInOperands(); ++i) {
163 Instruction* type_inst = get_def_use_mgr()->GetDef(type_id);
164 uint32_t member_idx = inst->GetSingleWordInOperand(i);
165 switch (type_inst->opcode()) {
166 case SpvOpTypeStruct:
167 used_members_[type_id].insert(member_idx);
168 type_id = type_inst->GetSingleWordInOperand(member_idx);
169 break;
170 case SpvOpTypeArray:
171 case SpvOpTypeRuntimeArray:
172 case SpvOpTypeVector:
173 case SpvOpTypeMatrix:
174 type_id = type_inst->GetSingleWordInOperand(0);
175 break;
176 default:
177 assert(false);
178 }
179 }
180}
181
182void EliminateDeadMembersPass::MarkMembersAsLiveForAccessChain(
183 const Instruction* inst) {
184 assert(inst->opcode() == SpvOpAccessChain ||
185 inst->opcode() == SpvOpInBoundsAccessChain ||
186 inst->opcode() == SpvOpPtrAccessChain ||
187 inst->opcode() == SpvOpInBoundsPtrAccessChain);
188
189 uint32_t pointer_id = inst->GetSingleWordInOperand(0);
190 Instruction* pointer_inst = get_def_use_mgr()->GetDef(pointer_id);
191 uint32_t pointer_type_id = pointer_inst->type_id();
192 Instruction* pointer_type_inst = get_def_use_mgr()->GetDef(pointer_type_id);
193 uint32_t type_id = pointer_type_inst->GetSingleWordInOperand(1);
194
195 analysis::ConstantManager* const_mgr = context()->get_constant_mgr();
196
197 // For a pointer access chain, we need to skip the |element| index. It is not
198 // a reference to the member of a struct, and it does not change the type.
199 uint32_t i = (inst->opcode() == SpvOpAccessChain ||
200 inst->opcode() == SpvOpInBoundsAccessChain
201 ? 1
202 : 2);
203 for (; i < inst->NumInOperands(); ++i) {
204 Instruction* type_inst = get_def_use_mgr()->GetDef(type_id);
205 switch (type_inst->opcode()) {
206 case SpvOpTypeStruct: {
207 const analysis::IntConstant* member_idx =
208 const_mgr->FindDeclaredConstant(inst->GetSingleWordInOperand(i))
209 ->AsIntConstant();
210 assert(member_idx);
211 if (member_idx->type()->AsInteger()->width() == 32) {
212 used_members_[type_id].insert(member_idx->GetU32());
213 type_id = type_inst->GetSingleWordInOperand(member_idx->GetU32());
214 } else {
215 used_members_[type_id].insert(
216 static_cast<uint32_t>(member_idx->GetU64()));
217 type_id = type_inst->GetSingleWordInOperand(
218 static_cast<uint32_t>(member_idx->GetU64()));
219 }
220 } break;
221 case SpvOpTypeArray:
222 case SpvOpTypeRuntimeArray:
223 case SpvOpTypeVector:
224 case SpvOpTypeMatrix:
225 type_id = type_inst->GetSingleWordInOperand(0);
226 break;
227 default:
228 assert(false);
229 }
230 }
231}
232
233void EliminateDeadMembersPass::MarkOperandTypeAsFullyUsed(
234 const Instruction* inst, uint32_t in_idx) {
235 uint32_t op_id = inst->GetSingleWordInOperand(in_idx);
236 Instruction* op_inst = get_def_use_mgr()->GetDef(op_id);
237 MarkTypeAsFullyUsed(op_inst->type_id());
238}
239
240void EliminateDeadMembersPass::MarkMembersAsLiveForArrayLength(
241 const Instruction* inst) {
242 assert(inst->opcode() == SpvOpArrayLength);
243 uint32_t object_id = inst->GetSingleWordInOperand(0);
244 Instruction* object_inst = get_def_use_mgr()->GetDef(object_id);
245 uint32_t pointer_type_id = object_inst->type_id();
246 Instruction* pointer_type_inst = get_def_use_mgr()->GetDef(pointer_type_id);
247 uint32_t type_id = pointer_type_inst->GetSingleWordInOperand(1);
248 used_members_[type_id].insert(inst->GetSingleWordInOperand(1));
249}
250
251bool EliminateDeadMembersPass::RemoveDeadMembers() {
252 bool modified = false;
253
254 // First update all of the OpTypeStruct instructions.
255 get_module()->ForEachInst([&modified, this](Instruction* inst) {
256 switch (inst->opcode()) {
257 case SpvOpTypeStruct:
258 modified |= UpdateOpTypeStruct(inst);
259 break;
260 default:
261 break;
262 }
263 });
264
265 // Now update all of the instructions that reference the OpTypeStructs.
266 get_module()->ForEachInst([&modified, this](Instruction* inst) {
267 switch (inst->opcode()) {
268 case SpvOpMemberName:
269 modified |= UpdateOpMemberNameOrDecorate(inst);
270 break;
271 case SpvOpMemberDecorate:
272 modified |= UpdateOpMemberNameOrDecorate(inst);
273 break;
274 case SpvOpGroupMemberDecorate:
275 modified |= UpdateOpGroupMemberDecorate(inst);
276 break;
277 case SpvOpSpecConstantComposite:
278 case SpvOpConstantComposite:
279 case SpvOpCompositeConstruct:
280 modified |= UpdateConstantComposite(inst);
281 break;
282 case SpvOpAccessChain:
283 case SpvOpInBoundsAccessChain:
284 case SpvOpPtrAccessChain:
285 case SpvOpInBoundsPtrAccessChain:
286 modified |= UpdateAccessChain(inst);
287 break;
288 case SpvOpCompositeExtract:
289 modified |= UpdateCompsiteExtract(inst);
290 break;
291 case SpvOpCompositeInsert:
292 modified |= UpdateCompositeInsert(inst);
293 break;
294 case SpvOpArrayLength:
295 modified |= UpdateOpArrayLength(inst);
296 break;
297 case SpvOpSpecConstantOp:
298 assert(false && "Not yet implemented.");
299 // with OpCompositeExtract, OpCompositeInsert
300 // For kernels: OpAccessChain, OpInBoundsAccessChain, OpPtrAccessChain,
301 // OpInBoundsPtrAccessChain
302 break;
303 default:
304 break;
305 }
306 });
307 return modified;
308}
309
310bool EliminateDeadMembersPass::UpdateOpTypeStruct(Instruction* inst) {
311 assert(inst->opcode() == SpvOpTypeStruct);
312
313 const auto& live_members = used_members_[inst->result_id()];
314 if (live_members.size() == inst->NumInOperands()) {
315 return false;
316 }
317
318 Instruction::OperandList new_operands;
319 for (uint32_t idx : live_members) {
320 new_operands.emplace_back(inst->GetInOperand(idx));
321 }
322
323 inst->SetInOperands(std::move(new_operands));
324 context()->UpdateDefUse(inst);
325 return true;
326}
327
328bool EliminateDeadMembersPass::UpdateOpMemberNameOrDecorate(Instruction* inst) {
329 assert(inst->opcode() == SpvOpMemberName ||
330 inst->opcode() == SpvOpMemberDecorate);
331
332 uint32_t type_id = inst->GetSingleWordInOperand(0);
333 auto live_members = used_members_.find(type_id);
334 if (live_members == used_members_.end()) {
335 return false;
336 }
337
338 uint32_t orig_member_idx = inst->GetSingleWordInOperand(1);
339 uint32_t new_member_idx = GetNewMemberIndex(type_id, orig_member_idx);
340
341 if (new_member_idx == kRemovedMember) {
342 context()->KillInst(inst);
343 return true;
344 }
345
346 if (new_member_idx == orig_member_idx) {
347 return false;
348 }
349
350 inst->SetInOperand(1, {new_member_idx});
351 return true;
352}
353
354bool EliminateDeadMembersPass::UpdateOpGroupMemberDecorate(Instruction* inst) {
355 assert(inst->opcode() == SpvOpGroupMemberDecorate);
356
357 bool modified = false;
358
359 Instruction::OperandList new_operands;
360 new_operands.emplace_back(inst->GetInOperand(0));
361 for (uint32_t i = 1; i < inst->NumInOperands(); i += 2) {
362 uint32_t type_id = inst->GetSingleWordInOperand(i);
363 uint32_t member_idx = inst->GetSingleWordInOperand(i + 1);
364 uint32_t new_member_idx = GetNewMemberIndex(type_id, member_idx);
365
366 if (new_member_idx == kRemovedMember) {
367 modified = true;
368 continue;
369 }
370
371 new_operands.emplace_back(inst->GetOperand(i));
372 if (new_member_idx != member_idx) {
373 new_operands.emplace_back(
374 Operand({SPV_OPERAND_TYPE_LITERAL_INTEGER, {new_member_idx}}));
375 modified = true;
376 } else {
377 new_operands.emplace_back(inst->GetOperand(i + 1));
378 }
379 }
380
381 if (!modified) {
382 return false;
383 }
384
385 if (new_operands.size() == 1) {
386 context()->KillInst(inst);
387 return true;
388 }
389
390 inst->SetInOperands(std::move(new_operands));
391 context()->UpdateDefUse(inst);
392 return true;
393}
394
395bool EliminateDeadMembersPass::UpdateConstantComposite(Instruction* inst) {
396 assert(inst->opcode() == SpvOpConstantComposite ||
397 inst->opcode() == SpvOpCompositeConstruct);
398 uint32_t type_id = inst->type_id();
399
400 bool modified = false;
401 Instruction::OperandList new_operands;
402 for (uint32_t i = 0; i < inst->NumInOperands(); ++i) {
403 uint32_t new_idx = GetNewMemberIndex(type_id, i);
404 if (new_idx == kRemovedMember) {
405 modified = true;
406 } else {
407 new_operands.emplace_back(inst->GetInOperand(i));
408 }
409 }
410 inst->SetInOperands(std::move(new_operands));
411 context()->UpdateDefUse(inst);
412 return modified;
413}
414
415bool EliminateDeadMembersPass::UpdateAccessChain(Instruction* inst) {
416 assert(inst->opcode() == SpvOpAccessChain ||
417 inst->opcode() == SpvOpInBoundsAccessChain ||
418 inst->opcode() == SpvOpPtrAccessChain ||
419 inst->opcode() == SpvOpInBoundsPtrAccessChain);
420
421 uint32_t pointer_id = inst->GetSingleWordInOperand(0);
422 Instruction* pointer_inst = get_def_use_mgr()->GetDef(pointer_id);
423 uint32_t pointer_type_id = pointer_inst->type_id();
424 Instruction* pointer_type_inst = get_def_use_mgr()->GetDef(pointer_type_id);
425 uint32_t type_id = pointer_type_inst->GetSingleWordInOperand(1);
426
427 analysis::ConstantManager* const_mgr = context()->get_constant_mgr();
428 Instruction::OperandList new_operands;
429 bool modified = false;
430 new_operands.emplace_back(inst->GetInOperand(0));
431
432 // For pointer access chains we want to copy the element operand.
433 if (inst->opcode() == SpvOpPtrAccessChain ||
434 inst->opcode() == SpvOpInBoundsPtrAccessChain) {
435 new_operands.emplace_back(inst->GetInOperand(1));
436 }
437
438 for (uint32_t i = static_cast<uint32_t>(new_operands.size());
439 i < inst->NumInOperands(); ++i) {
440 Instruction* type_inst = get_def_use_mgr()->GetDef(type_id);
441 switch (type_inst->opcode()) {
442 case SpvOpTypeStruct: {
443 const analysis::IntConstant* member_idx =
444 const_mgr->FindDeclaredConstant(inst->GetSingleWordInOperand(i))
445 ->AsIntConstant();
446 assert(member_idx);
447 uint32_t orig_member_idx;
448 if (member_idx->type()->AsInteger()->width() == 32) {
449 orig_member_idx = member_idx->GetU32();
450 } else {
451 orig_member_idx = static_cast<uint32_t>(member_idx->GetU64());
452 }
453 uint32_t new_member_idx = GetNewMemberIndex(type_id, orig_member_idx);
454 assert(new_member_idx != kRemovedMember);
455 if (orig_member_idx != new_member_idx) {
456 InstructionBuilder ir_builder(
457 context(), inst,
458 IRContext::kAnalysisDefUse |
459 IRContext::kAnalysisInstrToBlockMapping);
460 uint32_t const_id =
461 ir_builder.GetUintConstant(new_member_idx)->result_id();
462 new_operands.emplace_back(Operand({SPV_OPERAND_TYPE_ID, {const_id}}));
463 modified = true;
464 } else {
465 new_operands.emplace_back(inst->GetInOperand(i));
466 }
467 // The type will have already been rewritten, so use the new member
468 // index.
469 type_id = type_inst->GetSingleWordInOperand(new_member_idx);
470 } break;
471 case SpvOpTypeArray:
472 case SpvOpTypeRuntimeArray:
473 case SpvOpTypeVector:
474 case SpvOpTypeMatrix:
475 new_operands.emplace_back(inst->GetInOperand(i));
476 type_id = type_inst->GetSingleWordInOperand(0);
477 break;
478 default:
479 assert(false);
480 break;
481 }
482 }
483
484 if (!modified) {
485 return false;
486 }
487 inst->SetInOperands(std::move(new_operands));
488 context()->UpdateDefUse(inst);
489 return true;
490}
491
492uint32_t EliminateDeadMembersPass::GetNewMemberIndex(uint32_t type_id,
493 uint32_t member_idx) {
494 auto live_members = used_members_.find(type_id);
495 if (live_members == used_members_.end()) {
496 return member_idx;
497 }
498
499 auto current_member = live_members->second.find(member_idx);
500 if (current_member == live_members->second.end()) {
501 return kRemovedMember;
502 }
503
504 return static_cast<uint32_t>(
505 std::distance(live_members->second.begin(), current_member));
506}
507
508bool EliminateDeadMembersPass::UpdateCompsiteExtract(Instruction* inst) {
509 uint32_t object_id = inst->GetSingleWordInOperand(0);
510 Instruction* object_inst = get_def_use_mgr()->GetDef(object_id);
511 uint32_t type_id = object_inst->type_id();
512
513 Instruction::OperandList new_operands;
514 bool modified = false;
515 new_operands.emplace_back(inst->GetInOperand(0));
516 for (uint32_t i = 1; i < inst->NumInOperands(); ++i) {
517 uint32_t member_idx = inst->GetSingleWordInOperand(i);
518 uint32_t new_member_idx = GetNewMemberIndex(type_id, member_idx);
519 assert(new_member_idx != kRemovedMember);
520 if (member_idx != new_member_idx) {
521 modified = true;
522 }
523 new_operands.emplace_back(
524 Operand({SPV_OPERAND_TYPE_LITERAL_INTEGER, {new_member_idx}}));
525
526 Instruction* type_inst = get_def_use_mgr()->GetDef(type_id);
527 switch (type_inst->opcode()) {
528 case SpvOpTypeStruct:
529 assert(i != 1 || (inst->opcode() != SpvOpPtrAccessChain &&
530 inst->opcode() != SpvOpInBoundsPtrAccessChain));
531 // The type will have already been rewriten, so use the new member
532 // index.
533 type_id = type_inst->GetSingleWordInOperand(new_member_idx);
534 break;
535 case SpvOpTypeArray:
536 case SpvOpTypeRuntimeArray:
537 case SpvOpTypeVector:
538 case SpvOpTypeMatrix:
539 type_id = type_inst->GetSingleWordInOperand(0);
540 break;
541 default:
542 assert(false);
543 }
544 }
545
546 if (!modified) {
547 return false;
548 }
549 inst->SetInOperands(std::move(new_operands));
550 context()->UpdateDefUse(inst);
551 return true;
552}
553
554bool EliminateDeadMembersPass::UpdateCompositeInsert(Instruction* inst) {
555 uint32_t composite_id = inst->GetSingleWordInOperand(1);
556 Instruction* composite_inst = get_def_use_mgr()->GetDef(composite_id);
557 uint32_t type_id = composite_inst->type_id();
558
559 Instruction::OperandList new_operands;
560 bool modified = false;
561 new_operands.emplace_back(inst->GetInOperand(0));
562 new_operands.emplace_back(inst->GetInOperand(1));
563 for (uint32_t i = 2; i < inst->NumInOperands(); ++i) {
564 uint32_t member_idx = inst->GetSingleWordInOperand(i);
565 uint32_t new_member_idx = GetNewMemberIndex(type_id, member_idx);
566 if (new_member_idx == kRemovedMember) {
567 context()->KillInst(inst);
568 return true;
569 }
570
571 if (member_idx != new_member_idx) {
572 modified = true;
573 }
574 new_operands.emplace_back(
575 Operand({SPV_OPERAND_TYPE_LITERAL_INTEGER, {new_member_idx}}));
576
577 Instruction* type_inst = get_def_use_mgr()->GetDef(type_id);
578 switch (type_inst->opcode()) {
579 case SpvOpTypeStruct:
580 // The type will have already been rewritten, so use the new member
581 // index.
582 type_id = type_inst->GetSingleWordInOperand(new_member_idx);
583 break;
584 case SpvOpTypeArray:
585 case SpvOpTypeRuntimeArray:
586 case SpvOpTypeVector:
587 case SpvOpTypeMatrix:
588 type_id = type_inst->GetSingleWordInOperand(0);
589 break;
590 default:
591 assert(false);
592 }
593 }
594
595 if (!modified) {
596 return false;
597 }
598 inst->SetInOperands(std::move(new_operands));
599 context()->UpdateDefUse(inst);
600 return true;
601}
602
603bool EliminateDeadMembersPass::UpdateOpArrayLength(Instruction* inst) {
604 uint32_t struct_id = inst->GetSingleWordInOperand(0);
605 Instruction* struct_inst = get_def_use_mgr()->GetDef(struct_id);
606 uint32_t pointer_type_id = struct_inst->type_id();
607 Instruction* pointer_type_inst = get_def_use_mgr()->GetDef(pointer_type_id);
608 uint32_t type_id = pointer_type_inst->GetSingleWordInOperand(1);
609
610 uint32_t member_idx = inst->GetSingleWordInOperand(1);
611 uint32_t new_member_idx = GetNewMemberIndex(type_id, member_idx);
612 assert(new_member_idx != kRemovedMember);
613
614 if (member_idx == new_member_idx) {
615 return false;
616 }
617
618 inst->SetInOperand(1, {new_member_idx});
619 context()->UpdateDefUse(inst);
620 return true;
621}
622
623void EliminateDeadMembersPass::MarkStructOperandsAsFullyUsed(
624 const Instruction* inst) {
625 if (inst->type_id() != 0) {
626 MarkTypeAsFullyUsed(inst->type_id());
627 }
628
629 inst->ForEachInId([this](const uint32_t* id) {
630 Instruction* instruction = get_def_use_mgr()->GetDef(*id);
631 if (instruction->type_id() != 0) {
632 MarkTypeAsFullyUsed(instruction->type_id());
633 }
634 });
635}
636} // namespace opt
637} // namespace spvtools
638