Coverage Report

Created: 2025-12-31 06:15

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