Coverage Report

Created: 2025-11-16 06:14

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/spirv-tools/source/val/validate_scopes.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 "source/val/validate_scopes.h"
16
17
#include "source/spirv_target_env.h"
18
#include "source/val/instruction.h"
19
#include "source/val/validation_state.h"
20
21
namespace spvtools {
22
namespace val {
23
24
3.34k
bool IsValidScope(uint32_t scope) {
25
  // Deliberately avoid a default case so we have to update the list when the
26
  // scopes list changes.
27
3.34k
  switch (static_cast<spv::Scope>(scope)) {
28
175
    case spv::Scope::CrossDevice:
29
1.20k
    case spv::Scope::Device:
30
2.02k
    case spv::Scope::Workgroup:
31
3.20k
    case spv::Scope::Subgroup:
32
3.27k
    case spv::Scope::Invocation:
33
3.27k
    case spv::Scope::QueueFamilyKHR:
34
3.28k
    case spv::Scope::ShaderCallKHR:
35
3.28k
      return true;
36
0
    case spv::Scope::Max:
37
0
      break;
38
3.34k
  }
39
61
  return false;
40
3.34k
}
41
42
spv_result_t ValidateScope(ValidationState_t& _, const Instruction* inst,
43
3.65k
                           uint32_t scope) {
44
3.65k
  spv::Op opcode = inst->opcode();
45
3.65k
  bool is_int32 = false, is_const_int32 = false;
46
3.65k
  uint32_t value = 0;
47
3.65k
  std::tie(is_int32, is_const_int32, value) = _.EvalInt32IfConst(scope);
48
49
3.65k
  if (!is_int32) {
50
8
    return _.diag(SPV_ERROR_INVALID_DATA, inst)
51
8
           << spvOpcodeString(opcode) << ": expected scope to be a 32-bit int";
52
8
  }
53
54
3.65k
  if (!is_const_int32) {
55
308
    if (_.HasCapability(spv::Capability::Shader) &&
56
8
        !_.HasCapability(spv::Capability::CooperativeMatrixNV)) {
57
8
      return _.diag(SPV_ERROR_INVALID_DATA, inst)
58
8
             << "Scope ids must be OpConstant when Shader capability is "
59
8
             << "present";
60
8
    }
61
300
    if (_.HasCapability(spv::Capability::Shader) &&
62
0
        _.HasCapability(spv::Capability::CooperativeMatrixNV) &&
63
0
        !spvOpcodeIsConstant(_.GetIdOpcode(scope))) {
64
0
      return _.diag(SPV_ERROR_INVALID_DATA, inst)
65
0
             << "Scope ids must be constant or specialization constant when "
66
0
             << "CooperativeMatrixNV capability is present";
67
0
    }
68
300
  }
69
70
3.64k
  if (is_const_int32 && !IsValidScope(value)) {
71
61
    return _.diag(SPV_ERROR_INVALID_DATA, inst)
72
61
           << "Invalid scope value:\n " << _.Disassemble(*_.FindDef(scope));
73
61
  }
74
75
3.58k
  return SPV_SUCCESS;
76
3.64k
}
77
78
spv_result_t ValidateExecutionScope(ValidationState_t& _,
79
1.70k
                                    const Instruction* inst, uint32_t scope) {
80
1.70k
  spv::Op opcode = inst->opcode();
81
1.70k
  bool is_int32 = false, is_const_int32 = false;
82
1.70k
  uint32_t tmp_value = 0;
83
1.70k
  std::tie(is_int32, is_const_int32, tmp_value) = _.EvalInt32IfConst(scope);
84
85
1.70k
  if (auto error = ValidateScope(_, inst, scope)) {
86
25
    return error;
87
25
  }
88
89
1.67k
  if (!is_const_int32) {
90
150
    return SPV_SUCCESS;
91
150
  }
92
93
1.52k
  spv::Scope value = spv::Scope(tmp_value);
94
95
  // Vulkan specific rules
96
1.52k
  if (spvIsVulkanEnv(_.context()->target_env)) {
97
    // Subgroups were not added until 1.1
98
0
    if (_.context()->target_env != SPV_ENV_VULKAN_1_0) {
99
      // Scope for Non Uniform Group Operations must be limited to Subgroup
100
0
      if ((spvOpcodeIsNonUniformGroupOperation(opcode) &&
101
0
           (opcode != spv::Op::OpGroupNonUniformQuadAllKHR) &&
102
0
           (opcode != spv::Op::OpGroupNonUniformQuadAnyKHR)) &&
103
0
          (value != spv::Scope::Subgroup)) {
104
0
        return _.diag(SPV_ERROR_INVALID_DATA, inst)
105
0
               << _.VkErrorID(4642) << spvOpcodeString(opcode)
106
0
               << ": in Vulkan environment Execution scope is limited to "
107
0
               << "Subgroup";
108
0
      }
109
0
    }
110
111
    // OpControlBarrier must only use Subgroup execution scope for a subset of
112
    // execution models.
113
0
    if (opcode == spv::Op::OpControlBarrier && value != spv::Scope::Subgroup) {
114
0
      std::string errorVUID = _.VkErrorID(4682);
115
0
      _.function(inst->function()->id())
116
0
          ->RegisterExecutionModelLimitation([errorVUID](
117
0
                                                 spv::ExecutionModel model,
118
0
                                                 std::string* message) {
119
0
            if (model == spv::ExecutionModel::Fragment ||
120
0
                model == spv::ExecutionModel::Vertex ||
121
0
                model == spv::ExecutionModel::Geometry ||
122
0
                model == spv::ExecutionModel::TessellationEvaluation ||
123
0
                model == spv::ExecutionModel::RayGenerationKHR ||
124
0
                model == spv::ExecutionModel::IntersectionKHR ||
125
0
                model == spv::ExecutionModel::AnyHitKHR ||
126
0
                model == spv::ExecutionModel::ClosestHitKHR ||
127
0
                model == spv::ExecutionModel::MissKHR) {
128
0
              if (message) {
129
0
                *message =
130
0
                    errorVUID +
131
0
                    "in Vulkan environment, OpControlBarrier execution scope "
132
0
                    "must be Subgroup for Fragment, Vertex, Geometry, "
133
0
                    "TessellationEvaluation, RayGeneration, Intersection, "
134
0
                    "AnyHit, ClosestHit, and Miss execution models";
135
0
              }
136
0
              return false;
137
0
            }
138
0
            return true;
139
0
          });
140
0
    }
141
142
    // Only subset of execution models support Workgroup.
143
0
    if (value == spv::Scope::Workgroup) {
144
0
      std::string errorVUID = _.VkErrorID(4637);
145
0
      _.function(inst->function()->id())
146
0
          ->RegisterExecutionModelLimitation(
147
0
              [errorVUID](spv::ExecutionModel model, std::string* message) {
148
0
                if (model != spv::ExecutionModel::TaskNV &&
149
0
                    model != spv::ExecutionModel::MeshNV &&
150
0
                    model != spv::ExecutionModel::TaskEXT &&
151
0
                    model != spv::ExecutionModel::MeshEXT &&
152
0
                    model != spv::ExecutionModel::TessellationControl &&
153
0
                    model != spv::ExecutionModel::GLCompute) {
154
0
                  if (message) {
155
0
                    *message =
156
0
                        errorVUID +
157
0
                        "in Vulkan environment, Workgroup execution scope is "
158
0
                        "only for TaskNV, MeshNV, TaskEXT, MeshEXT, "
159
0
                        "TessellationControl, and GLCompute execution models";
160
0
                  }
161
0
                  return false;
162
0
                }
163
0
                return true;
164
0
              });
165
0
    }
166
167
    // Vulkan generic rules
168
    // Scope for execution must be limited to Workgroup or Subgroup
169
0
    if (value != spv::Scope::Workgroup && value != spv::Scope::Subgroup) {
170
0
      return _.diag(SPV_ERROR_INVALID_DATA, inst)
171
0
             << _.VkErrorID(4636) << spvOpcodeString(opcode)
172
0
             << ": in Vulkan environment Execution Scope is limited to "
173
0
             << "Workgroup and Subgroup";
174
0
    }
175
0
  }
176
177
  // TODO(atgoo@github.com) Add checks for OpenCL and OpenGL environments.
178
179
  // General SPIRV rules
180
  // Scope for execution must be limited to Workgroup or Subgroup for
181
  // non-uniform operations
182
1.52k
  if (spvOpcodeIsNonUniformGroupOperation(opcode) &&
183
8
      opcode != spv::Op::OpGroupNonUniformQuadAllKHR &&
184
8
      opcode != spv::Op::OpGroupNonUniformQuadAnyKHR &&
185
8
      value != spv::Scope::Subgroup && value != spv::Scope::Workgroup) {
186
3
    return _.diag(SPV_ERROR_INVALID_DATA, inst)
187
3
           << spvOpcodeString(opcode)
188
3
           << ": Execution scope is limited to Subgroup or Workgroup";
189
3
  }
190
191
1.52k
  return SPV_SUCCESS;
192
1.52k
}
193
194
spv_result_t ValidateMemoryScope(ValidationState_t& _, const Instruction* inst,
195
1.95k
                                 uint32_t scope) {
196
1.95k
  const spv::Op opcode = inst->opcode();
197
1.95k
  bool is_int32 = false, is_const_int32 = false;
198
1.95k
  uint32_t tmp_value = 0;
199
1.95k
  std::tie(is_int32, is_const_int32, tmp_value) = _.EvalInt32IfConst(scope);
200
201
1.95k
  if (auto error = ValidateScope(_, inst, scope)) {
202
52
    return error;
203
52
  }
204
205
1.90k
  if (!is_const_int32) {
206
150
    return SPV_SUCCESS;
207
150
  }
208
209
1.75k
  spv::Scope value = spv::Scope(tmp_value);
210
211
1.75k
  if (value == spv::Scope::QueueFamilyKHR) {
212
4
    if (_.HasCapability(spv::Capability::VulkanMemoryModelKHR)) {
213
0
      return SPV_SUCCESS;
214
4
    } else {
215
4
      return _.diag(SPV_ERROR_INVALID_DATA, inst)
216
4
             << spvOpcodeString(opcode)
217
4
             << ": Memory Scope QueueFamilyKHR requires capability "
218
4
             << "VulkanMemoryModelKHR";
219
4
    }
220
4
  }
221
222
1.75k
  if (value == spv::Scope::Device &&
223
532
      _.HasCapability(spv::Capability::VulkanMemoryModelKHR) &&
224
0
      !_.HasCapability(spv::Capability::VulkanMemoryModelDeviceScopeKHR)) {
225
0
    return _.diag(SPV_ERROR_INVALID_DATA, inst)
226
0
           << "Use of device scope with VulkanKHR memory model requires the "
227
0
           << "VulkanMemoryModelDeviceScopeKHR capability";
228
0
  }
229
230
  // Vulkan Specific rules
231
1.75k
  if (spvIsVulkanEnv(_.context()->target_env)) {
232
0
    if (value != spv::Scope::Device && value != spv::Scope::Workgroup &&
233
0
        value != spv::Scope::Subgroup && value != spv::Scope::Invocation &&
234
0
        value != spv::Scope::ShaderCallKHR &&
235
0
        value != spv::Scope::QueueFamily) {
236
0
      return _.diag(SPV_ERROR_INVALID_DATA, inst)
237
0
             << _.VkErrorID(4638) << spvOpcodeString(opcode)
238
0
             << ": in Vulkan environment Memory Scope is limited to Device, "
239
0
                "QueueFamily, Workgroup, ShaderCallKHR, Subgroup, or "
240
0
                "Invocation";
241
0
    } else if (_.context()->target_env == SPV_ENV_VULKAN_1_0 &&
242
0
               value == spv::Scope::Subgroup &&
243
0
               !_.HasCapability(spv::Capability::SubgroupBallotKHR) &&
244
0
               !_.HasCapability(spv::Capability::SubgroupVoteKHR)) {
245
0
      return _.diag(SPV_ERROR_INVALID_DATA, inst)
246
0
             << _.VkErrorID(7951) << spvOpcodeString(opcode)
247
0
             << ": in Vulkan 1.0 environment Memory Scope is can not be "
248
0
                "Subgroup without SubgroupBallotKHR or SubgroupVoteKHR "
249
0
                "declared";
250
0
    }
251
252
0
    if (value == spv::Scope::ShaderCallKHR) {
253
0
      std::string errorVUID = _.VkErrorID(4640);
254
0
      _.function(inst->function()->id())
255
0
          ->RegisterExecutionModelLimitation(
256
0
              [errorVUID](spv::ExecutionModel model, std::string* message) {
257
0
                if (model != spv::ExecutionModel::RayGenerationKHR &&
258
0
                    model != spv::ExecutionModel::IntersectionKHR &&
259
0
                    model != spv::ExecutionModel::AnyHitKHR &&
260
0
                    model != spv::ExecutionModel::ClosestHitKHR &&
261
0
                    model != spv::ExecutionModel::MissKHR &&
262
0
                    model != spv::ExecutionModel::CallableKHR) {
263
0
                  if (message) {
264
0
                    *message =
265
0
                        errorVUID +
266
0
                        "ShaderCallKHR Memory Scope requires a ray tracing "
267
0
                        "execution model";
268
0
                  }
269
0
                  return false;
270
0
                }
271
0
                return true;
272
0
              });
273
0
    }
274
275
0
    if (value == spv::Scope::Workgroup) {
276
0
      std::string errorVUID = _.VkErrorID(7321);
277
0
      _.function(inst->function()->id())
278
0
          ->RegisterExecutionModelLimitation(
279
0
              [errorVUID](spv::ExecutionModel model, std::string* message) {
280
0
                if (model != spv::ExecutionModel::GLCompute &&
281
0
                    model != spv::ExecutionModel::TessellationControl &&
282
0
                    model != spv::ExecutionModel::TaskNV &&
283
0
                    model != spv::ExecutionModel::MeshNV &&
284
0
                    model != spv::ExecutionModel::TaskEXT &&
285
0
                    model != spv::ExecutionModel::MeshEXT) {
286
0
                  if (message) {
287
0
                    *message = errorVUID +
288
0
                               "Workgroup Memory Scope is limited to MeshNV, "
289
0
                               "TaskNV, MeshEXT, TaskEXT, TessellationControl, "
290
0
                               "and GLCompute execution model";
291
0
                  }
292
0
                  return false;
293
0
                }
294
0
                return true;
295
0
              });
296
297
0
      if (_.memory_model() == spv::MemoryModel::GLSL450) {
298
0
        errorVUID = _.VkErrorID(7320);
299
0
        _.function(inst->function()->id())
300
0
            ->RegisterExecutionModelLimitation(
301
0
                [errorVUID](spv::ExecutionModel model, std::string* message) {
302
0
                  if (model == spv::ExecutionModel::TessellationControl) {
303
0
                    if (message) {
304
0
                      *message =
305
0
                          errorVUID +
306
0
                          "Workgroup Memory Scope can't be used with "
307
0
                          "TessellationControl using GLSL450 Memory Model";
308
0
                    }
309
0
                    return false;
310
0
                  }
311
0
                  return true;
312
0
                });
313
0
      }
314
0
    }
315
0
  }
316
317
  // TODO(atgoo@github.com) Add checks for OpenCL and OpenGL environments.
318
319
1.75k
  return SPV_SUCCESS;
320
1.75k
}
321
322
}  // namespace val
323
}  // namespace spvtools