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_builder.h"
20 #include "source/opt/ir_context.h"
21 #include "source/spirv_constant.h"
22 #include "source/util/make_unique.h"
23 #include "source/util/string_utils.h"
24
25 namespace spvtools {
26 namespace opt {
27
Process()28 Pass::Status UpgradeMemoryModel::Process() {
29 // TODO: This pass needs changes to support cooperative matrices.
30 if (context()->get_feature_mgr()->HasCapability(
31 SpvCapabilityCooperativeMatrixNV)) {
32 return Pass::Status::SuccessWithoutChange;
33 }
34
35 // Only update Logical GLSL450 to Logical VulkanKHR.
36 Instruction* memory_model = get_module()->GetMemoryModel();
37 if (memory_model->GetSingleWordInOperand(0u) != SpvAddressingModelLogical ||
38 memory_model->GetSingleWordInOperand(1u) != SpvMemoryModelGLSL450) {
39 return Pass::Status::SuccessWithoutChange;
40 }
41
42 UpgradeMemoryModelInstruction();
43 UpgradeInstructions();
44 CleanupDecorations();
45 UpgradeBarriers();
46 UpgradeMemoryScope();
47
48 return Pass::Status::SuccessWithChange;
49 }
50
UpgradeMemoryModelInstruction()51 void UpgradeMemoryModel::UpgradeMemoryModelInstruction() {
52 // Overall changes necessary:
53 // 1. Add the OpExtension.
54 // 2. Add the OpCapability.
55 // 3. Modify the memory model.
56 Instruction* memory_model = get_module()->GetMemoryModel();
57 context()->AddCapability(MakeUnique<Instruction>(
58 context(), SpvOpCapability, 0, 0,
59 std::initializer_list<Operand>{
60 {SPV_OPERAND_TYPE_CAPABILITY, {SpvCapabilityVulkanMemoryModelKHR}}}));
61 const std::string extension = "SPV_KHR_vulkan_memory_model";
62 std::vector<uint32_t> words = spvtools::utils::MakeVector(extension);
63 context()->AddExtension(
64 MakeUnique<Instruction>(context(), SpvOpExtension, 0, 0,
65 std::initializer_list<Operand>{
66 {SPV_OPERAND_TYPE_LITERAL_STRING, words}}));
67 memory_model->SetInOperand(1u, {SpvMemoryModelVulkanKHR});
68 }
69
UpgradeInstructions()70 void UpgradeMemoryModel::UpgradeInstructions() {
71 // Coherent and Volatile decorations are deprecated. Remove them and replace
72 // with flags on the memory/image operations. The decorations can occur on
73 // OpVariable, OpFunctionParameter (of pointer type) and OpStructType (member
74 // decoration). Trace from the decoration target(s) to the final memory/image
75 // instructions. Additionally, Workgroup storage class variables and function
76 // parameters are implicitly coherent in GLSL450.
77
78 // Upgrade modf and frexp first since they generate new stores.
79 // In SPIR-V 1.4 or later, normalize OpCopyMemory* access operands.
80 for (auto& func : *get_module()) {
81 func.ForEachInst([this](Instruction* inst) {
82 if (inst->opcode() == SpvOpExtInst) {
83 auto ext_inst = inst->GetSingleWordInOperand(1u);
84 if (ext_inst == GLSLstd450Modf || ext_inst == GLSLstd450Frexp) {
85 auto import =
86 get_def_use_mgr()->GetDef(inst->GetSingleWordInOperand(0u));
87 if (import->GetInOperand(0u).AsString() == "GLSL.std.450") {
88 UpgradeExtInst(inst);
89 }
90 }
91 } else if (get_module()->version() >= SPV_SPIRV_VERSION_WORD(1, 4)) {
92 if (inst->opcode() == SpvOpCopyMemory ||
93 inst->opcode() == SpvOpCopyMemorySized) {
94 uint32_t start_operand = inst->opcode() == SpvOpCopyMemory ? 2u : 3u;
95 if (inst->NumInOperands() > start_operand) {
96 auto num_access_words = MemoryAccessNumWords(
97 inst->GetSingleWordInOperand(start_operand));
98 if ((num_access_words + start_operand) == inst->NumInOperands()) {
99 // There is a single memory access operand. Duplicate it to have a
100 // separate operand for both source and target.
101 for (uint32_t i = 0; i < num_access_words; ++i) {
102 auto operand = inst->GetInOperand(start_operand + i);
103 inst->AddOperand(std::move(operand));
104 }
105 }
106 } else {
107 // Add two memory access operands.
108 inst->AddOperand(
109 {SPV_OPERAND_TYPE_MEMORY_ACCESS, {SpvMemoryAccessMaskNone}});
110 inst->AddOperand(
111 {SPV_OPERAND_TYPE_MEMORY_ACCESS, {SpvMemoryAccessMaskNone}});
112 }
113 }
114 }
115 });
116 }
117
118 UpgradeMemoryAndImages();
119 UpgradeAtomics();
120 }
121
UpgradeMemoryAndImages()122 void UpgradeMemoryModel::UpgradeMemoryAndImages() {
123 for (auto& func : *get_module()) {
124 func.ForEachInst([this](Instruction* inst) {
125 bool is_coherent = false;
126 bool is_volatile = false;
127 bool src_coherent = false;
128 bool src_volatile = false;
129 bool dst_coherent = false;
130 bool dst_volatile = false;
131 uint32_t start_operand = 0u;
132 SpvScope scope = SpvScopeQueueFamilyKHR;
133 SpvScope src_scope = SpvScopeQueueFamilyKHR;
134 SpvScope dst_scope = SpvScopeQueueFamilyKHR;
135 switch (inst->opcode()) {
136 case SpvOpLoad:
137 case SpvOpStore:
138 std::tie(is_coherent, is_volatile, scope) =
139 GetInstructionAttributes(inst->GetSingleWordInOperand(0u));
140 break;
141 case SpvOpImageRead:
142 case SpvOpImageSparseRead:
143 case SpvOpImageWrite:
144 std::tie(is_coherent, is_volatile, scope) =
145 GetInstructionAttributes(inst->GetSingleWordInOperand(0u));
146 break;
147 case SpvOpCopyMemory:
148 case SpvOpCopyMemorySized:
149 std::tie(dst_coherent, dst_volatile, dst_scope) =
150 GetInstructionAttributes(inst->GetSingleWordInOperand(0u));
151 std::tie(src_coherent, src_volatile, src_scope) =
152 GetInstructionAttributes(inst->GetSingleWordInOperand(1u));
153 break;
154 default:
155 break;
156 }
157
158 switch (inst->opcode()) {
159 case SpvOpLoad:
160 UpgradeFlags(inst, 1u, is_coherent, is_volatile, kVisibility,
161 kMemory);
162 break;
163 case SpvOpStore:
164 UpgradeFlags(inst, 2u, is_coherent, is_volatile, kAvailability,
165 kMemory);
166 break;
167 case SpvOpCopyMemory:
168 case SpvOpCopyMemorySized:
169 start_operand = inst->opcode() == SpvOpCopyMemory ? 2u : 3u;
170 if (get_module()->version() >= SPV_SPIRV_VERSION_WORD(1, 4)) {
171 // There are guaranteed to be two memory access operands at this
172 // point so treat source and target separately.
173 uint32_t num_access_words = MemoryAccessNumWords(
174 inst->GetSingleWordInOperand(start_operand));
175 UpgradeFlags(inst, start_operand, dst_coherent, dst_volatile,
176 kAvailability, kMemory);
177 UpgradeFlags(inst, start_operand + num_access_words, src_coherent,
178 src_volatile, kVisibility, kMemory);
179 } else {
180 UpgradeFlags(inst, start_operand, dst_coherent, dst_volatile,
181 kAvailability, kMemory);
182 UpgradeFlags(inst, start_operand, src_coherent, src_volatile,
183 kVisibility, kMemory);
184 }
185 break;
186 case SpvOpImageRead:
187 case SpvOpImageSparseRead:
188 UpgradeFlags(inst, 2u, is_coherent, is_volatile, kVisibility, kImage);
189 break;
190 case SpvOpImageWrite:
191 UpgradeFlags(inst, 3u, is_coherent, is_volatile, kAvailability,
192 kImage);
193 break;
194 default:
195 break;
196 }
197
198 // |is_coherent| is never used for the same instructions as
199 // |src_coherent| and |dst_coherent|.
200 if (is_coherent) {
201 inst->AddOperand(
202 {SPV_OPERAND_TYPE_SCOPE_ID, {GetScopeConstant(scope)}});
203 }
204 if (get_module()->version() >= SPV_SPIRV_VERSION_WORD(1, 4)) {
205 // There are two memory access operands. The first is for the target and
206 // the second is for the source.
207 if (dst_coherent || src_coherent) {
208 start_operand = inst->opcode() == SpvOpCopyMemory ? 2u : 3u;
209 std::vector<Operand> new_operands;
210 uint32_t num_access_words =
211 MemoryAccessNumWords(inst->GetSingleWordInOperand(start_operand));
212 // The flags were already updated so subtract if we're adding a
213 // scope.
214 if (dst_coherent) --num_access_words;
215 for (uint32_t i = 0; i < start_operand + num_access_words; ++i) {
216 new_operands.push_back(inst->GetInOperand(i));
217 }
218 // Add the target scope if necessary.
219 if (dst_coherent) {
220 new_operands.push_back(
221 {SPV_OPERAND_TYPE_SCOPE_ID, {GetScopeConstant(dst_scope)}});
222 }
223 // Copy the remaining current operands.
224 for (uint32_t i = start_operand + num_access_words;
225 i < inst->NumInOperands(); ++i) {
226 new_operands.push_back(inst->GetInOperand(i));
227 }
228 // Add the source scope if necessary.
229 if (src_coherent) {
230 new_operands.push_back(
231 {SPV_OPERAND_TYPE_SCOPE_ID, {GetScopeConstant(src_scope)}});
232 }
233 inst->SetInOperands(std::move(new_operands));
234 }
235 } else {
236 // According to SPV_KHR_vulkan_memory_model, if both available and
237 // visible flags are used the first scope operand is for availability
238 // (writes) and the second is for visibility (reads).
239 if (dst_coherent) {
240 inst->AddOperand(
241 {SPV_OPERAND_TYPE_SCOPE_ID, {GetScopeConstant(dst_scope)}});
242 }
243 if (src_coherent) {
244 inst->AddOperand(
245 {SPV_OPERAND_TYPE_SCOPE_ID, {GetScopeConstant(src_scope)}});
246 }
247 }
248 });
249 }
250 }
251
UpgradeAtomics()252 void UpgradeMemoryModel::UpgradeAtomics() {
253 for (auto& func : *get_module()) {
254 func.ForEachInst([this](Instruction* inst) {
255 if (spvOpcodeIsAtomicOp(inst->opcode())) {
256 bool unused_coherent = false;
257 bool is_volatile = false;
258 SpvScope unused_scope = SpvScopeQueueFamilyKHR;
259 std::tie(unused_coherent, is_volatile, unused_scope) =
260 GetInstructionAttributes(inst->GetSingleWordInOperand(0));
261
262 UpgradeSemantics(inst, 2u, is_volatile);
263 if (inst->opcode() == SpvOpAtomicCompareExchange ||
264 inst->opcode() == SpvOpAtomicCompareExchangeWeak) {
265 UpgradeSemantics(inst, 3u, is_volatile);
266 }
267 }
268 });
269 }
270 }
271
UpgradeSemantics(Instruction * inst,uint32_t in_operand,bool is_volatile)272 void UpgradeMemoryModel::UpgradeSemantics(Instruction* inst,
273 uint32_t in_operand,
274 bool is_volatile) {
275 if (!is_volatile) return;
276
277 uint32_t semantics_id = inst->GetSingleWordInOperand(in_operand);
278 const analysis::Constant* constant =
279 context()->get_constant_mgr()->FindDeclaredConstant(semantics_id);
280 const analysis::Integer* type = constant->type()->AsInteger();
281 assert(type && type->width() == 32);
282 uint32_t value = 0;
283 if (type->IsSigned()) {
284 value = static_cast<uint32_t>(constant->GetS32());
285 } else {
286 value = constant->GetU32();
287 }
288
289 value |= SpvMemorySemanticsVolatileMask;
290 auto new_constant = context()->get_constant_mgr()->GetConstant(type, {value});
291 auto new_semantics =
292 context()->get_constant_mgr()->GetDefiningInstruction(new_constant);
293 inst->SetInOperand(in_operand, {new_semantics->result_id()});
294 }
295
GetInstructionAttributes(uint32_t id)296 std::tuple<bool, bool, SpvScope> UpgradeMemoryModel::GetInstructionAttributes(
297 uint32_t id) {
298 // |id| is a pointer used in a memory/image instruction. Need to determine if
299 // that pointer points to volatile or coherent memory. Workgroup storage
300 // class is implicitly coherent and cannot be decorated with volatile, so
301 // short circuit that case.
302 Instruction* inst = context()->get_def_use_mgr()->GetDef(id);
303 analysis::Type* type = context()->get_type_mgr()->GetType(inst->type_id());
304 if (type->AsPointer() &&
305 type->AsPointer()->storage_class() == SpvStorageClassWorkgroup) {
306 return std::make_tuple(true, false, SpvScopeWorkgroup);
307 }
308
309 bool is_coherent = false;
310 bool is_volatile = false;
311 std::unordered_set<uint32_t> visited;
312 std::tie(is_coherent, is_volatile) =
313 TraceInstruction(context()->get_def_use_mgr()->GetDef(id),
314 std::vector<uint32_t>(), &visited);
315
316 return std::make_tuple(is_coherent, is_volatile, SpvScopeQueueFamilyKHR);
317 }
318
TraceInstruction(Instruction * inst,std::vector<uint32_t> indices,std::unordered_set<uint32_t> * visited)319 std::pair<bool, bool> UpgradeMemoryModel::TraceInstruction(
320 Instruction* inst, std::vector<uint32_t> indices,
321 std::unordered_set<uint32_t>* visited) {
322 auto iter = cache_.find(std::make_pair(inst->result_id(), indices));
323 if (iter != cache_.end()) {
324 return iter->second;
325 }
326
327 if (!visited->insert(inst->result_id()).second) {
328 return std::make_pair(false, false);
329 }
330
331 // Initialize the cache before |indices| is (potentially) modified.
332 auto& cached_result = cache_[std::make_pair(inst->result_id(), indices)];
333 cached_result.first = false;
334 cached_result.second = false;
335
336 bool is_coherent = false;
337 bool is_volatile = false;
338 switch (inst->opcode()) {
339 case SpvOpVariable:
340 case SpvOpFunctionParameter:
341 is_coherent |= HasDecoration(inst, 0, SpvDecorationCoherent);
342 is_volatile |= HasDecoration(inst, 0, SpvDecorationVolatile);
343 if (!is_coherent || !is_volatile) {
344 bool type_coherent = false;
345 bool type_volatile = false;
346 std::tie(type_coherent, type_volatile) =
347 CheckType(inst->type_id(), indices);
348 is_coherent |= type_coherent;
349 is_volatile |= type_volatile;
350 }
351 break;
352 case SpvOpAccessChain:
353 case SpvOpInBoundsAccessChain:
354 // Store indices in reverse order.
355 for (uint32_t i = inst->NumInOperands() - 1; i > 0; --i) {
356 indices.push_back(inst->GetSingleWordInOperand(i));
357 }
358 break;
359 case SpvOpPtrAccessChain:
360 // Store indices in reverse order. Skip the |Element| operand.
361 for (uint32_t i = inst->NumInOperands() - 1; i > 1; --i) {
362 indices.push_back(inst->GetSingleWordInOperand(i));
363 }
364 break;
365 default:
366 break;
367 }
368
369 // No point searching further.
370 if (is_coherent && is_volatile) {
371 cached_result.first = true;
372 cached_result.second = true;
373 return std::make_pair(true, true);
374 }
375
376 // Variables and function parameters are sources. Continue searching until we
377 // reach them.
378 if (inst->opcode() != SpvOpVariable &&
379 inst->opcode() != SpvOpFunctionParameter) {
380 inst->ForEachInId([this, &is_coherent, &is_volatile, &indices,
381 &visited](const uint32_t* id_ptr) {
382 Instruction* op_inst = context()->get_def_use_mgr()->GetDef(*id_ptr);
383 const analysis::Type* type =
384 context()->get_type_mgr()->GetType(op_inst->type_id());
385 if (type &&
386 (type->AsPointer() || type->AsImage() || type->AsSampledImage())) {
387 bool operand_coherent = false;
388 bool operand_volatile = false;
389 std::tie(operand_coherent, operand_volatile) =
390 TraceInstruction(op_inst, indices, visited);
391 is_coherent |= operand_coherent;
392 is_volatile |= operand_volatile;
393 }
394 });
395 }
396
397 cached_result.first = is_coherent;
398 cached_result.second = is_volatile;
399 return std::make_pair(is_coherent, is_volatile);
400 }
401
CheckType(uint32_t type_id,const std::vector<uint32_t> & indices)402 std::pair<bool, bool> UpgradeMemoryModel::CheckType(
403 uint32_t type_id, const std::vector<uint32_t>& indices) {
404 bool is_coherent = false;
405 bool is_volatile = false;
406 Instruction* type_inst = context()->get_def_use_mgr()->GetDef(type_id);
407 assert(type_inst->opcode() == SpvOpTypePointer);
408 Instruction* element_inst = context()->get_def_use_mgr()->GetDef(
409 type_inst->GetSingleWordInOperand(1u));
410 for (int i = (int)indices.size() - 1; i >= 0; --i) {
411 if (is_coherent && is_volatile) break;
412
413 if (element_inst->opcode() == SpvOpTypePointer) {
414 element_inst = context()->get_def_use_mgr()->GetDef(
415 element_inst->GetSingleWordInOperand(1u));
416 } else if (element_inst->opcode() == SpvOpTypeStruct) {
417 uint32_t index = indices.at(i);
418 Instruction* index_inst = context()->get_def_use_mgr()->GetDef(index);
419 assert(index_inst->opcode() == SpvOpConstant);
420 uint64_t value = GetIndexValue(index_inst);
421 is_coherent |= HasDecoration(element_inst, static_cast<uint32_t>(value),
422 SpvDecorationCoherent);
423 is_volatile |= HasDecoration(element_inst, static_cast<uint32_t>(value),
424 SpvDecorationVolatile);
425 element_inst = context()->get_def_use_mgr()->GetDef(
426 element_inst->GetSingleWordInOperand(static_cast<uint32_t>(value)));
427 } else {
428 assert(spvOpcodeIsComposite(element_inst->opcode()));
429 element_inst = context()->get_def_use_mgr()->GetDef(
430 element_inst->GetSingleWordInOperand(0u));
431 }
432 }
433
434 if (!is_coherent || !is_volatile) {
435 bool remaining_coherent = false;
436 bool remaining_volatile = false;
437 std::tie(remaining_coherent, remaining_volatile) =
438 CheckAllTypes(element_inst);
439 is_coherent |= remaining_coherent;
440 is_volatile |= remaining_volatile;
441 }
442
443 return std::make_pair(is_coherent, is_volatile);
444 }
445
CheckAllTypes(const Instruction * inst)446 std::pair<bool, bool> UpgradeMemoryModel::CheckAllTypes(
447 const Instruction* inst) {
448 std::unordered_set<const Instruction*> visited;
449 std::vector<const Instruction*> stack;
450 stack.push_back(inst);
451
452 bool is_coherent = false;
453 bool is_volatile = false;
454 while (!stack.empty()) {
455 const Instruction* def = stack.back();
456 stack.pop_back();
457
458 if (!visited.insert(def).second) continue;
459
460 if (def->opcode() == SpvOpTypeStruct) {
461 // Any member decorated with coherent and/or volatile is enough to have
462 // the related operation be flagged as coherent and/or volatile.
463 is_coherent |= HasDecoration(def, std::numeric_limits<uint32_t>::max(),
464 SpvDecorationCoherent);
465 is_volatile |= HasDecoration(def, std::numeric_limits<uint32_t>::max(),
466 SpvDecorationVolatile);
467 if (is_coherent && is_volatile)
468 return std::make_pair(is_coherent, is_volatile);
469
470 // Check the subtypes.
471 for (uint32_t i = 0; i < def->NumInOperands(); ++i) {
472 stack.push_back(context()->get_def_use_mgr()->GetDef(
473 def->GetSingleWordInOperand(i)));
474 }
475 } else if (spvOpcodeIsComposite(def->opcode())) {
476 stack.push_back(context()->get_def_use_mgr()->GetDef(
477 def->GetSingleWordInOperand(0u)));
478 } else if (def->opcode() == SpvOpTypePointer) {
479 stack.push_back(context()->get_def_use_mgr()->GetDef(
480 def->GetSingleWordInOperand(1u)));
481 }
482 }
483
484 return std::make_pair(is_coherent, is_volatile);
485 }
486
GetIndexValue(Instruction * index_inst)487 uint64_t UpgradeMemoryModel::GetIndexValue(Instruction* index_inst) {
488 const analysis::Constant* index_constant =
489 context()->get_constant_mgr()->GetConstantFromInst(index_inst);
490 assert(index_constant->AsIntConstant());
491 if (index_constant->type()->AsInteger()->IsSigned()) {
492 if (index_constant->type()->AsInteger()->width() == 32) {
493 return index_constant->GetS32();
494 } else {
495 return index_constant->GetS64();
496 }
497 } else {
498 if (index_constant->type()->AsInteger()->width() == 32) {
499 return index_constant->GetU32();
500 } else {
501 return index_constant->GetU64();
502 }
503 }
504 }
505
HasDecoration(const Instruction * inst,uint32_t value,SpvDecoration decoration)506 bool UpgradeMemoryModel::HasDecoration(const Instruction* inst, uint32_t value,
507 SpvDecoration decoration) {
508 // If the iteration was terminated early then an appropriate decoration was
509 // found.
510 return !context()->get_decoration_mgr()->WhileEachDecoration(
511 inst->result_id(), decoration, [value](const Instruction& i) {
512 if (i.opcode() == SpvOpDecorate || i.opcode() == SpvOpDecorateId) {
513 return false;
514 } else if (i.opcode() == SpvOpMemberDecorate) {
515 if (value == i.GetSingleWordInOperand(1u) ||
516 value == std::numeric_limits<uint32_t>::max())
517 return false;
518 }
519
520 return true;
521 });
522 }
523
UpgradeFlags(Instruction * inst,uint32_t in_operand,bool is_coherent,bool is_volatile,OperationType operation_type,InstructionType inst_type)524 void UpgradeMemoryModel::UpgradeFlags(Instruction* inst, uint32_t in_operand,
525 bool is_coherent, bool is_volatile,
526 OperationType operation_type,
527 InstructionType inst_type) {
528 if (!is_coherent && !is_volatile) return;
529
530 uint32_t flags = 0;
531 if (inst->NumInOperands() > in_operand) {
532 flags |= inst->GetSingleWordInOperand(in_operand);
533 }
534 if (is_coherent) {
535 if (inst_type == kMemory) {
536 flags |= SpvMemoryAccessNonPrivatePointerKHRMask;
537 if (operation_type == kVisibility) {
538 flags |= SpvMemoryAccessMakePointerVisibleKHRMask;
539 } else {
540 flags |= SpvMemoryAccessMakePointerAvailableKHRMask;
541 }
542 } else {
543 flags |= SpvImageOperandsNonPrivateTexelKHRMask;
544 if (operation_type == kVisibility) {
545 flags |= SpvImageOperandsMakeTexelVisibleKHRMask;
546 } else {
547 flags |= SpvImageOperandsMakeTexelAvailableKHRMask;
548 }
549 }
550 }
551
552 if (is_volatile) {
553 if (inst_type == kMemory) {
554 flags |= SpvMemoryAccessVolatileMask;
555 } else {
556 flags |= SpvImageOperandsVolatileTexelKHRMask;
557 }
558 }
559
560 if (inst->NumInOperands() > in_operand) {
561 inst->SetInOperand(in_operand, {flags});
562 } else if (inst_type == kMemory) {
563 inst->AddOperand({SPV_OPERAND_TYPE_OPTIONAL_MEMORY_ACCESS, {flags}});
564 } else {
565 inst->AddOperand({SPV_OPERAND_TYPE_OPTIONAL_IMAGE, {flags}});
566 }
567 }
568
GetScopeConstant(SpvScope scope)569 uint32_t UpgradeMemoryModel::GetScopeConstant(SpvScope scope) {
570 analysis::Integer int_ty(32, false);
571 uint32_t int_id = context()->get_type_mgr()->GetTypeInstruction(&int_ty);
572 const analysis::Constant* constant =
573 context()->get_constant_mgr()->GetConstant(
574 context()->get_type_mgr()->GetType(int_id),
575 {static_cast<uint32_t>(scope)});
576 return context()
577 ->get_constant_mgr()
578 ->GetDefiningInstruction(constant)
579 ->result_id();
580 }
581
CleanupDecorations()582 void UpgradeMemoryModel::CleanupDecorations() {
583 // All of the volatile and coherent decorations have been dealt with, so now
584 // we can just remove them.
585 get_module()->ForEachInst([this](Instruction* inst) {
586 if (inst->result_id() != 0) {
587 context()->get_decoration_mgr()->RemoveDecorationsFrom(
588 inst->result_id(), [](const Instruction& dec) {
589 switch (dec.opcode()) {
590 case SpvOpDecorate:
591 case SpvOpDecorateId:
592 if (dec.GetSingleWordInOperand(1u) == SpvDecorationCoherent ||
593 dec.GetSingleWordInOperand(1u) == SpvDecorationVolatile)
594 return true;
595 break;
596 case SpvOpMemberDecorate:
597 if (dec.GetSingleWordInOperand(2u) == SpvDecorationCoherent ||
598 dec.GetSingleWordInOperand(2u) == SpvDecorationVolatile)
599 return true;
600 break;
601 default:
602 break;
603 }
604 return false;
605 });
606 }
607 });
608 }
609
UpgradeBarriers()610 void UpgradeMemoryModel::UpgradeBarriers() {
611 std::vector<Instruction*> barriers;
612 // Collects all the control barriers in |function|. Returns true if the
613 // function operates on the Output storage class.
614 ProcessFunction CollectBarriers = [this, &barriers](Function* function) {
615 bool operates_on_output = false;
616 for (auto& block : *function) {
617 block.ForEachInst([this, &barriers,
618 &operates_on_output](Instruction* inst) {
619 if (inst->opcode() == SpvOpControlBarrier) {
620 barriers.push_back(inst);
621 } else if (!operates_on_output) {
622 // This instruction operates on output storage class if it is a
623 // pointer to output type or any input operand is a pointer to output
624 // type.
625 analysis::Type* type =
626 context()->get_type_mgr()->GetType(inst->type_id());
627 if (type && type->AsPointer() &&
628 type->AsPointer()->storage_class() == SpvStorageClassOutput) {
629 operates_on_output = true;
630 return;
631 }
632 inst->ForEachInId([this, &operates_on_output](uint32_t* id_ptr) {
633 Instruction* op_inst =
634 context()->get_def_use_mgr()->GetDef(*id_ptr);
635 analysis::Type* op_type =
636 context()->get_type_mgr()->GetType(op_inst->type_id());
637 if (op_type && op_type->AsPointer() &&
638 op_type->AsPointer()->storage_class() == SpvStorageClassOutput)
639 operates_on_output = true;
640 });
641 }
642 });
643 }
644 return operates_on_output;
645 };
646
647 std::queue<uint32_t> roots;
648 for (auto& e : get_module()->entry_points())
649 if (e.GetSingleWordInOperand(0u) == SpvExecutionModelTessellationControl) {
650 roots.push(e.GetSingleWordInOperand(1u));
651 if (context()->ProcessCallTreeFromRoots(CollectBarriers, &roots)) {
652 for (auto barrier : barriers) {
653 // Add OutputMemoryKHR to the semantics of the barriers.
654 uint32_t semantics_id = barrier->GetSingleWordInOperand(2u);
655 Instruction* semantics_inst =
656 context()->get_def_use_mgr()->GetDef(semantics_id);
657 analysis::Type* semantics_type =
658 context()->get_type_mgr()->GetType(semantics_inst->type_id());
659 uint64_t semantics_value = GetIndexValue(semantics_inst);
660 const analysis::Constant* constant =
661 context()->get_constant_mgr()->GetConstant(
662 semantics_type, {static_cast<uint32_t>(semantics_value) |
663 SpvMemorySemanticsOutputMemoryKHRMask});
664 barrier->SetInOperand(2u, {context()
665 ->get_constant_mgr()
666 ->GetDefiningInstruction(constant)
667 ->result_id()});
668 }
669 }
670 barriers.clear();
671 }
672 }
673
UpgradeMemoryScope()674 void UpgradeMemoryModel::UpgradeMemoryScope() {
675 get_module()->ForEachInst([this](Instruction* inst) {
676 // Don't need to handle all the operations that take a scope.
677 // * Group operations can only be subgroup
678 // * Non-uniform can only be workgroup or subgroup
679 // * Named barriers are not supported by Vulkan
680 // * Workgroup ops (e.g. async_copy) have at most workgroup scope.
681 if (spvOpcodeIsAtomicOp(inst->opcode())) {
682 if (IsDeviceScope(inst->GetSingleWordInOperand(1))) {
683 inst->SetInOperand(1, {GetScopeConstant(SpvScopeQueueFamilyKHR)});
684 }
685 } else if (inst->opcode() == SpvOpControlBarrier) {
686 if (IsDeviceScope(inst->GetSingleWordInOperand(1))) {
687 inst->SetInOperand(1, {GetScopeConstant(SpvScopeQueueFamilyKHR)});
688 }
689 } else if (inst->opcode() == SpvOpMemoryBarrier) {
690 if (IsDeviceScope(inst->GetSingleWordInOperand(0))) {
691 inst->SetInOperand(0, {GetScopeConstant(SpvScopeQueueFamilyKHR)});
692 }
693 }
694 });
695 }
696
IsDeviceScope(uint32_t scope_id)697 bool UpgradeMemoryModel::IsDeviceScope(uint32_t scope_id) {
698 const analysis::Constant* constant =
699 context()->get_constant_mgr()->FindDeclaredConstant(scope_id);
700 assert(constant && "Memory scope must be a constant");
701
702 const analysis::Integer* type = constant->type()->AsInteger();
703 assert(type);
704 assert(type->width() == 32 || type->width() == 64);
705 if (type->width() == 32) {
706 if (type->IsSigned())
707 return static_cast<uint32_t>(constant->GetS32()) == SpvScopeDevice;
708 else
709 return static_cast<uint32_t>(constant->GetU32()) == SpvScopeDevice;
710 } else {
711 if (type->IsSigned())
712 return static_cast<uint32_t>(constant->GetS64()) == SpvScopeDevice;
713 else
714 return static_cast<uint32_t>(constant->GetU64()) == SpvScopeDevice;
715 }
716
717 assert(false);
718 return false;
719 }
720
UpgradeExtInst(Instruction * ext_inst)721 void UpgradeMemoryModel::UpgradeExtInst(Instruction* ext_inst) {
722 const bool is_modf = ext_inst->GetSingleWordInOperand(1u) == GLSLstd450Modf;
723 auto ptr_id = ext_inst->GetSingleWordInOperand(3u);
724 auto ptr_type_id = get_def_use_mgr()->GetDef(ptr_id)->type_id();
725 auto pointee_type_id =
726 get_def_use_mgr()->GetDef(ptr_type_id)->GetSingleWordInOperand(1u);
727 auto element_type_id = ext_inst->type_id();
728 std::vector<const analysis::Type*> element_types(2);
729 element_types[0] = context()->get_type_mgr()->GetType(element_type_id);
730 element_types[1] = context()->get_type_mgr()->GetType(pointee_type_id);
731 analysis::Struct struct_type(element_types);
732 uint32_t struct_id =
733 context()->get_type_mgr()->GetTypeInstruction(&struct_type);
734 // Change the operation
735 GLSLstd450 new_op = is_modf ? GLSLstd450ModfStruct : GLSLstd450FrexpStruct;
736 ext_inst->SetOperand(3u, {static_cast<uint32_t>(new_op)});
737 // Remove the pointer argument
738 ext_inst->RemoveOperand(5u);
739 // Set the type id to the new struct.
740 ext_inst->SetResultType(struct_id);
741
742 // The result is now a struct of the original result. The zero'th element is
743 // old result and should replace the old result. The one'th element needs to
744 // be stored via a new instruction.
745 auto where = ext_inst->NextNode();
746 InstructionBuilder builder(
747 context(), where,
748 IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
749 auto extract_0 =
750 builder.AddCompositeExtract(element_type_id, ext_inst->result_id(), {0});
751 context()->ReplaceAllUsesWith(ext_inst->result_id(), extract_0->result_id());
752 // The extract's input was just changed to itself, so fix that.
753 extract_0->SetInOperand(0u, {ext_inst->result_id()});
754 auto extract_1 =
755 builder.AddCompositeExtract(pointee_type_id, ext_inst->result_id(), {1});
756 builder.AddStore(ptr_id, extract_1->result_id());
757 }
758
MemoryAccessNumWords(uint32_t mask)759 uint32_t UpgradeMemoryModel::MemoryAccessNumWords(uint32_t mask) {
760 uint32_t result = 1;
761 if (mask & SpvMemoryAccessAlignedMask) ++result;
762 if (mask & SpvMemoryAccessMakePointerAvailableKHRMask) ++result;
763 if (mask & SpvMemoryAccessMakePointerVisibleKHRMask) ++result;
764 return result;
765 }
766
767 } // namespace opt
768 } // namespace spvtools
769