1 // Copyright (c) 2018 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 "upgrade_memory_model.h"
16
17 #include <utility>
18
19 #include "source/opt/ir_context.h"
20 #include "source/util/make_unique.h"
21
22 namespace spvtools {
23 namespace opt {
24
Process()25 Pass::Status UpgradeMemoryModel::Process() {
26 // Only update Logical GLSL450 to Logical VulkanKHR.
27 Instruction* memory_model = get_module()->GetMemoryModel();
28 if (memory_model->GetSingleWordInOperand(0u) != SpvAddressingModelLogical ||
29 memory_model->GetSingleWordInOperand(1u) != SpvMemoryModelGLSL450) {
30 return Pass::Status::SuccessWithoutChange;
31 }
32
33 UpgradeMemoryModelInstruction();
34 UpgradeInstructions();
35 CleanupDecorations();
36 UpgradeBarriers();
37 UpgradeMemoryScope();
38
39 return Pass::Status::SuccessWithChange;
40 }
41
UpgradeMemoryModelInstruction()42 void UpgradeMemoryModel::UpgradeMemoryModelInstruction() {
43 // Overall changes necessary:
44 // 1. Add the OpExtension.
45 // 2. Add the OpCapability.
46 // 3. Modify the memory model.
47 Instruction* memory_model = get_module()->GetMemoryModel();
48 get_module()->AddCapability(MakeUnique<Instruction>(
49 context(), SpvOpCapability, 0, 0,
50 std::initializer_list<Operand>{
51 {SPV_OPERAND_TYPE_CAPABILITY, {SpvCapabilityVulkanMemoryModelKHR}}}));
52 const std::string extension = "SPV_KHR_vulkan_memory_model";
53 std::vector<uint32_t> words(extension.size() / 4 + 1, 0);
54 char* dst = reinterpret_cast<char*>(words.data());
55 strncpy(dst, extension.c_str(), extension.size());
56 get_module()->AddExtension(
57 MakeUnique<Instruction>(context(), SpvOpExtension, 0, 0,
58 std::initializer_list<Operand>{
59 {SPV_OPERAND_TYPE_LITERAL_STRING, words}}));
60 memory_model->SetInOperand(1u, {SpvMemoryModelVulkanKHR});
61 }
62
UpgradeInstructions()63 void UpgradeMemoryModel::UpgradeInstructions() {
64 // Coherent and Volatile decorations are deprecated. Remove them and replace
65 // with flags on the memory/image operations. The decorations can occur on
66 // OpVariable, OpFunctionParameter (of pointer type) and OpStructType (member
67 // decoration). Trace from the decoration target(s) to the final memory/image
68 // instructions. Additionally, Workgroup storage class variables and function
69 // parameters are implicitly coherent in GLSL450.
70
71 for (auto& func : *get_module()) {
72 func.ForEachInst([this](Instruction* inst) {
73 bool is_coherent = false;
74 bool is_volatile = false;
75 bool src_coherent = false;
76 bool src_volatile = false;
77 bool dst_coherent = false;
78 bool dst_volatile = false;
79 SpvScope scope = SpvScopeQueueFamilyKHR;
80 SpvScope src_scope = SpvScopeQueueFamilyKHR;
81 SpvScope dst_scope = SpvScopeQueueFamilyKHR;
82 switch (inst->opcode()) {
83 case SpvOpLoad:
84 case SpvOpStore:
85 std::tie(is_coherent, is_volatile, scope) =
86 GetInstructionAttributes(inst->GetSingleWordInOperand(0u));
87 break;
88 case SpvOpImageRead:
89 case SpvOpImageSparseRead:
90 case SpvOpImageWrite:
91 std::tie(is_coherent, is_volatile, scope) =
92 GetInstructionAttributes(inst->GetSingleWordInOperand(0u));
93 break;
94 case SpvOpCopyMemory:
95 case SpvOpCopyMemorySized:
96 std::tie(dst_coherent, dst_volatile, dst_scope) =
97 GetInstructionAttributes(inst->GetSingleWordInOperand(0u));
98 std::tie(src_coherent, src_volatile, src_scope) =
99 GetInstructionAttributes(inst->GetSingleWordInOperand(1u));
100 break;
101 default:
102 break;
103 }
104
105 switch (inst->opcode()) {
106 case SpvOpLoad:
107 UpgradeFlags(inst, 1u, is_coherent, is_volatile, kAvailability,
108 kMemory);
109 break;
110 case SpvOpStore:
111 UpgradeFlags(inst, 2u, is_coherent, is_volatile, kVisibility,
112 kMemory);
113 break;
114 case SpvOpCopyMemory:
115 UpgradeFlags(inst, 2u, dst_coherent, dst_volatile, kAvailability,
116 kMemory);
117 UpgradeFlags(inst, 2u, src_coherent, src_volatile, kVisibility,
118 kMemory);
119 break;
120 case SpvOpCopyMemorySized:
121 UpgradeFlags(inst, 3u, dst_coherent, dst_volatile, kAvailability,
122 kMemory);
123 UpgradeFlags(inst, 3u, src_coherent, src_volatile, kVisibility,
124 kMemory);
125 break;
126 case SpvOpImageRead:
127 case SpvOpImageSparseRead:
128 UpgradeFlags(inst, 2u, is_coherent, is_volatile, kAvailability,
129 kImage);
130 break;
131 case SpvOpImageWrite:
132 UpgradeFlags(inst, 3u, is_coherent, is_volatile, kVisibility, kImage);
133 break;
134 default:
135 break;
136 }
137
138 // |is_coherent| is never used for the same instructions as
139 // |src_coherent| and |dst_coherent|.
140 if (is_coherent) {
141 inst->AddOperand(
142 {SPV_OPERAND_TYPE_SCOPE_ID, {GetScopeConstant(scope)}});
143 }
144 // According to SPV_KHR_vulkan_memory_model, if both available and
145 // visible flags are used the first scope operand is for availability
146 // (reads) and the second is for visibiity (writes).
147 if (src_coherent) {
148 inst->AddOperand(
149 {SPV_OPERAND_TYPE_SCOPE_ID, {GetScopeConstant(src_scope)}});
150 }
151 if (dst_coherent) {
152 inst->AddOperand(
153 {SPV_OPERAND_TYPE_SCOPE_ID, {GetScopeConstant(dst_scope)}});
154 }
155 });
156 }
157 }
158
GetInstructionAttributes(uint32_t id)159 std::tuple<bool, bool, SpvScope> UpgradeMemoryModel::GetInstructionAttributes(
160 uint32_t id) {
161 // |id| is a pointer used in a memory/image instruction. Need to determine if
162 // that pointer points to volatile or coherent memory. Workgroup storage
163 // class is implicitly coherent and cannot be decorated with volatile, so
164 // short circuit that case.
165 Instruction* inst = context()->get_def_use_mgr()->GetDef(id);
166 analysis::Type* type = context()->get_type_mgr()->GetType(inst->type_id());
167 if (type->AsPointer() &&
168 type->AsPointer()->storage_class() == SpvStorageClassWorkgroup) {
169 return std::make_tuple(true, false, SpvScopeWorkgroup);
170 }
171
172 bool is_coherent = false;
173 bool is_volatile = false;
174 std::unordered_set<uint32_t> visited;
175 std::tie(is_coherent, is_volatile) =
176 TraceInstruction(context()->get_def_use_mgr()->GetDef(id),
177 std::vector<uint32_t>(), &visited);
178
179 return std::make_tuple(is_coherent, is_volatile, SpvScopeQueueFamilyKHR);
180 }
181
TraceInstruction(Instruction * inst,std::vector<uint32_t> indices,std::unordered_set<uint32_t> * visited)182 std::pair<bool, bool> UpgradeMemoryModel::TraceInstruction(
183 Instruction* inst, std::vector<uint32_t> indices,
184 std::unordered_set<uint32_t>* visited) {
185 auto iter = cache_.find(std::make_pair(inst->result_id(), indices));
186 if (iter != cache_.end()) {
187 return iter->second;
188 }
189
190 if (!visited->insert(inst->result_id()).second) {
191 return std::make_pair(false, false);
192 }
193
194 // Initialize the cache before |indices| is (potentially) modified.
195 auto& cached_result = cache_[std::make_pair(inst->result_id(), indices)];
196 cached_result.first = false;
197 cached_result.second = false;
198
199 bool is_coherent = false;
200 bool is_volatile = false;
201 switch (inst->opcode()) {
202 case SpvOpVariable:
203 case SpvOpFunctionParameter:
204 is_coherent |= HasDecoration(inst, 0, SpvDecorationCoherent);
205 is_volatile |= HasDecoration(inst, 0, SpvDecorationVolatile);
206 if (!is_coherent || !is_volatile) {
207 bool type_coherent = false;
208 bool type_volatile = false;
209 std::tie(type_coherent, type_volatile) =
210 CheckType(inst->type_id(), indices);
211 is_coherent |= type_coherent;
212 is_volatile |= type_volatile;
213 }
214 break;
215 case SpvOpAccessChain:
216 case SpvOpInBoundsAccessChain:
217 // Store indices in reverse order.
218 for (uint32_t i = inst->NumInOperands() - 1; i > 0; --i) {
219 indices.push_back(inst->GetSingleWordInOperand(i));
220 }
221 break;
222 case SpvOpPtrAccessChain:
223 // Store indices in reverse order. Skip the |Element| operand.
224 for (uint32_t i = inst->NumInOperands() - 1; i > 1; --i) {
225 indices.push_back(inst->GetSingleWordInOperand(i));
226 }
227 break;
228 default:
229 break;
230 }
231
232 // No point searching further.
233 if (is_coherent && is_volatile) {
234 cached_result.first = true;
235 cached_result.second = true;
236 return std::make_pair(true, true);
237 }
238
239 // Variables and function parameters are sources. Continue searching until we
240 // reach them.
241 if (inst->opcode() != SpvOpVariable &&
242 inst->opcode() != SpvOpFunctionParameter) {
243 inst->ForEachInId([this, &is_coherent, &is_volatile, &indices,
244 &visited](const uint32_t* id_ptr) {
245 Instruction* op_inst = context()->get_def_use_mgr()->GetDef(*id_ptr);
246 const analysis::Type* type =
247 context()->get_type_mgr()->GetType(op_inst->type_id());
248 if (type &&
249 (type->AsPointer() || type->AsImage() || type->AsSampledImage())) {
250 bool operand_coherent = false;
251 bool operand_volatile = false;
252 std::tie(operand_coherent, operand_volatile) =
253 TraceInstruction(op_inst, indices, visited);
254 is_coherent |= operand_coherent;
255 is_volatile |= operand_volatile;
256 }
257 });
258 }
259
260 cached_result.first = is_coherent;
261 cached_result.second = is_volatile;
262 return std::make_pair(is_coherent, is_volatile);
263 }
264
CheckType(uint32_t type_id,const std::vector<uint32_t> & indices)265 std::pair<bool, bool> UpgradeMemoryModel::CheckType(
266 uint32_t type_id, const std::vector<uint32_t>& indices) {
267 bool is_coherent = false;
268 bool is_volatile = false;
269 Instruction* type_inst = context()->get_def_use_mgr()->GetDef(type_id);
270 assert(type_inst->opcode() == SpvOpTypePointer);
271 Instruction* element_inst = context()->get_def_use_mgr()->GetDef(
272 type_inst->GetSingleWordInOperand(1u));
273 for (int i = (int)indices.size() - 1; i >= 0; --i) {
274 if (is_coherent && is_volatile) break;
275
276 if (element_inst->opcode() == SpvOpTypePointer) {
277 element_inst = context()->get_def_use_mgr()->GetDef(
278 element_inst->GetSingleWordInOperand(1u));
279 } else if (element_inst->opcode() == SpvOpTypeStruct) {
280 uint32_t index = indices.at(i);
281 Instruction* index_inst = context()->get_def_use_mgr()->GetDef(index);
282 assert(index_inst->opcode() == SpvOpConstant);
283 uint64_t value = GetIndexValue(index_inst);
284 is_coherent |= HasDecoration(element_inst, static_cast<uint32_t>(value),
285 SpvDecorationCoherent);
286 is_volatile |= HasDecoration(element_inst, static_cast<uint32_t>(value),
287 SpvDecorationVolatile);
288 element_inst = context()->get_def_use_mgr()->GetDef(
289 element_inst->GetSingleWordInOperand(static_cast<uint32_t>(value)));
290 } else {
291 assert(spvOpcodeIsComposite(element_inst->opcode()));
292 element_inst = context()->get_def_use_mgr()->GetDef(
293 element_inst->GetSingleWordInOperand(1u));
294 }
295 }
296
297 if (!is_coherent || !is_volatile) {
298 bool remaining_coherent = false;
299 bool remaining_volatile = false;
300 std::tie(remaining_coherent, remaining_volatile) =
301 CheckAllTypes(element_inst);
302 is_coherent |= remaining_coherent;
303 is_volatile |= remaining_volatile;
304 }
305
306 return std::make_pair(is_coherent, is_volatile);
307 }
308
CheckAllTypes(const Instruction * inst)309 std::pair<bool, bool> UpgradeMemoryModel::CheckAllTypes(
310 const Instruction* inst) {
311 std::unordered_set<const Instruction*> visited;
312 std::vector<const Instruction*> stack;
313 stack.push_back(inst);
314
315 bool is_coherent = false;
316 bool is_volatile = false;
317 while (!stack.empty()) {
318 const Instruction* def = stack.back();
319 stack.pop_back();
320
321 if (!visited.insert(def).second) continue;
322
323 if (def->opcode() == SpvOpTypeStruct) {
324 // Any member decorated with coherent and/or volatile is enough to have
325 // the related operation be flagged as coherent and/or volatile.
326 is_coherent |= HasDecoration(def, std::numeric_limits<uint32_t>::max(),
327 SpvDecorationCoherent);
328 is_volatile |= HasDecoration(def, std::numeric_limits<uint32_t>::max(),
329 SpvDecorationVolatile);
330 if (is_coherent && is_volatile)
331 return std::make_pair(is_coherent, is_volatile);
332
333 // Check the subtypes.
334 for (uint32_t i = 0; i < def->NumInOperands(); ++i) {
335 stack.push_back(context()->get_def_use_mgr()->GetDef(
336 def->GetSingleWordInOperand(i)));
337 }
338 } else if (spvOpcodeIsComposite(def->opcode())) {
339 stack.push_back(context()->get_def_use_mgr()->GetDef(
340 def->GetSingleWordInOperand(0u)));
341 } else if (def->opcode() == SpvOpTypePointer) {
342 stack.push_back(context()->get_def_use_mgr()->GetDef(
343 def->GetSingleWordInOperand(1u)));
344 }
345 }
346
347 return std::make_pair(is_coherent, is_volatile);
348 }
349
GetIndexValue(Instruction * index_inst)350 uint64_t UpgradeMemoryModel::GetIndexValue(Instruction* index_inst) {
351 const analysis::Constant* index_constant =
352 context()->get_constant_mgr()->GetConstantFromInst(index_inst);
353 assert(index_constant->AsIntConstant());
354 if (index_constant->type()->AsInteger()->IsSigned()) {
355 if (index_constant->type()->AsInteger()->width() == 32) {
356 return index_constant->GetS32();
357 } else {
358 return index_constant->GetS64();
359 }
360 } else {
361 if (index_constant->type()->AsInteger()->width() == 32) {
362 return index_constant->GetU32();
363 } else {
364 return index_constant->GetU64();
365 }
366 }
367 }
368
HasDecoration(const Instruction * inst,uint32_t value,SpvDecoration decoration)369 bool UpgradeMemoryModel::HasDecoration(const Instruction* inst, uint32_t value,
370 SpvDecoration decoration) {
371 // If the iteration was terminated early then an appropriate decoration was
372 // found.
373 return !context()->get_decoration_mgr()->WhileEachDecoration(
374 inst->result_id(), decoration, [value](const Instruction& i) {
375 if (i.opcode() == SpvOpDecorate || i.opcode() == SpvOpDecorateId) {
376 return false;
377 } else if (i.opcode() == SpvOpMemberDecorate) {
378 if (value == i.GetSingleWordInOperand(1u) ||
379 value == std::numeric_limits<uint32_t>::max())
380 return false;
381 }
382
383 return true;
384 });
385 }
386
UpgradeFlags(Instruction * inst,uint32_t in_operand,bool is_coherent,bool is_volatile,OperationType operation_type,InstructionType inst_type)387 void UpgradeMemoryModel::UpgradeFlags(Instruction* inst, uint32_t in_operand,
388 bool is_coherent, bool is_volatile,
389 OperationType operation_type,
390 InstructionType inst_type) {
391 if (!is_coherent && !is_volatile) return;
392
393 uint32_t flags = 0;
394 if (inst->NumInOperands() > in_operand) {
395 flags |= inst->GetSingleWordInOperand(in_operand);
396 }
397 if (is_coherent) {
398 if (inst_type == kMemory) {
399 flags |= SpvMemoryAccessNonPrivatePointerKHRMask;
400 if (operation_type == kVisibility) {
401 flags |= SpvMemoryAccessMakePointerVisibleKHRMask;
402 } else {
403 flags |= SpvMemoryAccessMakePointerAvailableKHRMask;
404 }
405 } else {
406 flags |= SpvImageOperandsNonPrivateTexelKHRMask;
407 if (operation_type == kVisibility) {
408 flags |= SpvImageOperandsMakeTexelVisibleKHRMask;
409 } else {
410 flags |= SpvImageOperandsMakeTexelAvailableKHRMask;
411 }
412 }
413 }
414
415 if (is_volatile) {
416 if (inst_type == kMemory) {
417 flags |= SpvMemoryAccessVolatileMask;
418 } else {
419 flags |= SpvImageOperandsVolatileTexelKHRMask;
420 }
421 }
422
423 if (inst->NumInOperands() > in_operand) {
424 inst->SetInOperand(in_operand, {flags});
425 } else if (inst_type == kMemory) {
426 inst->AddOperand({SPV_OPERAND_TYPE_OPTIONAL_MEMORY_ACCESS, {flags}});
427 } else {
428 inst->AddOperand({SPV_OPERAND_TYPE_OPTIONAL_IMAGE, {flags}});
429 }
430 }
431
GetScopeConstant(SpvScope scope)432 uint32_t UpgradeMemoryModel::GetScopeConstant(SpvScope scope) {
433 analysis::Integer int_ty(32, false);
434 uint32_t int_id = context()->get_type_mgr()->GetTypeInstruction(&int_ty);
435 const analysis::Constant* constant =
436 context()->get_constant_mgr()->GetConstant(
437 context()->get_type_mgr()->GetType(int_id),
438 {static_cast<uint32_t>(scope)});
439 return context()
440 ->get_constant_mgr()
441 ->GetDefiningInstruction(constant)
442 ->result_id();
443 }
444
CleanupDecorations()445 void UpgradeMemoryModel::CleanupDecorations() {
446 // All of the volatile and coherent decorations have been dealt with, so now
447 // we can just remove them.
448 get_module()->ForEachInst([this](Instruction* inst) {
449 if (inst->result_id() != 0) {
450 context()->get_decoration_mgr()->RemoveDecorationsFrom(
451 inst->result_id(), [](const Instruction& dec) {
452 switch (dec.opcode()) {
453 case SpvOpDecorate:
454 case SpvOpDecorateId:
455 if (dec.GetSingleWordInOperand(1u) == SpvDecorationCoherent ||
456 dec.GetSingleWordInOperand(1u) == SpvDecorationVolatile)
457 return true;
458 break;
459 case SpvOpMemberDecorate:
460 if (dec.GetSingleWordInOperand(2u) == SpvDecorationCoherent ||
461 dec.GetSingleWordInOperand(2u) == SpvDecorationVolatile)
462 return true;
463 break;
464 default:
465 break;
466 }
467 return false;
468 });
469 }
470 });
471 }
472
UpgradeBarriers()473 void UpgradeMemoryModel::UpgradeBarriers() {
474 std::vector<Instruction*> barriers;
475 // Collects all the control barriers in |function|. Returns true if the
476 // function operates on the Output storage class.
477 ProcessFunction CollectBarriers = [this, &barriers](Function* function) {
478 bool operates_on_output = false;
479 for (auto& block : *function) {
480 block.ForEachInst([this, &barriers,
481 &operates_on_output](Instruction* inst) {
482 if (inst->opcode() == SpvOpControlBarrier) {
483 barriers.push_back(inst);
484 } else if (!operates_on_output) {
485 // This instruction operates on output storage class if it is a
486 // pointer to output type or any input operand is a pointer to output
487 // type.
488 analysis::Type* type =
489 context()->get_type_mgr()->GetType(inst->type_id());
490 if (type && type->AsPointer() &&
491 type->AsPointer()->storage_class() == SpvStorageClassOutput) {
492 operates_on_output = true;
493 return;
494 }
495 inst->ForEachInId([this, &operates_on_output](uint32_t* id_ptr) {
496 Instruction* op_inst =
497 context()->get_def_use_mgr()->GetDef(*id_ptr);
498 analysis::Type* op_type =
499 context()->get_type_mgr()->GetType(op_inst->type_id());
500 if (op_type && op_type->AsPointer() &&
501 op_type->AsPointer()->storage_class() == SpvStorageClassOutput)
502 operates_on_output = true;
503 });
504 }
505 });
506 }
507 return operates_on_output;
508 };
509
510 std::queue<uint32_t> roots;
511 for (auto& e : get_module()->entry_points())
512 if (e.GetSingleWordInOperand(0u) == SpvExecutionModelTessellationControl) {
513 roots.push(e.GetSingleWordInOperand(1u));
514 if (context()->ProcessCallTreeFromRoots(CollectBarriers, &roots)) {
515 for (auto barrier : barriers) {
516 // Add OutputMemoryKHR to the semantics of the barriers.
517 uint32_t semantics_id = barrier->GetSingleWordInOperand(2u);
518 Instruction* semantics_inst =
519 context()->get_def_use_mgr()->GetDef(semantics_id);
520 analysis::Type* semantics_type =
521 context()->get_type_mgr()->GetType(semantics_inst->type_id());
522 uint64_t semantics_value = GetIndexValue(semantics_inst);
523 const analysis::Constant* constant =
524 context()->get_constant_mgr()->GetConstant(
525 semantics_type, {static_cast<uint32_t>(semantics_value) |
526 SpvMemorySemanticsOutputMemoryKHRMask});
527 barrier->SetInOperand(2u, {context()
528 ->get_constant_mgr()
529 ->GetDefiningInstruction(constant)
530 ->result_id()});
531 }
532 }
533 barriers.clear();
534 }
535 }
536
UpgradeMemoryScope()537 void UpgradeMemoryModel::UpgradeMemoryScope() {
538 get_module()->ForEachInst([this](Instruction* inst) {
539 // Don't need to handle all the operations that take a scope.
540 // * Group operations can only be subgroup
541 // * Non-uniform can only be workgroup or subgroup
542 // * Named barriers are not supported by Vulkan
543 // * Workgroup ops (e.g. async_copy) have at most workgroup scope.
544 if (spvOpcodeIsAtomicOp(inst->opcode())) {
545 if (IsDeviceScope(inst->GetSingleWordInOperand(1))) {
546 inst->SetInOperand(1, {GetScopeConstant(SpvScopeQueueFamilyKHR)});
547 }
548 } else if (inst->opcode() == SpvOpControlBarrier) {
549 if (IsDeviceScope(inst->GetSingleWordInOperand(1))) {
550 inst->SetInOperand(1, {GetScopeConstant(SpvScopeQueueFamilyKHR)});
551 }
552 } else if (inst->opcode() == SpvOpMemoryBarrier) {
553 if (IsDeviceScope(inst->GetSingleWordInOperand(0))) {
554 inst->SetInOperand(0, {GetScopeConstant(SpvScopeQueueFamilyKHR)});
555 }
556 }
557 });
558 }
559
IsDeviceScope(uint32_t scope_id)560 bool UpgradeMemoryModel::IsDeviceScope(uint32_t scope_id) {
561 const analysis::Constant* constant =
562 context()->get_constant_mgr()->FindDeclaredConstant(scope_id);
563 assert(constant && "Memory scope must be a constant");
564
565 const analysis::Integer* type = constant->type()->AsInteger();
566 assert(type);
567 assert(type->width() == 32 || type->width() == 64);
568 if (type->width() == 32) {
569 if (type->IsSigned())
570 return static_cast<uint32_t>(constant->GetS32()) == SpvScopeDevice;
571 else
572 return static_cast<uint32_t>(constant->GetU32()) == SpvScopeDevice;
573 } else {
574 if (type->IsSigned())
575 return static_cast<uint32_t>(constant->GetS64()) == SpvScopeDevice;
576 else
577 return static_cast<uint32_t>(constant->GetU64()) == SpvScopeDevice;
578 }
579
580 assert(false);
581 return false;
582 }
583
584 } // namespace opt
585 } // namespace spvtools
586