• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2019 The Khronos Group Inc.
2 // Copyright (c) 2019 Valve Corporation
3 // Copyright (c) 2019 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 "convert_to_half_pass.h"
18 
19 #include "source/opt/ir_builder.h"
20 
21 namespace spvtools {
22 namespace opt {
23 namespace {
24 // Indices of operands in SPIR-V instructions
25 constexpr int kImageSampleDrefIdInIdx = 2;
26 }  // namespace
27 
IsArithmetic(Instruction * inst)28 bool ConvertToHalfPass::IsArithmetic(Instruction* inst) {
29   return target_ops_core_.count(inst->opcode()) != 0 ||
30          (inst->opcode() == spv::Op::OpExtInst &&
31           inst->GetSingleWordInOperand(0) ==
32               context()->get_feature_mgr()->GetExtInstImportId_GLSLstd450() &&
33           target_ops_450_.count(inst->GetSingleWordInOperand(1)) != 0);
34 }
35 
IsFloat(Instruction * inst,uint32_t width)36 bool ConvertToHalfPass::IsFloat(Instruction* inst, uint32_t width) {
37   uint32_t ty_id = inst->type_id();
38   if (ty_id == 0) return false;
39   return Pass::IsFloat(ty_id, width);
40 }
41 
IsStruct(Instruction * inst)42 bool ConvertToHalfPass::IsStruct(Instruction* inst) {
43   uint32_t ty_id = inst->type_id();
44   if (ty_id == 0) return false;
45   Instruction* ty_inst = Pass::GetBaseType(ty_id);
46   return (ty_inst->opcode() == spv::Op::OpTypeStruct);
47 }
48 
IsDecoratedRelaxed(Instruction * inst)49 bool ConvertToHalfPass::IsDecoratedRelaxed(Instruction* inst) {
50   uint32_t r_id = inst->result_id();
51   for (auto r_inst : get_decoration_mgr()->GetDecorationsFor(r_id, false))
52     if (r_inst->opcode() == spv::Op::OpDecorate &&
53         spv::Decoration(r_inst->GetSingleWordInOperand(1)) ==
54             spv::Decoration::RelaxedPrecision) {
55       return true;
56     }
57   return false;
58 }
59 
IsRelaxed(uint32_t id)60 bool ConvertToHalfPass::IsRelaxed(uint32_t id) {
61   return relaxed_ids_set_.count(id) > 0;
62 }
63 
AddRelaxed(uint32_t id)64 void ConvertToHalfPass::AddRelaxed(uint32_t id) { relaxed_ids_set_.insert(id); }
65 
CanRelaxOpOperands(Instruction * inst)66 bool ConvertToHalfPass::CanRelaxOpOperands(Instruction* inst) {
67   return image_ops_.count(inst->opcode()) == 0;
68 }
69 
FloatScalarType(uint32_t width)70 analysis::Type* ConvertToHalfPass::FloatScalarType(uint32_t width) {
71   analysis::Float float_ty(width);
72   return context()->get_type_mgr()->GetRegisteredType(&float_ty);
73 }
74 
FloatVectorType(uint32_t v_len,uint32_t width)75 analysis::Type* ConvertToHalfPass::FloatVectorType(uint32_t v_len,
76                                                    uint32_t width) {
77   analysis::Type* reg_float_ty = FloatScalarType(width);
78   analysis::Vector vec_ty(reg_float_ty, v_len);
79   return context()->get_type_mgr()->GetRegisteredType(&vec_ty);
80 }
81 
FloatMatrixType(uint32_t v_cnt,uint32_t vty_id,uint32_t width)82 analysis::Type* ConvertToHalfPass::FloatMatrixType(uint32_t v_cnt,
83                                                    uint32_t vty_id,
84                                                    uint32_t width) {
85   Instruction* vty_inst = get_def_use_mgr()->GetDef(vty_id);
86   uint32_t v_len = vty_inst->GetSingleWordInOperand(1);
87   analysis::Type* reg_vec_ty = FloatVectorType(v_len, width);
88   analysis::Matrix mat_ty(reg_vec_ty, v_cnt);
89   return context()->get_type_mgr()->GetRegisteredType(&mat_ty);
90 }
91 
EquivFloatTypeId(uint32_t ty_id,uint32_t width)92 uint32_t ConvertToHalfPass::EquivFloatTypeId(uint32_t ty_id, uint32_t width) {
93   analysis::Type* reg_equiv_ty;
94   Instruction* ty_inst = get_def_use_mgr()->GetDef(ty_id);
95   if (ty_inst->opcode() == spv::Op::OpTypeMatrix)
96     reg_equiv_ty = FloatMatrixType(ty_inst->GetSingleWordInOperand(1),
97                                    ty_inst->GetSingleWordInOperand(0), width);
98   else if (ty_inst->opcode() == spv::Op::OpTypeVector)
99     reg_equiv_ty = FloatVectorType(ty_inst->GetSingleWordInOperand(1), width);
100   else  // spv::Op::OpTypeFloat
101     reg_equiv_ty = FloatScalarType(width);
102   return context()->get_type_mgr()->GetTypeInstruction(reg_equiv_ty);
103 }
104 
GenConvert(uint32_t * val_idp,uint32_t width,Instruction * inst)105 void ConvertToHalfPass::GenConvert(uint32_t* val_idp, uint32_t width,
106                                    Instruction* inst) {
107   Instruction* val_inst = get_def_use_mgr()->GetDef(*val_idp);
108   uint32_t ty_id = val_inst->type_id();
109   uint32_t nty_id = EquivFloatTypeId(ty_id, width);
110   if (nty_id == ty_id) return;
111   Instruction* cvt_inst;
112   InstructionBuilder builder(
113       context(), inst,
114       IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
115   if (val_inst->opcode() == spv::Op::OpUndef)
116     cvt_inst = builder.AddNullaryOp(nty_id, spv::Op::OpUndef);
117   else
118     cvt_inst = builder.AddUnaryOp(nty_id, spv::Op::OpFConvert, *val_idp);
119   *val_idp = cvt_inst->result_id();
120 }
121 
MatConvertCleanup(Instruction * inst)122 bool ConvertToHalfPass::MatConvertCleanup(Instruction* inst) {
123   if (inst->opcode() != spv::Op::OpFConvert) return false;
124   uint32_t mty_id = inst->type_id();
125   Instruction* mty_inst = get_def_use_mgr()->GetDef(mty_id);
126   if (mty_inst->opcode() != spv::Op::OpTypeMatrix) return false;
127   uint32_t vty_id = mty_inst->GetSingleWordInOperand(0);
128   uint32_t v_cnt = mty_inst->GetSingleWordInOperand(1);
129   Instruction* vty_inst = get_def_use_mgr()->GetDef(vty_id);
130   uint32_t cty_id = vty_inst->GetSingleWordInOperand(0);
131   Instruction* cty_inst = get_def_use_mgr()->GetDef(cty_id);
132   InstructionBuilder builder(
133       context(), inst,
134       IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
135   // Convert each component vector, combine them with OpCompositeConstruct
136   // and replace original instruction.
137   uint32_t orig_width = (cty_inst->GetSingleWordInOperand(0) == 16) ? 32 : 16;
138   uint32_t orig_mat_id = inst->GetSingleWordInOperand(0);
139   uint32_t orig_vty_id = EquivFloatTypeId(vty_id, orig_width);
140   std::vector<Operand> opnds = {};
141   for (uint32_t vidx = 0; vidx < v_cnt; ++vidx) {
142     Instruction* ext_inst = builder.AddIdLiteralOp(
143         orig_vty_id, spv::Op::OpCompositeExtract, orig_mat_id, vidx);
144     Instruction* cvt_inst =
145         builder.AddUnaryOp(vty_id, spv::Op::OpFConvert, ext_inst->result_id());
146     opnds.push_back({SPV_OPERAND_TYPE_ID, {cvt_inst->result_id()}});
147   }
148   uint32_t mat_id = TakeNextId();
149   std::unique_ptr<Instruction> mat_inst(new Instruction(
150       context(), spv::Op::OpCompositeConstruct, mty_id, mat_id, opnds));
151   (void)builder.AddInstruction(std::move(mat_inst));
152   context()->ReplaceAllUsesWith(inst->result_id(), mat_id);
153   // Turn original instruction into copy so it is valid.
154   inst->SetOpcode(spv::Op::OpCopyObject);
155   inst->SetResultType(EquivFloatTypeId(mty_id, orig_width));
156   get_def_use_mgr()->AnalyzeInstUse(inst);
157   return true;
158 }
159 
RemoveRelaxedDecoration(uint32_t id)160 bool ConvertToHalfPass::RemoveRelaxedDecoration(uint32_t id) {
161   return context()->get_decoration_mgr()->RemoveDecorationsFrom(
162       id, [](const Instruction& dec) {
163         if (dec.opcode() == spv::Op::OpDecorate &&
164             spv::Decoration(dec.GetSingleWordInOperand(1u)) ==
165                 spv::Decoration::RelaxedPrecision) {
166           return true;
167         } else
168           return false;
169       });
170 }
171 
GenHalfArith(Instruction * inst)172 bool ConvertToHalfPass::GenHalfArith(Instruction* inst) {
173   bool modified = false;
174   // If this is a OpCompositeExtract instruction and has a struct operand, we
175   // should not relax this instruction. Doing so could cause a mismatch between
176   // the result type and the struct member type.
177   bool hasStructOperand = false;
178   if (inst->opcode() == spv::Op::OpCompositeExtract) {
179     inst->ForEachInId([&hasStructOperand, this](uint32_t* idp) {
180       Instruction* op_inst = get_def_use_mgr()->GetDef(*idp);
181       if (IsStruct(op_inst)) hasStructOperand = true;
182     });
183     if (hasStructOperand) {
184       return false;
185     }
186   }
187   // Convert all float32 based operands to float16 equivalent and change
188   // instruction type to float16 equivalent.
189   inst->ForEachInId([&inst, &modified, this](uint32_t* idp) {
190     Instruction* op_inst = get_def_use_mgr()->GetDef(*idp);
191     if (!IsFloat(op_inst, 32)) return;
192     GenConvert(idp, 16, inst);
193     modified = true;
194   });
195   if (IsFloat(inst, 32)) {
196     inst->SetResultType(EquivFloatTypeId(inst->type_id(), 16));
197     converted_ids_.insert(inst->result_id());
198     modified = true;
199   }
200   if (modified) get_def_use_mgr()->AnalyzeInstUse(inst);
201   return modified;
202 }
203 
ProcessPhi(Instruction * inst,uint32_t from_width,uint32_t to_width)204 bool ConvertToHalfPass::ProcessPhi(Instruction* inst, uint32_t from_width,
205                                    uint32_t to_width) {
206   // Add converts of any float operands to to_width if they are of from_width.
207   // If converting to 16, change type of phi to float16 equivalent and remember
208   // result id. Converts need to be added to preceding blocks.
209   uint32_t ocnt = 0;
210   uint32_t* prev_idp;
211   bool modified = false;
212   inst->ForEachInId([&ocnt, &prev_idp, &from_width, &to_width, &modified,
213                      this](uint32_t* idp) {
214     if (ocnt % 2 == 0) {
215       prev_idp = idp;
216     } else {
217       Instruction* val_inst = get_def_use_mgr()->GetDef(*prev_idp);
218       if (IsFloat(val_inst, from_width)) {
219         BasicBlock* bp = context()->get_instr_block(*idp);
220         auto insert_before = bp->tail();
221         if (insert_before != bp->begin()) {
222           --insert_before;
223           if (insert_before->opcode() != spv::Op::OpSelectionMerge &&
224               insert_before->opcode() != spv::Op::OpLoopMerge)
225             ++insert_before;
226         }
227         GenConvert(prev_idp, to_width, &*insert_before);
228         modified = true;
229       }
230     }
231     ++ocnt;
232   });
233   if (to_width == 16u) {
234     inst->SetResultType(EquivFloatTypeId(inst->type_id(), 16u));
235     converted_ids_.insert(inst->result_id());
236     modified = true;
237   }
238   if (modified) get_def_use_mgr()->AnalyzeInstUse(inst);
239   return modified;
240 }
241 
ProcessConvert(Instruction * inst)242 bool ConvertToHalfPass::ProcessConvert(Instruction* inst) {
243   // If float32 and relaxed, change to float16 convert
244   if (IsFloat(inst, 32) && IsRelaxed(inst->result_id())) {
245     inst->SetResultType(EquivFloatTypeId(inst->type_id(), 16));
246     get_def_use_mgr()->AnalyzeInstUse(inst);
247     converted_ids_.insert(inst->result_id());
248   }
249   // If operand and result types are the same, change FConvert to CopyObject to
250   // keep validator happy; simplification and DCE will clean it up
251   // One way this can happen is if an FConvert generated during this pass
252   // (likely by ProcessPhi) is later encountered here and its operand has been
253   // changed to half.
254   uint32_t val_id = inst->GetSingleWordInOperand(0);
255   Instruction* val_inst = get_def_use_mgr()->GetDef(val_id);
256   if (inst->type_id() == val_inst->type_id())
257     inst->SetOpcode(spv::Op::OpCopyObject);
258   return true;  // modified
259 }
260 
ProcessImageRef(Instruction * inst)261 bool ConvertToHalfPass::ProcessImageRef(Instruction* inst) {
262   bool modified = false;
263   // If image reference, only need to convert dref args back to float32
264   if (dref_image_ops_.count(inst->opcode()) != 0) {
265     uint32_t dref_id = inst->GetSingleWordInOperand(kImageSampleDrefIdInIdx);
266     if (converted_ids_.count(dref_id) > 0) {
267       GenConvert(&dref_id, 32, inst);
268       inst->SetInOperand(kImageSampleDrefIdInIdx, {dref_id});
269       get_def_use_mgr()->AnalyzeInstUse(inst);
270       modified = true;
271     }
272   }
273   return modified;
274 }
275 
ProcessDefault(Instruction * inst)276 bool ConvertToHalfPass::ProcessDefault(Instruction* inst) {
277   // If non-relaxed instruction has changed operands, need to convert
278   // them back to float32
279   if (inst->opcode() == spv::Op::OpPhi) return ProcessPhi(inst, 16u, 32u);
280   bool modified = false;
281   inst->ForEachInId([&inst, &modified, this](uint32_t* idp) {
282     if (converted_ids_.count(*idp) == 0) return;
283     uint32_t old_id = *idp;
284     GenConvert(idp, 32, inst);
285     if (*idp != old_id) modified = true;
286   });
287   if (modified) get_def_use_mgr()->AnalyzeInstUse(inst);
288   return modified;
289 }
290 
GenHalfInst(Instruction * inst)291 bool ConvertToHalfPass::GenHalfInst(Instruction* inst) {
292   bool modified = false;
293   // Remember id for later deletion of RelaxedPrecision decoration
294   bool inst_relaxed = IsRelaxed(inst->result_id());
295   if (IsArithmetic(inst) && inst_relaxed)
296     modified = GenHalfArith(inst);
297   else if (inst->opcode() == spv::Op::OpPhi && inst_relaxed)
298     modified = ProcessPhi(inst, 32u, 16u);
299   else if (inst->opcode() == spv::Op::OpFConvert)
300     modified = ProcessConvert(inst);
301   else if (image_ops_.count(inst->opcode()) != 0)
302     modified = ProcessImageRef(inst);
303   else
304     modified = ProcessDefault(inst);
305   return modified;
306 }
307 
CloseRelaxInst(Instruction * inst)308 bool ConvertToHalfPass::CloseRelaxInst(Instruction* inst) {
309   if (inst->result_id() == 0) return false;
310   if (IsRelaxed(inst->result_id())) return false;
311   if (!IsFloat(inst, 32)) return false;
312   if (IsDecoratedRelaxed(inst)) {
313     AddRelaxed(inst->result_id());
314     return true;
315   }
316   if (closure_ops_.count(inst->opcode()) == 0) return false;
317   // Can relax if all float operands are relaxed
318   bool relax = true;
319   bool hasStructOperand = false;
320   inst->ForEachInId([&relax, &hasStructOperand, this](uint32_t* idp) {
321     Instruction* op_inst = get_def_use_mgr()->GetDef(*idp);
322     if (IsStruct(op_inst)) hasStructOperand = true;
323     if (!IsFloat(op_inst, 32)) return;
324     if (!IsRelaxed(*idp)) relax = false;
325   });
326   // If the instruction has a struct operand, we should not relax it, even if
327   // all its uses are relaxed. Doing so could cause a mismatch between the
328   // result type and the struct member type.
329   if (hasStructOperand) {
330     return false;
331   }
332   if (relax) {
333     AddRelaxed(inst->result_id());
334     return true;
335   }
336   // Can relax if all uses are relaxed
337   relax = true;
338   get_def_use_mgr()->ForEachUser(inst, [&relax, this](Instruction* uinst) {
339     if (uinst->result_id() == 0 || !IsFloat(uinst, 32) ||
340         (!IsDecoratedRelaxed(uinst) && !IsRelaxed(uinst->result_id())) ||
341         !CanRelaxOpOperands(uinst)) {
342       relax = false;
343       return;
344     }
345   });
346   if (relax) {
347     AddRelaxed(inst->result_id());
348     return true;
349   }
350   return false;
351 }
352 
ProcessFunction(Function * func)353 bool ConvertToHalfPass::ProcessFunction(Function* func) {
354   // Do a closure of Relaxed on composite and phi instructions
355   bool changed = true;
356   while (changed) {
357     changed = false;
358     cfg()->ForEachBlockInReversePostOrder(
359         func->entry().get(), [&changed, this](BasicBlock* bb) {
360           for (auto ii = bb->begin(); ii != bb->end(); ++ii)
361             changed |= CloseRelaxInst(&*ii);
362         });
363   }
364   // Do convert of relaxed instructions to half precision
365   bool modified = false;
366   cfg()->ForEachBlockInReversePostOrder(
367       func->entry().get(), [&modified, this](BasicBlock* bb) {
368         for (auto ii = bb->begin(); ii != bb->end(); ++ii)
369           modified |= GenHalfInst(&*ii);
370       });
371   // Replace invalid converts of matrix into equivalent vector extracts,
372   // converts and finally a composite construct
373   cfg()->ForEachBlockInReversePostOrder(
374       func->entry().get(), [&modified, this](BasicBlock* bb) {
375         for (auto ii = bb->begin(); ii != bb->end(); ++ii)
376           modified |= MatConvertCleanup(&*ii);
377       });
378   return modified;
379 }
380 
ProcessImpl()381 Pass::Status ConvertToHalfPass::ProcessImpl() {
382   Pass::ProcessFunction pfn = [this](Function* fp) {
383     return ProcessFunction(fp);
384   };
385   bool modified = context()->ProcessReachableCallTree(pfn);
386   // If modified, make sure module has Float16 capability
387   if (modified) context()->AddCapability(spv::Capability::Float16);
388   // Remove all RelaxedPrecision decorations from instructions and globals
389   for (auto c_id : relaxed_ids_set_) {
390     modified |= RemoveRelaxedDecoration(c_id);
391   }
392   for (auto& val : get_module()->types_values()) {
393     uint32_t v_id = val.result_id();
394     if (v_id != 0) {
395       modified |= RemoveRelaxedDecoration(v_id);
396     }
397   }
398   return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
399 }
400 
Process()401 Pass::Status ConvertToHalfPass::Process() {
402   Initialize();
403   return ProcessImpl();
404 }
405 
Initialize()406 void ConvertToHalfPass::Initialize() {
407   target_ops_core_ = {
408       spv::Op::OpVectorExtractDynamic,
409       spv::Op::OpVectorInsertDynamic,
410       spv::Op::OpVectorShuffle,
411       spv::Op::OpCompositeConstruct,
412       spv::Op::OpCompositeInsert,
413       spv::Op::OpCompositeExtract,
414       spv::Op::OpCopyObject,
415       spv::Op::OpTranspose,
416       spv::Op::OpConvertSToF,
417       spv::Op::OpConvertUToF,
418       // spv::Op::OpFConvert,
419       // spv::Op::OpQuantizeToF16,
420       spv::Op::OpFNegate,
421       spv::Op::OpFAdd,
422       spv::Op::OpFSub,
423       spv::Op::OpFMul,
424       spv::Op::OpFDiv,
425       spv::Op::OpFMod,
426       spv::Op::OpVectorTimesScalar,
427       spv::Op::OpMatrixTimesScalar,
428       spv::Op::OpVectorTimesMatrix,
429       spv::Op::OpMatrixTimesVector,
430       spv::Op::OpMatrixTimesMatrix,
431       spv::Op::OpOuterProduct,
432       spv::Op::OpDot,
433       spv::Op::OpSelect,
434       spv::Op::OpFOrdEqual,
435       spv::Op::OpFUnordEqual,
436       spv::Op::OpFOrdNotEqual,
437       spv::Op::OpFUnordNotEqual,
438       spv::Op::OpFOrdLessThan,
439       spv::Op::OpFUnordLessThan,
440       spv::Op::OpFOrdGreaterThan,
441       spv::Op::OpFUnordGreaterThan,
442       spv::Op::OpFOrdLessThanEqual,
443       spv::Op::OpFUnordLessThanEqual,
444       spv::Op::OpFOrdGreaterThanEqual,
445       spv::Op::OpFUnordGreaterThanEqual,
446   };
447   target_ops_450_ = {
448       GLSLstd450Round, GLSLstd450RoundEven, GLSLstd450Trunc, GLSLstd450FAbs,
449       GLSLstd450FSign, GLSLstd450Floor, GLSLstd450Ceil, GLSLstd450Fract,
450       GLSLstd450Radians, GLSLstd450Degrees, GLSLstd450Sin, GLSLstd450Cos,
451       GLSLstd450Tan, GLSLstd450Asin, GLSLstd450Acos, GLSLstd450Atan,
452       GLSLstd450Sinh, GLSLstd450Cosh, GLSLstd450Tanh, GLSLstd450Asinh,
453       GLSLstd450Acosh, GLSLstd450Atanh, GLSLstd450Atan2, GLSLstd450Pow,
454       GLSLstd450Exp, GLSLstd450Log, GLSLstd450Exp2, GLSLstd450Log2,
455       GLSLstd450Sqrt, GLSLstd450InverseSqrt, GLSLstd450Determinant,
456       GLSLstd450MatrixInverse,
457       // TODO(greg-lunarg): GLSLstd450ModfStruct,
458       GLSLstd450FMin, GLSLstd450FMax, GLSLstd450FClamp, GLSLstd450FMix,
459       GLSLstd450Step, GLSLstd450SmoothStep, GLSLstd450Fma,
460       // TODO(greg-lunarg): GLSLstd450FrexpStruct,
461       GLSLstd450Ldexp, GLSLstd450Length, GLSLstd450Distance, GLSLstd450Cross,
462       GLSLstd450Normalize, GLSLstd450FaceForward, GLSLstd450Reflect,
463       GLSLstd450Refract, GLSLstd450NMin, GLSLstd450NMax, GLSLstd450NClamp};
464   image_ops_ = {spv::Op::OpImageSampleImplicitLod,
465                 spv::Op::OpImageSampleExplicitLod,
466                 spv::Op::OpImageSampleDrefImplicitLod,
467                 spv::Op::OpImageSampleDrefExplicitLod,
468                 spv::Op::OpImageSampleProjImplicitLod,
469                 spv::Op::OpImageSampleProjExplicitLod,
470                 spv::Op::OpImageSampleProjDrefImplicitLod,
471                 spv::Op::OpImageSampleProjDrefExplicitLod,
472                 spv::Op::OpImageFetch,
473                 spv::Op::OpImageGather,
474                 spv::Op::OpImageDrefGather,
475                 spv::Op::OpImageRead,
476                 spv::Op::OpImageSparseSampleImplicitLod,
477                 spv::Op::OpImageSparseSampleExplicitLod,
478                 spv::Op::OpImageSparseSampleDrefImplicitLod,
479                 spv::Op::OpImageSparseSampleDrefExplicitLod,
480                 spv::Op::OpImageSparseSampleProjImplicitLod,
481                 spv::Op::OpImageSparseSampleProjExplicitLod,
482                 spv::Op::OpImageSparseSampleProjDrefImplicitLod,
483                 spv::Op::OpImageSparseSampleProjDrefExplicitLod,
484                 spv::Op::OpImageSparseFetch,
485                 spv::Op::OpImageSparseGather,
486                 spv::Op::OpImageSparseDrefGather,
487                 spv::Op::OpImageSparseTexelsResident,
488                 spv::Op::OpImageSparseRead};
489   dref_image_ops_ = {
490       spv::Op::OpImageSampleDrefImplicitLod,
491       spv::Op::OpImageSampleDrefExplicitLod,
492       spv::Op::OpImageSampleProjDrefImplicitLod,
493       spv::Op::OpImageSampleProjDrefExplicitLod,
494       spv::Op::OpImageDrefGather,
495       spv::Op::OpImageSparseSampleDrefImplicitLod,
496       spv::Op::OpImageSparseSampleDrefExplicitLod,
497       spv::Op::OpImageSparseSampleProjDrefImplicitLod,
498       spv::Op::OpImageSparseSampleProjDrefExplicitLod,
499       spv::Op::OpImageSparseDrefGather,
500   };
501   closure_ops_ = {
502       spv::Op::OpVectorExtractDynamic,
503       spv::Op::OpVectorInsertDynamic,
504       spv::Op::OpVectorShuffle,
505       spv::Op::OpCompositeConstruct,
506       spv::Op::OpCompositeInsert,
507       spv::Op::OpCompositeExtract,
508       spv::Op::OpCopyObject,
509       spv::Op::OpTranspose,
510       spv::Op::OpPhi,
511   };
512   relaxed_ids_set_.clear();
513   converted_ids_.clear();
514 }
515 
516 }  // namespace opt
517 }  // namespace spvtools
518