• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2025 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/split_combined_image_sampler_pass.h"
16 
17 #include <algorithm>
18 #include <cassert>
19 #include <memory>
20 
21 #include "source/opt/instruction.h"
22 #include "source/opt/ir_builder.h"
23 #include "source/opt/ir_context.h"
24 #include "source/opt/type_manager.h"
25 #include "source/opt/types.h"
26 #include "source/util/make_unique.h"
27 #include "source/util/string_utils.h"
28 #include "spirv/unified1/spirv.h"
29 
30 namespace spvtools {
31 namespace opt {
32 
33 #define CHECK(cond)                                          \
34   {                                                          \
35     if ((cond) != SPV_SUCCESS) return Pass::Status::Failure; \
36   }
37 
38 #define CHECK_STATUS(cond)                           \
39   {                                                  \
40     if (auto c = (cond); c != SPV_SUCCESS) return c; \
41   }
42 
GetPreservedAnalyses()43 IRContext::Analysis SplitCombinedImageSamplerPass::GetPreservedAnalyses() {
44   return
45       // def use manager is updated
46       IRContext::kAnalysisDefUse
47 
48       // decorations are updated
49       | IRContext::kAnalysisDecorations
50 
51       // control flow is not changed
52       | IRContext::kAnalysisCFG           //
53       | IRContext::kAnalysisLoopAnalysis  //
54       | IRContext::kAnalysisStructuredCFG
55 
56       // type manager is updated
57       | IRContext::kAnalysisTypes;
58 }
59 
Process()60 Pass::Status SplitCombinedImageSamplerPass::Process() {
61   def_use_mgr_ = context()->get_def_use_mgr();
62   type_mgr_ = context()->get_type_mgr();
63 
64   FindCombinedTextureSamplers();
65   if (combined_types_to_remove_.empty() && !sampled_image_used_as_param_) {
66     return Ok();
67   }
68 
69   CHECK(RemapFunctions());
70   CHECK(RemapVars());
71   CHECK(RemoveDeadTypes());
72 
73   def_use_mgr_ = nullptr;
74   type_mgr_ = nullptr;
75 
76   return Ok();
77 }
78 
Fail()79 spvtools::DiagnosticStream SplitCombinedImageSamplerPass::Fail() {
80   return std::move(
81       spvtools::DiagnosticStream({}, consumer(), "", SPV_ERROR_INVALID_BINARY)
82       << "split-combined-image-sampler: ");
83 }
84 
FindCombinedTextureSamplers()85 void SplitCombinedImageSamplerPass::FindCombinedTextureSamplers() {
86   for (auto& inst : context()->types_values()) {
87     RegisterGlobal(inst.result_id());
88     switch (inst.opcode()) {
89       case spv::Op::OpTypeSampler:
90         // Modules can't have duplicate sampler types.
91         assert(!sampler_type_);
92         sampler_type_ = &inst;
93         break;
94 
95       case spv::Op::OpTypeSampledImage:
96         if (!first_sampled_image_type_) {
97           first_sampled_image_type_ = &inst;
98         }
99         combined_types_.insert(inst.result_id());
100         def_use_mgr_->WhileEachUser(inst.result_id(), [&](Instruction* i) {
101           sampled_image_used_as_param_ |=
102               i->opcode() == spv::Op::OpTypeFunction;
103           return !sampled_image_used_as_param_;
104         });
105         break;
106 
107       case spv::Op::OpTypeArray:
108       case spv::Op::OpTypeRuntimeArray: {
109         auto pointee_id = inst.GetSingleWordInOperand(0);
110         if (combined_types_.find(pointee_id) != combined_types_.end()) {
111           combined_types_.insert(inst.result_id());
112           combined_types_to_remove_.push_back(inst.result_id());
113         }
114       } break;
115 
116       case spv::Op::OpTypePointer: {
117         auto sc =
118             static_cast<spv::StorageClass>(inst.GetSingleWordInOperand(0));
119         if (sc == spv::StorageClass::UniformConstant) {
120           auto pointee_id = inst.GetSingleWordInOperand(1);
121           if (combined_types_.find(pointee_id) != combined_types_.end()) {
122             combined_types_.insert(inst.result_id());
123             combined_types_to_remove_.push_back(inst.result_id());
124           }
125         }
126       } break;
127 
128       case spv::Op::OpVariable:
129         if (combined_types_.find(inst.type_id()) != combined_types_.end()) {
130           ordered_vars_.push_back(&inst);
131         }
132         break;
133 
134       default:
135         break;
136     }
137   }
138 }
139 
GetSamplerType()140 Instruction* SplitCombinedImageSamplerPass::GetSamplerType() {
141   if (!sampler_type_) {
142     analysis::Sampler s;
143     uint32_t sampler_type_id = type_mgr_->GetTypeInstruction(&s);
144     sampler_type_ = def_use_mgr_->GetDef(sampler_type_id);
145     assert(first_sampled_image_type_);
146     sampler_type_->InsertBefore(first_sampled_image_type_);
147     RegisterNewGlobal(sampler_type_->result_id());
148   }
149   return sampler_type_;
150 }
151 
RemapVars()152 spv_result_t SplitCombinedImageSamplerPass::RemapVars() {
153   for (Instruction* var : ordered_vars_) {
154     CHECK_STATUS(RemapVar(var));
155   }
156   return SPV_SUCCESS;
157 }
158 
SplitType(Instruction & combined_kind_type)159 std::pair<Instruction*, Instruction*> SplitCombinedImageSamplerPass::SplitType(
160     Instruction& combined_kind_type) {
161   if (auto where = type_remap_.find(combined_kind_type.result_id());
162       where != type_remap_.end()) {
163     auto& type_remap = where->second;
164     return {type_remap.image_kind_type, type_remap.sampler_kind_type};
165   }
166 
167   switch (combined_kind_type.opcode()) {
168     case spv::Op::OpTypeSampledImage: {
169       auto* image_type =
170           def_use_mgr_->GetDef(combined_kind_type.GetSingleWordInOperand(0));
171       auto* sampler_type = GetSamplerType();
172       type_remap_[combined_kind_type.result_id()] = {&combined_kind_type,
173                                                      image_type, sampler_type};
174       return {image_type, sampler_type};
175       break;
176     }
177     case spv::Op::OpTypePointer: {
178       auto sc = static_cast<spv::StorageClass>(
179           combined_kind_type.GetSingleWordInOperand(0));
180       if (sc == spv::StorageClass::UniformConstant) {
181         auto* pointee =
182             def_use_mgr_->GetDef(combined_kind_type.GetSingleWordInOperand(1));
183         auto [image_pointee, sampler_pointee] = SplitType(*pointee);
184         // These would be null if the pointee is an image type or a sampler
185         // type. Don't decompose them. Currently this method does not check the
186         // assumption that it is being only called on combined types. So code
187         // this defensively.
188         if (image_pointee && sampler_pointee) {
189           auto* ptr_image = MakeUniformConstantPointer(image_pointee);
190           auto* ptr_sampler = MakeUniformConstantPointer(sampler_pointee);
191           type_remap_[combined_kind_type.result_id()] = {
192               &combined_kind_type, ptr_image, ptr_sampler};
193           return {ptr_image, ptr_sampler};
194         }
195       }
196       break;
197     }
198     case spv::Op::OpTypeArray: {
199       const auto* array_ty =
200           type_mgr_->GetType(combined_kind_type.result_id())->AsArray();
201       assert(array_ty);
202       const auto* sampled_image_ty = array_ty->element_type()->AsSampledImage();
203       assert(sampled_image_ty);
204 
205       const analysis::Type* image_ty = sampled_image_ty->image_type();
206       assert(image_ty);
207       analysis::Array array_image_ty(image_ty, array_ty->length_info());
208       const uint32_t array_image_ty_id =
209           type_mgr_->GetTypeInstruction(&array_image_ty);
210       auto* array_image_ty_inst = def_use_mgr_->GetDef(array_image_ty_id);
211       if (!IsKnownGlobal(array_image_ty_id)) {
212         array_image_ty_inst->InsertBefore(&combined_kind_type);
213         RegisterNewGlobal(array_image_ty_id);
214         // GetTypeInstruction also updated the def-use manager.
215       }
216 
217       analysis::Array sampler_array_ty(
218           type_mgr_->GetType(GetSamplerType()->result_id()),
219           array_ty->length_info());
220       const uint32_t array_sampler_ty_id =
221           type_mgr_->GetTypeInstruction(&sampler_array_ty);
222       auto* array_sampler_ty_inst = def_use_mgr_->GetDef(array_sampler_ty_id);
223       if (!IsKnownGlobal(array_sampler_ty_id)) {
224         array_sampler_ty_inst->InsertBefore(&combined_kind_type);
225         RegisterNewGlobal(array_sampler_ty_id);
226         // GetTypeInstruction also updated the def-use manager.
227       }
228       return {array_image_ty_inst, array_sampler_ty_inst};
229     }
230     case spv::Op::OpTypeRuntimeArray: {
231       // This is like the sized-array case, but there is no length parameter.
232       auto* array_ty =
233           type_mgr_->GetType(combined_kind_type.result_id())->AsRuntimeArray();
234       assert(array_ty);
235       auto* sampled_image_ty = array_ty->element_type()->AsSampledImage();
236       assert(sampled_image_ty);
237 
238       const analysis::Type* image_ty = sampled_image_ty->image_type();
239       assert(image_ty);
240       analysis::RuntimeArray array_image_ty(image_ty);
241       const uint32_t array_image_ty_id =
242           type_mgr_->GetTypeInstruction(&array_image_ty);
243       auto* array_image_ty_inst = def_use_mgr_->GetDef(array_image_ty_id);
244       if (!IsKnownGlobal(array_image_ty_id)) {
245         array_image_ty_inst->InsertBefore(&combined_kind_type);
246         RegisterNewGlobal(array_image_ty_id);
247         // GetTypeInstruction also updated the def-use manager.
248       }
249 
250       analysis::RuntimeArray sampler_array_ty(
251           type_mgr_->GetType(GetSamplerType()->result_id()));
252       const uint32_t array_sampler_ty_id =
253           type_mgr_->GetTypeInstruction(&sampler_array_ty);
254       auto* array_sampler_ty_inst = def_use_mgr_->GetDef(array_sampler_ty_id);
255       if (!IsKnownGlobal(array_sampler_ty_id)) {
256         array_sampler_ty_inst->InsertBefore(&combined_kind_type);
257         RegisterNewGlobal(array_sampler_ty_id);
258         // GetTypeInstruction also updated the def-use manager.
259       }
260       return {array_image_ty_inst, array_sampler_ty_inst};
261     }
262     default:
263       break;
264   }
265   return {nullptr, nullptr};
266 }
267 
RemapVar(Instruction * combined_var)268 spv_result_t SplitCombinedImageSamplerPass::RemapVar(
269     Instruction* combined_var) {
270   InstructionBuilder builder(context(), combined_var,
271                              IRContext::kAnalysisDefUse);
272 
273   // Create an image variable, and a sampler variable.
274   auto* combined_var_type = def_use_mgr_->GetDef(combined_var->type_id());
275   auto [ptr_image_ty, ptr_sampler_ty] = SplitType(*combined_var_type);
276   assert(ptr_image_ty);
277   assert(ptr_sampler_ty);
278   Instruction* sampler_var = builder.AddVariable(
279       ptr_sampler_ty->result_id(), SpvStorageClassUniformConstant);
280   Instruction* image_var = builder.AddVariable(ptr_image_ty->result_id(),
281                                                SpvStorageClassUniformConstant);
282   modified_ = true;
283   return RemapUses(combined_var, image_var, sampler_var);
284 }
285 
RemapUses(Instruction * combined,Instruction * image_part,Instruction * sampler_part)286 spv_result_t SplitCombinedImageSamplerPass::RemapUses(
287     Instruction* combined, Instruction* image_part, Instruction* sampler_part) {
288   // The instructions to delete.
289   std::unordered_set<Instruction*> dead_insts;
290   // The insertion point should be updated before using this builder.
291   // We needed *something* here.
292   InstructionBuilder builder(context(), combined, IRContext::kAnalysisDefUse);
293 
294   // This code must maintain the SPIR-V "Data rule" about sampled image values:
295   //  > All OpSampledImage instructions, or instructions that load an image or
296   //  > sampler reference, must be in the same block in which their Result <id>
297   //  > are consumed.
298   //
299   // When the code below inserts OpSampledImage instructions, it is always
300   // either:
301   // - in the same block as the previous OpSampledImage instruction it is
302   //   replacing, or
303   // - in the same block as the instruction using sampled image value it is
304   //   replacing.
305   //
306   // Assuming that rule is already honoured by the module, these updates will
307   // continue to honour the rule.
308 
309   // Represents a single use of a value to be remapped.
310   struct RemapUse {
311     uint32_t used_id;  // The ID that is being used.
312     Instruction* user;
313     uint32_t index;
314     Instruction* image_part;    // The image part of the replacement.
315     Instruction* sampler_part;  // The sampler part of the replacement.
316   };
317   // The work list of uses to be remapped.
318   std::vector<RemapUse> uses;
319 
320   // Adds remap records for each use of a value to be remapped.
321   // Also schedules the original value for deletion.
322   auto add_remap = [this, &dead_insts, &uses](Instruction* combined_arg,
323                                               Instruction* image_part_arg,
324                                               Instruction* sampler_part_arg) {
325     const uint32_t used_combined_id = combined_arg->result_id();
326 
327     def_use_mgr_->ForEachUse(
328         combined_arg, [&](Instruction* user, uint32_t use_index) {
329           uses.push_back({used_combined_id, user, use_index, image_part_arg,
330                           sampler_part_arg});
331         });
332     dead_insts.insert(combined_arg);
333   };
334 
335   add_remap(combined, image_part, sampler_part);
336 
337   // Use index-based iteration because we can add to the work list as we go
338   // along, and reallocation would invalidate ordinary iterators.
339   for (size_t use_index = 0; use_index < uses.size(); ++use_index) {
340     auto& use = uses[use_index];
341     switch (use.user->opcode()) {
342       case spv::Op::OpCopyObject: {
343         // Append the uses of this OpCopyObject to the work list.
344         add_remap(use.user, image_part, sampler_part);
345         break;
346       }
347       case spv::Op::OpLoad: {
348         assert(use.index == 2 && "variable used as non-pointer index on load");
349         Instruction* load = use.user;
350 
351         // Assume the loaded value is a sampled image.
352         assert(def_use_mgr_->GetDef(load->type_id())->opcode() ==
353                spv::Op::OpTypeSampledImage);
354 
355         // Create loads for the image part and sampler part.
356         builder.SetInsertPoint(load);
357         auto* image = builder.AddLoad(PointeeTypeId(use.image_part),
358                                       use.image_part->result_id());
359         auto* sampler = builder.AddLoad(PointeeTypeId(use.sampler_part),
360                                         use.sampler_part->result_id());
361 
362         // Move decorations, such as RelaxedPrecision.
363         auto* deco_mgr = context()->get_decoration_mgr();
364         deco_mgr->CloneDecorations(load->result_id(), image->result_id());
365         deco_mgr->CloneDecorations(load->result_id(), sampler->result_id());
366         deco_mgr->RemoveDecorationsFrom(load->result_id());
367 
368         // Create a sampled image from the loads of the two parts.
369         auto* sampled_image = builder.AddSampledImage(
370             load->type_id(), image->result_id(), sampler->result_id());
371         // Replace the original sampled image value with the new one.
372         std::unordered_set<Instruction*> users;
373         def_use_mgr_->ForEachUse(
374             load, [&users, sampled_image](Instruction* user, uint32_t index) {
375               user->SetOperand(index, {sampled_image->result_id()});
376               users.insert(user);
377             });
378         for (auto* user : users) {
379           def_use_mgr_->AnalyzeInstUse(user);
380         }
381         dead_insts.insert(load);
382         break;
383       }
384       case spv::Op::OpDecorate: {
385         assert(use.index == 0 && "variable used as non-target index");
386         builder.SetInsertPoint(use.user);
387         spv::Decoration deco{use.user->GetSingleWordInOperand(1)};
388         std::vector<uint32_t> literals;
389         for (uint32_t i = 2; i < use.user->NumInOperands(); i++) {
390           literals.push_back(use.user->GetSingleWordInOperand(i));
391         }
392         builder.AddDecoration(use.image_part->result_id(), deco, literals);
393         builder.AddDecoration(use.sampler_part->result_id(), deco, literals);
394         // KillInst will delete names and decorations, so don't schedule a
395         // deletion of this instruction.
396         break;
397       }
398       case spv::Op::OpEntryPoint: {
399         // The entry point lists variables in the shader interface, i.e.
400         // module-scope variables referenced by the static call tree rooted
401         // at the entry point. (It can be a proper superset).  Before SPIR-V
402         // 1.4, only Input and Output variables are listed; in 1.4 and later,
403         // module-scope variables in all storage classes are listed.
404         // If a combined image+sampler is listed by the entry point, then
405         // the separated image and sampler variables should be.
406         assert(use.index >= 3 &&
407                "variable used in OpEntryPoint but not as an interface ID");
408         use.user->SetOperand(use.index, {use.image_part->result_id()});
409         use.user->InsertOperand(
410             use.user->NumOperands(),
411             {SPV_OPERAND_TYPE_ID, {use.sampler_part->result_id()}});
412         def_use_mgr_->AnalyzeInstUse(use.user);
413         break;
414       }
415       case spv::Op::OpName: {
416         // Synthesize new names from the old.
417         const auto name = use.user->GetOperand(1).AsString();
418         AddOpName(use.image_part->result_id(), name + "_image");
419         AddOpName(use.sampler_part->result_id(), name + "_sampler");
420 
421         // KillInst will delete names and decorations, so don't schedule a
422         // deletion of this instruction.
423         break;
424       }
425       case spv::Op::OpFunctionCall: {
426         // Replace each combined arg with two args: the image part, then the
427         // sampler part.
428         // The combined value could have been used twice in the argument list.
429         // Moving things around now will invalidate the 'use' list above.
430         // So don't trust the use index value.
431         auto& call = *use.user;
432         // The insert API only takes absolute arg IDs, not "in" arg IDs.
433         const auto first_arg_operand_index = 3;  // Skip the callee ID
434         for (uint32_t i = first_arg_operand_index; i < call.NumOperands();
435              ++i) {
436           if (use.used_id == call.GetSingleWordOperand(i)) {
437             call.SetOperand(i, {use.sampler_part->result_id()});
438             call.InsertOperand(
439                 i, {SPV_OPERAND_TYPE_ID, {use.image_part->result_id()}});
440             ++i;
441           }
442         }
443         def_use_mgr_->AnalyzeInstUse(&call);
444         break;
445       }
446       case spv::Op::OpAccessChain:
447       case spv::Op::OpInBoundsAccessChain: {
448         auto* original_access_chain = use.user;
449         builder.SetInsertPoint(original_access_chain);
450         // It can only be the base pointer
451         assert(use.index == 2);
452 
453         // Replace the original access chain with access chains for the image
454         // part and the sampler part.
455         std::vector<uint32_t> indices;
456         for (uint32_t i = 3; i < original_access_chain->NumOperands(); i++) {
457           indices.push_back(original_access_chain->GetSingleWordOperand(i));
458         }
459 
460         auto [result_image_part_ty, result_sampler_part_ty] =
461             SplitType(*def_use_mgr_->GetDef(original_access_chain->type_id()));
462         auto* result_image_part = builder.AddOpcodeAccessChain(
463             use.user->opcode(), result_image_part_ty->result_id(),
464             use.image_part->result_id(), indices);
465         auto* result_sampler_part = builder.AddOpcodeAccessChain(
466             use.user->opcode(), result_sampler_part_ty->result_id(),
467             use.sampler_part->result_id(), indices);
468 
469         // Remap uses of the original access chain.
470         add_remap(original_access_chain, result_image_part,
471                   result_sampler_part);
472         break;
473       }
474       default: {
475         uint32_t used_type_id = def_use_mgr_->GetDef(use.used_id)->type_id();
476         auto* used_type = def_use_mgr_->GetDef(used_type_id);
477         if (used_type->opcode() == spv::Op::OpTypeSampledImage) {
478           // This value being used is a sampled image value.  But it's
479           // being replaced, so recreate it here.
480           // Example: used by OpImage, OpImageSampleExplicitLod, etc.
481           builder.SetInsertPoint(use.user);
482           auto* sampled_image =
483               builder.AddSampledImage(used_type_id, use.image_part->result_id(),
484                                       use.sampler_part->result_id());
485           use.user->SetOperand(use.index, {sampled_image->result_id()});
486           def_use_mgr_->AnalyzeInstUse(use.user);
487           break;
488         }
489         return Fail() << "unhandled user: " << *use.user;
490       }
491     }
492   }
493 
494   for (auto* inst : dead_insts) {
495     KillInst(inst);
496   }
497 
498   return SPV_SUCCESS;
499 }
500 
RemapFunctions()501 spv_result_t SplitCombinedImageSamplerPass::RemapFunctions() {
502   // Remap function types. A combined type can appear as a parameter, but not as
503   // the return type.
504   {
505     std::unordered_set<Instruction*> dead_insts;
506     for (auto& inst : context()->types_values()) {
507       if (inst.opcode() != spv::Op::OpTypeFunction) {
508         continue;
509       }
510       analysis::Function* f_ty =
511           type_mgr_->GetType(inst.result_id())->AsFunction();
512       std::vector<const analysis::Type*> new_params;
513       for (const auto* param_ty : f_ty->param_types()) {
514         const auto param_ty_id = type_mgr_->GetId(param_ty);
515         if (combined_types_.find(param_ty_id) != combined_types_.end()) {
516           auto* param_type = def_use_mgr_->GetDef(param_ty_id);
517           auto [image_type, sampler_type] = SplitType(*param_type);
518           assert(image_type);
519           assert(sampler_type);
520           // The image and sampler types must already exist, so there is no
521           // need to move them to the right spot.
522           new_params.push_back(type_mgr_->GetType(image_type->result_id()));
523           new_params.push_back(type_mgr_->GetType(sampler_type->result_id()));
524         } else {
525           new_params.push_back(param_ty);
526         }
527       }
528       if (new_params.size() != f_ty->param_types().size()) {
529         // Replace this type.
530         analysis::Function new_f_ty(f_ty->return_type(), new_params);
531         const uint32_t new_f_ty_id = type_mgr_->GetTypeInstruction(&new_f_ty);
532         std::unordered_set<Instruction*> users;
533         def_use_mgr_->ForEachUse(
534             &inst,
535             [&users, new_f_ty_id](Instruction* user, uint32_t use_index) {
536               user->SetOperand(use_index, {new_f_ty_id});
537               users.insert(user);
538             });
539         for (auto* user : users) {
540           def_use_mgr_->AnalyzeInstUse(user);
541         }
542         dead_insts.insert(&inst);
543       }
544     }
545     for (auto* inst : dead_insts) {
546       KillInst(inst);
547     }
548   }
549 
550   // Rewite OpFunctionParameter in function definitions.
551   for (Function& fn : *context()->module()) {
552     // Rewrite the function parameters and record their replacements.
553     struct Replacement {
554       Instruction* combined;
555       Instruction* image;
556       Instruction* sampler;
557     };
558     std::vector<Replacement> replacements;
559 
560     Function::RewriteParamFn rewriter =
561         [&](std::unique_ptr<Instruction>&& param,
562             std::back_insert_iterator<Function::ParamList>& appender) {
563           if (combined_types_.count(param->type_id()) == 0) {
564             appender = std::move(param);
565             return;
566           }
567 
568           // Replace this parameter with two new parameters.
569           auto* combined_inst = param.release();
570           auto* combined_type = def_use_mgr_->GetDef(combined_inst->type_id());
571           auto [image_type, sampler_type] = SplitType(*combined_type);
572           auto image_param = MakeUnique<Instruction>(
573               context(), spv::Op::OpFunctionParameter, image_type->result_id(),
574               context()->TakeNextId(), Instruction::OperandList{});
575           auto sampler_param = MakeUnique<Instruction>(
576               context(), spv::Op::OpFunctionParameter,
577               sampler_type->result_id(), context()->TakeNextId(),
578               Instruction::OperandList{});
579           replacements.push_back(
580               {combined_inst, image_param.get(), sampler_param.get()});
581           appender = std::move(image_param);
582           appender = std::move(sampler_param);
583         };
584     fn.RewriteParams(rewriter);
585 
586     for (auto& r : replacements) {
587       modified_ = true;
588       def_use_mgr_->AnalyzeInstDefUse(r.image);
589       def_use_mgr_->AnalyzeInstDefUse(r.sampler);
590       CHECK_STATUS(RemapUses(r.combined, r.image, r.sampler));
591     }
592   }
593   return SPV_SUCCESS;
594 }
595 
MakeUniformConstantPointer(Instruction * pointee)596 Instruction* SplitCombinedImageSamplerPass::MakeUniformConstantPointer(
597     Instruction* pointee) {
598   uint32_t ptr_id = type_mgr_->FindPointerToType(
599       pointee->result_id(), spv::StorageClass::UniformConstant);
600   auto* ptr = def_use_mgr_->GetDef(ptr_id);
601   if (!IsKnownGlobal(ptr_id)) {
602     // The pointer type was created at the end. Put it right after the
603     // pointee.
604     ptr->InsertBefore(pointee);
605     pointee->InsertBefore(ptr);
606     RegisterNewGlobal(ptr_id);
607     // FindPointerToType also updated the def-use manager.
608   }
609   return ptr;
610 }
611 
AddOpName(uint32_t id,const std::string & name)612 void SplitCombinedImageSamplerPass::AddOpName(uint32_t id,
613                                               const std::string& name) {
614   std::unique_ptr<Instruction> opname{new Instruction{
615       context(),
616       spv::Op::OpName,
617       0u,
618       0u,
619       {{SPV_OPERAND_TYPE_ID, {id}},
620        {SPV_OPERAND_TYPE_LITERAL_STRING,
621         utils::MakeVector<spvtools::opt::Operand::OperandData>(name)}}}};
622 
623   context()->AddDebug2Inst(std::move(opname));
624 }
625 
RemoveDeadTypes()626 spv_result_t SplitCombinedImageSamplerPass::RemoveDeadTypes() {
627   for (auto dead_type_id : combined_types_to_remove_) {
628     if (auto* ty = def_use_mgr_->GetDef(dead_type_id)) {
629       KillInst(ty);
630     }
631   }
632   return SPV_SUCCESS;
633 }
634 
KillInst(Instruction * inst)635 void SplitCombinedImageSamplerPass::KillInst(Instruction* inst) {
636   // IRContext::KillInst will remove associated debug instructions and
637   // decorations. It will delete the object only if it is already in a list.
638   const bool was_in_list = inst->IsInAList();
639   context()->KillInst(inst);
640   if (!was_in_list) {
641     // Avoid leaking
642     delete inst;
643   }
644   modified_ = true;
645 }
646 
647 }  // namespace opt
648 }  // namespace spvtools
649