Coverage Report

Created: 2026-06-08 06:54

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/spirv-tools/source/val/validate_mode_setting.cpp
Line
Count
Source
1
// Copyright (c) 2018 Google LLC.
2
// Modifications Copyright (C) 2024 Advanced Micro Devices, Inc. All rights
3
// reserved.
4
// Copyright (C) 2026 Qualcomm Technologies, Inc.
5
//
6
// Licensed under the Apache License, Version 2.0 (the "License");
7
// you may not use this file except in compliance with the License.
8
// You may obtain a copy of the License at
9
//
10
//     http://www.apache.org/licenses/LICENSE-2.0
11
//
12
// Unless required by applicable law or agreed to in writing, software
13
// distributed under the License is distributed on an "AS IS" BASIS,
14
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
// See the License for the specific language governing permissions and
16
// limitations under the License.
17
//
18
#include <algorithm>
19
20
#include "source/opcode.h"
21
#include "source/spirv_target_env.h"
22
#include "source/table2.h"
23
#include "source/val/instruction.h"
24
#include "source/val/validate.h"
25
#include "source/val/validation_state.h"
26
27
namespace spvtools {
28
namespace val {
29
namespace {
30
31
// TODO - Make a common util if someone else needs it too outside this file
32
0
const char* ExecutionModelToString(spv::ExecutionModel value) {
33
0
  switch (value) {
34
0
    case spv::ExecutionModel::Vertex:
35
0
      return "Vertex";
36
0
    case spv::ExecutionModel::TessellationControl:
37
0
      return "TessellationControl";
38
0
    case spv::ExecutionModel::TessellationEvaluation:
39
0
      return "TessellationEvaluation";
40
0
    case spv::ExecutionModel::Geometry:
41
0
      return "Geometry";
42
0
    case spv::ExecutionModel::Fragment:
43
0
      return "Fragment";
44
0
    case spv::ExecutionModel::GLCompute:
45
0
      return "GLCompute";
46
0
    case spv::ExecutionModel::Kernel:
47
0
      return "Kernel";
48
0
    case spv::ExecutionModel::TaskNV:
49
0
      return "TaskNV";
50
0
    case spv::ExecutionModel::MeshNV:
51
0
      return "MeshNV";
52
0
    case spv::ExecutionModel::RayGenerationKHR:
53
0
      return "RayGenerationKHR";
54
0
    case spv::ExecutionModel::IntersectionKHR:
55
0
      return "IntersectionKHR";
56
0
    case spv::ExecutionModel::AnyHitKHR:
57
0
      return "AnyHitKHR";
58
0
    case spv::ExecutionModel::ClosestHitKHR:
59
0
      return "ClosestHitKHR";
60
0
    case spv::ExecutionModel::MissKHR:
61
0
      return "MissKHR";
62
0
    case spv::ExecutionModel::CallableKHR:
63
0
      return "CallableKHR";
64
0
    case spv::ExecutionModel::TaskEXT:
65
0
      return "TaskEXT";
66
0
    case spv::ExecutionModel::MeshEXT:
67
0
      return "MeshEXT";
68
0
    default:
69
0
      return "Unknown";
70
0
  }
71
0
}
72
73
24.2k
spv_result_t ValidateEntryPoint(ValidationState_t& _, const Instruction* inst) {
74
24.2k
  const auto entry_point_id = inst->GetOperandAs<uint32_t>(1);
75
24.2k
  auto entry_point = _.FindDef(entry_point_id);
76
24.2k
  if (!entry_point || spv::Op::OpFunction != entry_point->opcode()) {
77
15
    return _.diag(SPV_ERROR_INVALID_ID, inst)
78
15
           << "OpEntryPoint Entry Point <id> " << _.getIdName(entry_point_id)
79
15
           << " is not a function.";
80
15
  }
81
82
  // Only check the shader execution models
83
24.1k
  const spv::ExecutionModel execution_model =
84
24.1k
      inst->GetOperandAs<spv::ExecutionModel>(0);
85
24.1k
  if (execution_model != spv::ExecutionModel::Kernel) {
86
24.0k
    const auto entry_point_type_id = entry_point->GetOperandAs<uint32_t>(3);
87
24.0k
    const auto entry_point_type = _.FindDef(entry_point_type_id);
88
24.0k
    if (!entry_point_type || 3 != entry_point_type->words().size()) {
89
6
      return _.diag(SPV_ERROR_INVALID_ID, inst)
90
6
             << _.VkErrorID(4633) << "OpEntryPoint Entry Point <id> "
91
6
             << _.getIdName(entry_point_id)
92
6
             << "s function parameter count is not zero.";
93
6
    }
94
24.0k
  }
95
96
24.1k
  auto return_type = _.FindDef(entry_point->type_id());
97
24.1k
  if (!return_type || spv::Op::OpTypeVoid != return_type->opcode()) {
98
6
    return _.diag(SPV_ERROR_INVALID_ID, inst)
99
6
           << _.VkErrorID(4633) << "OpEntryPoint Entry Point <id> "
100
6
           << _.getIdName(entry_point_id)
101
6
           << "s function return type is not void.";
102
6
  }
103
104
24.1k
  const auto* execution_modes = _.GetExecutionModes(entry_point_id);
105
58.0k
  auto has_mode = [&execution_modes](spv::ExecutionMode mode) {
106
58.0k
    return execution_modes && execution_modes->count(mode);
107
58.0k
  };
108
109
24.1k
  if (_.HasCapability(spv::Capability::Shader)) {
110
24.0k
    switch (execution_model) {
111
18.9k
      case spv::ExecutionModel::Fragment:
112
18.9k
        if (has_mode(spv::ExecutionMode::OriginUpperLeft) &&
113
18.0k
            has_mode(spv::ExecutionMode::OriginLowerLeft)) {
114
3
          return _.diag(SPV_ERROR_INVALID_DATA, inst)
115
3
                 << "Fragment execution model entry points can only specify "
116
3
                    "one of OriginUpperLeft or OriginLowerLeft execution "
117
3
                    "modes.";
118
3
        }
119
18.9k
        if (!has_mode(spv::ExecutionMode::OriginUpperLeft) &&
120
972
            !has_mode(spv::ExecutionMode::OriginLowerLeft)) {
121
27
          return _.diag(SPV_ERROR_INVALID_DATA, inst)
122
27
                 << "Fragment execution model entry points require either an "
123
27
                    "OriginUpperLeft or OriginLowerLeft execution mode.";
124
27
        }
125
18.9k
        if (execution_modes &&
126
18.9k
            1 < std::count_if(execution_modes->begin(), execution_modes->end(),
127
20.0k
                              [](const spv::ExecutionMode& mode) {
128
20.0k
                                switch (mode) {
129
57
                                  case spv::ExecutionMode::DepthGreater:
130
102
                                  case spv::ExecutionMode::DepthLess:
131
243
                                  case spv::ExecutionMode::DepthUnchanged:
132
243
                                    return true;
133
19.7k
                                  default:
134
19.7k
                                    return false;
135
20.0k
                                }
136
20.0k
                              })) {
137
3
          return _.diag(SPV_ERROR_INVALID_DATA, inst)
138
3
                 << "Fragment execution model entry points can specify at most "
139
3
                    "one of DepthGreater, DepthLess or DepthUnchanged "
140
3
                    "execution modes.";
141
3
        }
142
18.9k
        if (execution_modes &&
143
18.9k
            1 < std::count_if(
144
18.9k
                    execution_modes->begin(), execution_modes->end(),
145
20.0k
                    [](const spv::ExecutionMode& mode) {
146
20.0k
                      switch (mode) {
147
0
                        case spv::ExecutionMode::PixelInterlockOrderedEXT:
148
0
                        case spv::ExecutionMode::PixelInterlockUnorderedEXT:
149
0
                        case spv::ExecutionMode::SampleInterlockOrderedEXT:
150
0
                        case spv::ExecutionMode::SampleInterlockUnorderedEXT:
151
0
                        case spv::ExecutionMode::ShadingRateInterlockOrderedEXT:
152
0
                        case spv::ExecutionMode::
153
0
                            ShadingRateInterlockUnorderedEXT:
154
0
                          return true;
155
20.0k
                        default:
156
20.0k
                          return false;
157
20.0k
                      }
158
20.0k
                    })) {
159
0
          return _.diag(SPV_ERROR_INVALID_DATA, inst)
160
0
                 << "Fragment execution model entry points can specify at most "
161
0
                    "one fragment shader interlock execution mode.";
162
0
        }
163
18.9k
        if (execution_modes &&
164
18.9k
            1 < std::count_if(
165
18.9k
                    execution_modes->begin(), execution_modes->end(),
166
20.0k
                    [](const spv::ExecutionMode& mode) {
167
20.0k
                      switch (mode) {
168
0
                        case spv::ExecutionMode::StencilRefUnchangedFrontAMD:
169
0
                        case spv::ExecutionMode::StencilRefLessFrontAMD:
170
0
                        case spv::ExecutionMode::StencilRefGreaterFrontAMD:
171
0
                          return true;
172
20.0k
                        default:
173
20.0k
                          return false;
174
20.0k
                      }
175
20.0k
                    })) {
176
0
          return _.diag(SPV_ERROR_INVALID_DATA, inst)
177
0
                 << "Fragment execution model entry points can specify at most "
178
0
                    "one of StencilRefUnchangedFrontAMD, "
179
0
                    "StencilRefLessFrontAMD or StencilRefGreaterFrontAMD "
180
0
                    "execution modes.";
181
0
        }
182
18.9k
        if (execution_modes &&
183
18.9k
            1 < std::count_if(
184
18.9k
                    execution_modes->begin(), execution_modes->end(),
185
20.0k
                    [](const spv::ExecutionMode& mode) {
186
20.0k
                      switch (mode) {
187
0
                        case spv::ExecutionMode::StencilRefUnchangedBackAMD:
188
0
                        case spv::ExecutionMode::StencilRefLessBackAMD:
189
0
                        case spv::ExecutionMode::StencilRefGreaterBackAMD:
190
0
                          return true;
191
20.0k
                        default:
192
20.0k
                          return false;
193
20.0k
                      }
194
20.0k
                    })) {
195
0
          return _.diag(SPV_ERROR_INVALID_DATA, inst)
196
0
                 << "Fragment execution model entry points can specify at most "
197
0
                    "one of StencilRefUnchangedBackAMD, "
198
0
                    "StencilRefLessBackAMD or StencilRefGreaterBackAMD "
199
0
                    "execution modes.";
200
0
        }
201
18.9k
        break;
202
18.9k
      case spv::ExecutionModel::TessellationControl:
203
929
      case spv::ExecutionModel::TessellationEvaluation:
204
929
        if (execution_modes &&
205
514
            1 < std::count_if(
206
514
                    execution_modes->begin(), execution_modes->end(),
207
1.31k
                    [](const spv::ExecutionMode& mode) {
208
1.31k
                      switch (mode) {
209
133
                        case spv::ExecutionMode::SpacingEqual:
210
189
                        case spv::ExecutionMode::SpacingFractionalEven:
211
237
                        case spv::ExecutionMode::SpacingFractionalOdd:
212
237
                          return true;
213
1.07k
                        default:
214
1.07k
                          return false;
215
1.31k
                      }
216
1.31k
                    })) {
217
3
          return _.diag(SPV_ERROR_INVALID_DATA, inst)
218
3
                 << "Tessellation execution model entry points can specify at "
219
3
                    "most one of SpacingEqual, SpacingFractionalOdd or "
220
3
                    "SpacingFractionalEven execution modes.";
221
3
        }
222
926
        if (execution_modes &&
223
511
            1 < std::count_if(execution_modes->begin(), execution_modes->end(),
224
1.30k
                              [](const spv::ExecutionMode& mode) {
225
1.30k
                                switch (mode) {
226
79
                                  case spv::ExecutionMode::Triangles:
227
140
                                  case spv::ExecutionMode::Quads:
228
233
                                  case spv::ExecutionMode::Isolines:
229
233
                                    return true;
230
1.07k
                                  default:
231
1.07k
                                    return false;
232
1.30k
                                }
233
1.30k
                              })) {
234
4
          return _.diag(SPV_ERROR_INVALID_DATA, inst)
235
4
                 << "Tessellation execution model entry points can specify at "
236
4
                    "most one of Triangles, Quads or Isolines execution modes.";
237
4
        }
238
922
        if (execution_modes &&
239
507
            1 < std::count_if(execution_modes->begin(), execution_modes->end(),
240
1.29k
                              [](const spv::ExecutionMode& mode) {
241
1.29k
                                switch (mode) {
242
86
                                  case spv::ExecutionMode::VertexOrderCw:
243
203
                                  case spv::ExecutionMode::VertexOrderCcw:
244
203
                                    return true;
245
1.09k
                                  default:
246
1.09k
                                    return false;
247
1.29k
                                }
248
1.29k
                              })) {
249
3
          return _.diag(SPV_ERROR_INVALID_DATA, inst)
250
3
                 << "Tessellation execution model entry points can specify at "
251
3
                    "most one of VertexOrderCw or VertexOrderCcw execution "
252
3
                    "modes.";
253
3
        }
254
919
        break;
255
919
      case spv::ExecutionModel::Geometry:
256
233
        if (!execution_modes ||
257
230
            1 != std::count_if(
258
230
                     execution_modes->begin(), execution_modes->end(),
259
669
                     [](const spv::ExecutionMode& mode) {
260
669
                       switch (mode) {
261
44
                         case spv::ExecutionMode::InputPoints:
262
91
                         case spv::ExecutionMode::InputLines:
263
196
                         case spv::ExecutionMode::InputLinesAdjacency:
264
203
                         case spv::ExecutionMode::Triangles:
265
234
                         case spv::ExecutionMode::InputTrianglesAdjacency:
266
234
                           return true;
267
435
                         default:
268
435
                           return false;
269
669
                       }
270
669
                     })) {
271
16
          return _.diag(SPV_ERROR_INVALID_DATA, inst)
272
16
                 << "Geometry execution model entry points must specify "
273
16
                    "exactly one of InputPoints, InputLines, "
274
16
                    "InputLinesAdjacency, Triangles or InputTrianglesAdjacency "
275
16
                    "execution modes.";
276
16
        }
277
217
        if (!execution_modes ||
278
217
            1 != std::count_if(execution_modes->begin(), execution_modes->end(),
279
636
                               [](const spv::ExecutionMode& mode) {
280
636
                                 switch (mode) {
281
41
                                   case spv::ExecutionMode::OutputPoints:
282
109
                                   case spv::ExecutionMode::OutputLineStrip:
283
212
                                   case spv::ExecutionMode::OutputTriangleStrip:
284
212
                                     return true;
285
424
                                   default:
286
424
                                     return false;
287
636
                                 }
288
636
                               })) {
289
12
          return _.diag(SPV_ERROR_INVALID_DATA, inst)
290
12
                 << "Geometry execution model entry points must specify "
291
12
                    "exactly one of OutputPoints, OutputLineStrip or "
292
12
                    "OutputTriangleStrip execution modes.";
293
12
        }
294
205
        break;
295
205
      case spv::ExecutionModel::MeshEXT:
296
0
        if (!execution_modes ||
297
0
            1 != std::count_if(execution_modes->begin(), execution_modes->end(),
298
0
                               [](const spv::ExecutionMode& mode) {
299
0
                                 switch (mode) {
300
0
                                   case spv::ExecutionMode::OutputPoints:
301
0
                                   case spv::ExecutionMode::OutputLinesEXT:
302
0
                                   case spv::ExecutionMode::OutputTrianglesEXT:
303
0
                                     return true;
304
0
                                   default:
305
0
                                     return false;
306
0
                                 }
307
0
                               })) {
308
0
          return _.diag(SPV_ERROR_INVALID_DATA, inst)
309
0
                 << "MeshEXT execution model entry points must specify exactly "
310
0
                    "one of OutputPoints, OutputLinesEXT, or "
311
0
                    "OutputTrianglesEXT Execution Modes.";
312
0
        } else if (2 != std::count_if(
313
0
                            execution_modes->begin(), execution_modes->end(),
314
0
                            [](const spv::ExecutionMode& mode) {
315
0
                              switch (mode) {
316
0
                                case spv::ExecutionMode::OutputPrimitivesEXT:
317
0
                                case spv::ExecutionMode::OutputVertices:
318
0
                                  return true;
319
0
                                default:
320
0
                                  return false;
321
0
                              }
322
0
                            })) {
323
0
          return _.diag(SPV_ERROR_INVALID_DATA, inst)
324
0
                 << "MeshEXT execution model entry points must specify both "
325
0
                    "OutputPrimitivesEXT and OutputVertices Execution Modes.";
326
0
        }
327
0
        break;
328
3.94k
      default:
329
3.94k
        break;
330
24.0k
    }
331
24.0k
  }
332
333
24.1k
  bool has_workgroup_size = false;
334
24.1k
  bool has_local_size_id = false;
335
1.42M
  for (auto& i : _.ordered_instructions()) {
336
1.42M
    if (i.opcode() == spv::Op::OpFunction) break;
337
1.39M
    if (i.opcode() == spv::Op::OpDecorate && i.operands().size() > 2) {
338
322k
      if (i.GetOperandAs<spv::Decoration>(1) == spv::Decoration::BuiltIn &&
339
3.90k
          i.GetOperandAs<spv::BuiltIn>(2) == spv::BuiltIn::WorkgroupSize) {
340
184
        has_workgroup_size = true;
341
184
      }
342
322k
    }
343
1.39M
    if (i.opcode() == spv::Op::OpExecutionModeId) {
344
0
      if (i.GetOperandAs<spv::ExecutionMode>(1) ==
345
0
          spv::ExecutionMode::LocalSizeId) {
346
0
        has_local_size_id = true;
347
0
      }
348
0
    }
349
1.39M
  }
350
351
24.1k
  if (spvIsVulkanEnv(_.context()->target_env)) {
352
    // SPV_QCOM_tile_shading checks
353
0
    if (execution_model == spv::ExecutionModel::GLCompute) {
354
0
      if (_.HasCapability(spv::Capability::TileShadingQCOM)) {
355
0
        if (has_mode(spv::ExecutionMode::TileShadingRateQCOM) &&
356
0
            (has_mode(spv::ExecutionMode::LocalSize) ||
357
0
             has_mode(spv::ExecutionMode::LocalSizeId))) {
358
0
          return _.diag(SPV_ERROR_INVALID_DATA, inst)
359
0
                 << _.VkErrorID(10692)
360
0
                 << "If the TileShadingRateQCOM execution mode is used, "
361
0
                 << "LocalSize and LocalSizeId must not be specified.";
362
0
        }
363
0
        if (has_mode(spv::ExecutionMode::NonCoherentTileAttachmentReadQCOM)) {
364
0
          return _.diag(SPV_ERROR_INVALID_DATA, inst)
365
0
                 << _.VkErrorID(10687)
366
0
                 << "The NonCoherentTileAttachmentQCOM execution mode must "
367
0
                    "not be used in any stage other than fragment.";
368
0
        }
369
0
      } else {
370
0
        if (has_mode(spv::ExecutionMode::TileShadingRateQCOM)) {
371
0
          return _.diag(SPV_ERROR_INVALID_DATA, inst)
372
0
                 << _.VkErrorID(10691)
373
0
                 << "If the TileShadingRateQCOM execution mode is used, the "
374
0
                    "TileShadingQCOM capability must be enabled.";
375
0
        }
376
0
      }
377
0
    } else {
378
0
      if (has_mode(spv::ExecutionMode::TileShadingRateQCOM)) {
379
0
        return _.diag(SPV_ERROR_INVALID_DATA, inst)
380
0
               << _.VkErrorID(10688)
381
0
               << "The TileShadingRateQCOM execution mode must not be used "
382
0
                  "in any stage other than compute.";
383
0
      }
384
0
      if (execution_model != spv::ExecutionModel::Fragment) {
385
0
        if (has_mode(spv::ExecutionMode::NonCoherentTileAttachmentReadQCOM)) {
386
0
          return _.diag(SPV_ERROR_INVALID_DATA, inst)
387
0
                 << _.VkErrorID(10687)
388
0
                 << "The NonCoherentTileAttachmentQCOM execution mode must "
389
0
                    "not be used in any stage other than fragment.";
390
0
        }
391
0
        if (_.HasCapability(spv::Capability::TileShadingQCOM)) {
392
0
          return _.diag(SPV_ERROR_INVALID_CAPABILITY, inst)
393
0
                 << _.VkErrorID(10686)
394
0
                 << "The TileShadingQCOM capability must not be enabled in "
395
0
                    "any stage other than compute or fragment.";
396
0
        }
397
0
      } else {
398
0
        if (has_mode(spv::ExecutionMode::NonCoherentTileAttachmentReadQCOM)) {
399
0
          if (!_.HasCapability(spv::Capability::TileShadingQCOM)) {
400
0
            return _.diag(SPV_ERROR_INVALID_DATA, inst)
401
0
                   << _.VkErrorID(10690)
402
0
                   << "If the NonCoherentTileAttachmentReadQCOM execution "
403
0
                      "mode is used, the TileShadingQCOM capability must be "
404
0
                      "enabled.";
405
0
          }
406
0
        }
407
0
      }
408
0
    }
409
410
0
    switch (execution_model) {
411
0
      case spv::ExecutionModel::GLCompute:
412
0
      case spv::ExecutionModel::MeshEXT:
413
0
      case spv::ExecutionModel::MeshNV:
414
0
      case spv::ExecutionModel::TaskEXT:
415
0
      case spv::ExecutionModel::TaskNV:
416
0
        if (!has_mode(spv::ExecutionMode::LocalSize) && !has_workgroup_size &&
417
0
            !has_local_size_id &&
418
0
            !has_mode(spv::ExecutionMode::TileShadingRateQCOM)) {
419
0
          return _.diag(SPV_ERROR_INVALID_DATA, inst)
420
0
                 << _.VkErrorID(10685) << "In the Vulkan environment, "
421
0
                 << ExecutionModelToString(execution_model)
422
0
                 << " execution model "
423
0
                    "entry points require either the "
424
0
                 << (_.HasCapability(spv::Capability::TileShadingQCOM)
425
0
                         ? "TileShadingRateQCOM, "
426
0
                         : "")
427
0
                 << "LocalSize or LocalSizeId execution mode or an object "
428
0
                    "decorated with WorkgroupSize must be specified.";
429
0
        }
430
0
        break;
431
0
      default:
432
0
        break;
433
0
    }
434
0
  }
435
436
  // WorkgroupSize decoration takes precedence over any LocalSize or LocalSizeId
437
  // execution mode, so the values can be ignored
438
24.1k
  if (_.EntryPointHasLocalSizeOrId(entry_point_id) && !has_workgroup_size) {
439
544
    const Instruction* local_size_inst =
440
544
        _.EntryPointLocalSizeOrId(entry_point_id);
441
544
    if (local_size_inst) {
442
544
      const auto mode = local_size_inst->GetOperandAs<spv::ExecutionMode>(1);
443
544
      const uint32_t operand_x = local_size_inst->GetOperandAs<uint32_t>(2);
444
544
      const uint32_t operand_y = local_size_inst->GetOperandAs<uint32_t>(3);
445
544
      const uint32_t operand_z = local_size_inst->GetOperandAs<uint32_t>(4);
446
544
      if (mode == spv::ExecutionMode::LocalSize) {
447
544
        const uint64_t product_size = operand_x * operand_y * operand_z;
448
544
        if (product_size == 0) {
449
6
          return _.diag(SPV_ERROR_INVALID_DATA, local_size_inst)
450
6
                 << "Local Size execution mode must not have a product of zero "
451
6
                    "(X "
452
6
                    "= "
453
6
                 << operand_x << ", Y = " << operand_y << ", Z = " << operand_z
454
6
                 << ").";
455
6
        }
456
538
        if (has_mode(spv::ExecutionMode::DerivativeGroupQuadsKHR)) {
457
0
          if (operand_x % 2 != 0 || operand_y % 2 != 0) {
458
0
            return _.diag(SPV_ERROR_INVALID_DATA, local_size_inst)
459
0
                   << _.VkErrorID(10151)
460
0
                   << "Local Size execution mode dimensions is "
461
0
                      "(X = "
462
0
                   << operand_x << ", Y = " << operand_y
463
0
                   << ") but Entry Point id " << entry_point_id
464
0
                   << " also has an DerivativeGroupQuadsKHR execution mode, so "
465
0
                      "both dimensions must be a multiple of 2";
466
0
          }
467
0
        }
468
538
        if (has_mode(spv::ExecutionMode::DerivativeGroupLinearKHR)) {
469
0
          if (product_size % 4 != 0) {
470
0
            return _.diag(SPV_ERROR_INVALID_DATA, local_size_inst)
471
0
                   << _.VkErrorID(10152)
472
0
                   << "Local Size execution mode dimensions is (X = "
473
0
                   << operand_x << ", Y = " << operand_y
474
0
                   << ", Z = " << operand_z << ") but Entry Point id "
475
0
                   << entry_point_id
476
0
                   << " also has an DerivativeGroupLinearKHR execution mode, "
477
0
                      "so "
478
0
                      "the product ("
479
0
                   << product_size << ") must be a multiple of 4";
480
0
          }
481
0
        }
482
538
      } else if (mode == spv::ExecutionMode::LocalSizeId) {
483
        // can only validate product if static and not spec constant
484
        // (This is done for us in EvalConstantValUint64)
485
0
        uint64_t x_size, y_size, z_size;
486
0
        bool static_x = _.EvalConstantValUint64(operand_x, &x_size);
487
0
        bool static_y = _.EvalConstantValUint64(operand_y, &y_size);
488
0
        bool static_z = _.EvalConstantValUint64(operand_z, &z_size);
489
0
        if (static_x && static_y && static_z) {
490
0
          const uint64_t product_size = x_size * y_size * z_size;
491
0
          if (product_size == 0) {
492
0
            return _.diag(SPV_ERROR_INVALID_DATA, local_size_inst)
493
0
                   << "LocalSizeId execution mode must not have a product of "
494
0
                      "zero "
495
0
                      "(X = "
496
0
                   << x_size << ", Y = " << y_size << ", Z = " << z_size
497
0
                   << ").";
498
0
          }
499
0
          if (has_mode(spv::ExecutionMode::DerivativeGroupQuadsKHR)) {
500
0
            if (x_size % 2 != 0 || y_size % 2 != 0) {
501
0
              return _.diag(SPV_ERROR_INVALID_DATA, local_size_inst)
502
0
                     << _.VkErrorID(10151)
503
0
                     << "LocalSizeId execution mode dimensions is "
504
0
                        "(X = "
505
0
                     << x_size << ", Y = " << y_size << ") but Entry Point id "
506
0
                     << entry_point_id
507
0
                     << " also has an DerivativeGroupQuadsKHR execution mode, "
508
0
                        "so "
509
0
                        "both dimensions must be a multiple of 2";
510
0
            }
511
0
          }
512
0
          if (has_mode(spv::ExecutionMode::DerivativeGroupLinearKHR)) {
513
0
            if (product_size % 4 != 0) {
514
0
              return _.diag(SPV_ERROR_INVALID_DATA, local_size_inst)
515
0
                     << _.VkErrorID(10152)
516
0
                     << "LocalSizeId execution mode dimensions is (X = "
517
0
                     << x_size << ", Y = " << y_size << ", Z = " << z_size
518
0
                     << ") but Entry Point id " << entry_point_id
519
0
                     << " also has an DerivativeGroupLinearKHR execution mode, "
520
0
                        "so "
521
0
                        "the product ("
522
0
                     << product_size << ") must be a multiple of 4";
523
0
            }
524
0
          }
525
0
        }
526
0
      }
527
544
    }
528
544
  }
529
530
24.1k
  return SPV_SUCCESS;
531
24.1k
}
532
533
spv_result_t ValidateExecutionMode(ValidationState_t& _,
534
32.3k
                                   const Instruction* inst) {
535
32.3k
  const auto entry_point_id = inst->GetOperandAs<uint32_t>(0);
536
32.3k
  const auto found = std::find(_.entry_points().cbegin(),
537
32.3k
                               _.entry_points().cend(), entry_point_id);
538
32.3k
  if (found == _.entry_points().cend()) {
539
85
    return _.diag(SPV_ERROR_INVALID_ID, inst)
540
85
           << "OpExecutionMode Entry Point <id> " << _.getIdName(entry_point_id)
541
85
           << " is not the Entry Point "
542
85
              "operand of an OpEntryPoint.";
543
85
  }
544
545
32.3k
  const auto mode = inst->GetOperandAs<spv::ExecutionMode>(1);
546
32.3k
  if (inst->opcode() == spv::Op::OpExecutionModeId) {
547
0
    bool valid_mode = false;
548
0
    switch (mode) {
549
0
      case spv::ExecutionMode::SubgroupsPerWorkgroupId:
550
0
      case spv::ExecutionMode::LocalSizeHintId:
551
0
      case spv::ExecutionMode::LocalSizeId:
552
0
      case spv::ExecutionMode::OpacityMicromapIdKHR:
553
0
      case spv::ExecutionMode::FPFastMathDefault:
554
0
      case spv::ExecutionMode::MaximumRegistersIdINTEL:
555
0
      case spv::ExecutionMode::IsApiEntryAMDX:
556
0
      case spv::ExecutionMode::MaxNodeRecursionAMDX:
557
0
      case spv::ExecutionMode::MaxNumWorkgroupsAMDX:
558
0
      case spv::ExecutionMode::ShaderIndexAMDX:
559
0
      case spv::ExecutionMode::SharesInputWithAMDX:
560
0
      case spv::ExecutionMode::StaticNumWorkgroupsAMDX:
561
0
        valid_mode = true;
562
0
        break;
563
0
      default:
564
0
        valid_mode = false;
565
0
        break;
566
0
    }
567
0
    if (!valid_mode) {
568
0
      return _.diag(SPV_ERROR_INVALID_ID, inst)
569
0
             << "OpExecutionModeId is only valid when the Mode operand is an "
570
0
                "execution mode that takes Extra Operands that are id "
571
0
                "operands.";
572
0
    }
573
574
0
    size_t operand_count = inst->operands().size();
575
0
    for (size_t i = 2; i < operand_count; ++i) {
576
0
      const auto operand_id = inst->GetOperandAs<uint32_t>(i);
577
0
      const auto* operand_inst = _.FindDef(operand_id);
578
0
      switch (mode) {
579
0
        case spv::ExecutionMode::SubgroupsPerWorkgroupId:
580
0
        case spv::ExecutionMode::LocalSizeHintId:
581
0
        case spv::ExecutionMode::LocalSizeId:
582
0
        case spv::ExecutionMode::IsApiEntryAMDX:
583
0
        case spv::ExecutionMode::MaxNodeRecursionAMDX:
584
0
        case spv::ExecutionMode::MaxNumWorkgroupsAMDX:
585
0
        case spv::ExecutionMode::ShaderIndexAMDX:
586
0
        case spv::ExecutionMode::SharesInputWithAMDX:
587
0
        case spv::ExecutionMode::StaticNumWorkgroupsAMDX:
588
0
          if (!spvOpcodeIsConstant(operand_inst->opcode())) {
589
0
            return _.diag(SPV_ERROR_INVALID_ID, inst)
590
0
                   << "For OpExecutionModeId all Extra Operand ids must be "
591
0
                      "constant instructions.";
592
0
          }
593
0
          break;
594
0
        case spv::ExecutionMode::FPFastMathDefault:
595
0
          if (i == 2) {
596
0
            if (!_.IsFloatScalarType(operand_id)) {
597
0
              return _.diag(SPV_ERROR_INVALID_ID, inst)
598
0
                     << "The Target Type operand must be a floating-point "
599
0
                        "scalar type";
600
0
            }
601
0
          } else {
602
0
            bool is_int32 = false;
603
0
            bool is_const = false;
604
0
            uint32_t value = 0;
605
0
            std::tie(is_int32, is_const, value) =
606
0
                _.EvalInt32IfConst(operand_id);
607
0
            if (is_int32 && is_const) {
608
              // Valid values include up to 0x00040000 (AllowTransform).
609
0
              uint32_t invalid_mask = 0xfff80000;
610
0
              if ((invalid_mask & value) != 0) {
611
0
                return _.diag(SPV_ERROR_INVALID_ID, inst)
612
0
                       << "The Fast Math Default operand is an invalid bitmask "
613
0
                          "value";
614
0
              }
615
0
              if (value &
616
0
                  static_cast<uint32_t>(spv::FPFastMathModeMask::Fast)) {
617
0
                return _.diag(SPV_ERROR_INVALID_ID, inst)
618
0
                       << "The Fast Math Default operand must not include Fast";
619
0
              }
620
0
              const auto reassoc_contract =
621
0
                  spv::FPFastMathModeMask::AllowContract |
622
0
                  spv::FPFastMathModeMask::AllowReassoc;
623
0
              if ((value & static_cast<uint32_t>(
624
0
                               spv::FPFastMathModeMask::AllowTransform)) != 0 &&
625
0
                  ((value & static_cast<uint32_t>(reassoc_contract)) !=
626
0
                   static_cast<uint32_t>(reassoc_contract))) {
627
0
                return _.diag(SPV_ERROR_INVALID_ID, inst)
628
0
                       << "The Fast Math Default operand must include "
629
0
                          "AllowContract and AllowReassoc when AllowTransform "
630
0
                          "is specified";
631
0
              }
632
0
            } else {
633
0
              return _.diag(SPV_ERROR_INVALID_ID, inst)
634
0
                     << "The Fast Math Default operand must be a "
635
0
                        "non-specialization constant";
636
0
            }
637
0
          }
638
0
          break;
639
0
        case spv::ExecutionMode::OpacityMicromapIdKHR: {
640
0
          spv::Op operand_opcode = operand_inst->opcode();
641
0
          if (!spvOpcodeIsConstant(operand_opcode) ||
642
0
              !_.IsBoolScalarType(operand_inst->type_id())) {
643
0
            return _.diag(SPV_ERROR_INVALID_DATA, operand_inst)
644
0
                   << "OpacityMicromapIdKHR's operand must be an <id> "
645
0
                      "of a constant instruction of OpTypeBool.";
646
0
          }
647
0
          break;
648
0
        }
649
0
        default:
650
0
          break;
651
0
      }
652
0
    }
653
32.3k
  } else if (mode == spv::ExecutionMode::SubgroupsPerWorkgroupId ||
654
32.3k
             mode == spv::ExecutionMode::LocalSizeHintId ||
655
32.3k
             mode == spv::ExecutionMode::LocalSizeId ||
656
32.3k
             mode == spv::ExecutionMode::FPFastMathDefault ||
657
32.3k
             mode == spv::ExecutionMode::IsApiEntryAMDX ||
658
32.3k
             mode == spv::ExecutionMode::MaxNodeRecursionAMDX ||
659
32.3k
             mode == spv::ExecutionMode::MaxNumWorkgroupsAMDX ||
660
32.3k
             mode == spv::ExecutionMode::ShaderIndexAMDX ||
661
32.3k
             mode == spv::ExecutionMode::SharesInputWithAMDX ||
662
32.3k
             mode == spv::ExecutionMode::StaticNumWorkgroupsAMDX) {
663
0
    return _.diag(SPV_ERROR_INVALID_DATA, inst)
664
0
           << "OpExecutionMode is only valid when the Mode operand is an "
665
0
              "execution mode that takes no Extra Operands, or takes Extra "
666
0
              "Operands that are not id operands.";
667
0
  }
668
669
32.3k
  const bool is_vulkan_env = (spvIsVulkanEnv(_.context()->target_env));
670
32.3k
  const auto* models = _.GetExecutionModels(entry_point_id);
671
32.3k
  switch (mode) {
672
71
    case spv::ExecutionMode::Invocations:
673
220
    case spv::ExecutionMode::InputPoints:
674
397
    case spv::ExecutionMode::InputLines:
675
469
    case spv::ExecutionMode::InputLinesAdjacency:
676
581
    case spv::ExecutionMode::InputTrianglesAdjacency:
677
824
    case spv::ExecutionMode::OutputLineStrip:
678
1.71k
    case spv::ExecutionMode::OutputTriangleStrip:
679
1.71k
      if (!std::all_of(models->begin(), models->end(),
680
1.71k
                       [](const spv::ExecutionModel& model) {
681
1.71k
                         return model == spv::ExecutionModel::Geometry;
682
1.71k
                       })) {
683
19
        return _.diag(SPV_ERROR_INVALID_DATA, inst)
684
19
               << "Execution mode can only be used with the Geometry execution "
685
19
                  "model.";
686
19
      }
687
1.69k
      break;
688
1.69k
    case spv::ExecutionMode::OutputPoints:
689
55
      if (!std::all_of(
690
55
              models->begin(), models->end(),
691
58
              [&_](const spv::ExecutionModel& model) {
692
58
                switch (model) {
693
51
                  case spv::ExecutionModel::Geometry:
694
51
                    return true;
695
0
                  case spv::ExecutionModel::MeshNV:
696
0
                    return _.HasCapability(spv::Capability::MeshShadingNV);
697
0
                  case spv::ExecutionModel::MeshEXT:
698
0
                    return _.HasCapability(spv::Capability::MeshShadingEXT);
699
7
                  default:
700
7
                    return false;
701
58
                }
702
58
              })) {
703
7
        if (_.HasCapability(spv::Capability::MeshShadingNV) ||
704
7
            _.HasCapability(spv::Capability::MeshShadingEXT)) {
705
0
          return _.diag(SPV_ERROR_INVALID_DATA, inst)
706
0
                 << "Execution mode can only be used with the Geometry "
707
0
                    "MeshNV or MeshEXT execution model.";
708
7
        } else {
709
7
          return _.diag(SPV_ERROR_INVALID_DATA, inst)
710
7
                 << "Execution mode can only be used with the Geometry "
711
7
                    "execution "
712
7
                    "model.";
713
7
        }
714
7
      }
715
48
      break;
716
675
    case spv::ExecutionMode::SpacingEqual:
717
1.11k
    case spv::ExecutionMode::SpacingFractionalEven:
718
1.22k
    case spv::ExecutionMode::SpacingFractionalOdd:
719
2.80k
    case spv::ExecutionMode::VertexOrderCw:
720
3.16k
    case spv::ExecutionMode::VertexOrderCcw:
721
3.80k
    case spv::ExecutionMode::PointMode:
722
3.84k
    case spv::ExecutionMode::Quads:
723
3.94k
    case spv::ExecutionMode::Isolines:
724
3.94k
      if (!std::all_of(
725
3.94k
              models->begin(), models->end(),
726
4.88k
              [](const spv::ExecutionModel& model) {
727
4.88k
                return (model == spv::ExecutionModel::TessellationControl) ||
728
1.22k
                       (model == spv::ExecutionModel::TessellationEvaluation);
729
4.88k
              })) {
730
52
        return _.diag(SPV_ERROR_INVALID_DATA, inst)
731
52
               << "Execution mode can only be used with a tessellation "
732
52
                  "execution model.";
733
52
      }
734
3.89k
      break;
735
3.89k
    case spv::ExecutionMode::Triangles:
736
91
      if (!std::all_of(models->begin(), models->end(),
737
121
                       [](const spv::ExecutionModel& model) {
738
121
                         switch (model) {
739
4
                           case spv::ExecutionModel::Geometry:
740
74
                           case spv::ExecutionModel::TessellationControl:
741
114
                           case spv::ExecutionModel::TessellationEvaluation:
742
114
                             return true;
743
7
                           default:
744
7
                             return false;
745
121
                         }
746
121
                       })) {
747
7
        return _.diag(SPV_ERROR_INVALID_DATA, inst)
748
7
               << "Execution mode can only be used with a Geometry or "
749
7
                  "tessellation execution model.";
750
7
      }
751
84
      break;
752
125
    case spv::ExecutionMode::OutputVertices:
753
125
      if (!std::all_of(
754
125
              models->begin(), models->end(),
755
125
              [&_](const spv::ExecutionModel& model) {
756
125
                switch (model) {
757
52
                  case spv::ExecutionModel::Geometry:
758
84
                  case spv::ExecutionModel::TessellationControl:
759
122
                  case spv::ExecutionModel::TessellationEvaluation:
760
122
                    return true;
761
0
                  case spv::ExecutionModel::MeshNV:
762
0
                    return _.HasCapability(spv::Capability::MeshShadingNV);
763
0
                  case spv::ExecutionModel::MeshEXT:
764
0
                    return _.HasCapability(spv::Capability::MeshShadingEXT);
765
3
                  default:
766
3
                    return false;
767
125
                }
768
125
              })) {
769
3
        if (_.HasCapability(spv::Capability::MeshShadingNV) ||
770
3
            _.HasCapability(spv::Capability::MeshShadingEXT)) {
771
0
          return _.diag(SPV_ERROR_INVALID_DATA, inst)
772
0
                 << "Execution mode can only be used with a Geometry, "
773
0
                    "tessellation, MeshNV or MeshEXT execution model.";
774
3
        } else {
775
3
          return _.diag(SPV_ERROR_INVALID_DATA, inst)
776
3
                 << "Execution mode can only be used with a Geometry or "
777
3
                    "tessellation execution model.";
778
3
        }
779
3
      }
780
122
      if (is_vulkan_env) {
781
0
        if (_.HasCapability(spv::Capability::MeshShadingEXT) &&
782
0
            inst->GetOperandAs<uint32_t>(2) == 0) {
783
0
          return _.diag(SPV_ERROR_INVALID_DATA, inst)
784
0
                 << _.VkErrorID(7330)
785
0
                 << "In mesh shaders using the MeshEXT Execution Model the "
786
0
                    "OutputVertices Execution Mode must be greater than 0";
787
0
        }
788
0
      }
789
122
      break;
790
122
    case spv::ExecutionMode::OutputLinesEXT:
791
0
    case spv::ExecutionMode::OutputTrianglesEXT:
792
0
    case spv::ExecutionMode::OutputPrimitivesEXT:
793
0
      if (!std::all_of(models->begin(), models->end(),
794
0
                       [](const spv::ExecutionModel& model) {
795
0
                         return (model == spv::ExecutionModel::MeshEXT ||
796
0
                                 model == spv::ExecutionModel::MeshNV);
797
0
                       })) {
798
0
        return _.diag(SPV_ERROR_INVALID_DATA, inst)
799
0
               << "Execution mode can only be used with the MeshEXT or MeshNV "
800
0
                  "execution "
801
0
                  "model.";
802
0
      }
803
0
      if (mode == spv::ExecutionMode::OutputPrimitivesEXT && is_vulkan_env) {
804
0
        if (_.HasCapability(spv::Capability::MeshShadingEXT) &&
805
0
            inst->GetOperandAs<uint32_t>(2) == 0) {
806
0
          return _.diag(SPV_ERROR_INVALID_DATA, inst)
807
0
                 << _.VkErrorID(7331)
808
0
                 << "In mesh shaders using the MeshEXT Execution Model the "
809
0
                    "OutputPrimitivesEXT Execution Mode must be greater than 0";
810
0
        }
811
0
      }
812
0
      break;
813
0
    case spv::ExecutionMode::QuadDerivativesKHR:
814
0
      if (!std::all_of(models->begin(), models->end(),
815
0
                       [](const spv::ExecutionModel& model) {
816
0
                         return (model == spv::ExecutionModel::Fragment ||
817
0
                                 model == spv::ExecutionModel::GLCompute);
818
0
                       })) {
819
0
        return _.diag(SPV_ERROR_INVALID_DATA, inst)
820
0
               << "Execution mode can only be used with the Fragment or "
821
0
                  "GLCompute execution model.";
822
0
      }
823
0
      break;
824
2.66k
    case spv::ExecutionMode::PixelCenterInteger:
825
20.5k
    case spv::ExecutionMode::OriginUpperLeft:
826
23.1k
    case spv::ExecutionMode::OriginLowerLeft:
827
24.0k
    case spv::ExecutionMode::EarlyFragmentTests:
828
24.2k
    case spv::ExecutionMode::DepthReplacing:
829
24.3k
    case spv::ExecutionMode::DepthGreater:
830
24.3k
    case spv::ExecutionMode::DepthLess:
831
25.7k
    case spv::ExecutionMode::DepthUnchanged:
832
25.7k
    case spv::ExecutionMode::StencilRefReplacingEXT:
833
25.7k
    case spv::ExecutionMode::NonCoherentColorAttachmentReadEXT:
834
25.7k
    case spv::ExecutionMode::NonCoherentDepthAttachmentReadEXT:
835
25.7k
    case spv::ExecutionMode::NonCoherentStencilAttachmentReadEXT:
836
25.7k
    case spv::ExecutionMode::PixelInterlockOrderedEXT:
837
25.7k
    case spv::ExecutionMode::PixelInterlockUnorderedEXT:
838
25.7k
    case spv::ExecutionMode::SampleInterlockOrderedEXT:
839
25.7k
    case spv::ExecutionMode::SampleInterlockUnorderedEXT:
840
25.7k
    case spv::ExecutionMode::ShadingRateInterlockOrderedEXT:
841
25.7k
    case spv::ExecutionMode::ShadingRateInterlockUnorderedEXT:
842
25.7k
    case spv::ExecutionMode::PostDepthCoverage:
843
25.7k
    case spv::ExecutionMode::EarlyAndLateFragmentTestsAMD:
844
25.7k
    case spv::ExecutionMode::StencilRefUnchangedFrontAMD:
845
25.7k
    case spv::ExecutionMode::StencilRefGreaterFrontAMD:
846
25.7k
    case spv::ExecutionMode::StencilRefLessFrontAMD:
847
25.7k
    case spv::ExecutionMode::StencilRefUnchangedBackAMD:
848
25.7k
    case spv::ExecutionMode::StencilRefGreaterBackAMD:
849
25.7k
    case spv::ExecutionMode::StencilRefLessBackAMD:
850
25.7k
    case spv::ExecutionMode::RequireFullQuadsKHR:
851
25.7k
      if (!std::all_of(models->begin(), models->end(),
852
25.7k
                       [](const spv::ExecutionModel& model) {
853
25.7k
                         return model == spv::ExecutionModel::Fragment;
854
25.7k
                       })) {
855
98
        return _.diag(SPV_ERROR_INVALID_DATA, inst)
856
98
               << "Execution mode can only be used with the Fragment execution "
857
98
                  "model.";
858
98
      }
859
25.6k
      break;
860
25.6k
    case spv::ExecutionMode::LocalSizeHint:
861
20
    case spv::ExecutionMode::VecTypeHint:
862
59
    case spv::ExecutionMode::ContractionOff:
863
59
    case spv::ExecutionMode::LocalSizeHintId:
864
59
      if (!std::all_of(models->begin(), models->end(),
865
59
                       [](const spv::ExecutionModel& model) {
866
59
                         return model == spv::ExecutionModel::Kernel;
867
59
                       })) {
868
0
        return _.diag(SPV_ERROR_INVALID_DATA, inst)
869
0
               << "Execution mode can only be used with the Kernel execution "
870
0
                  "model.";
871
0
      }
872
59
      break;
873
554
    case spv::ExecutionMode::LocalSize:
874
554
    case spv::ExecutionMode::LocalSizeId:
875
554
      if (mode == spv::ExecutionMode::LocalSizeId &&
876
0
          !_.IsLocalSizeIdAllowed()) {
877
0
        return _.diag(SPV_ERROR_INVALID_DATA, inst)
878
0
               << "LocalSizeId mode is not allowed by the current environment."
879
0
               << (is_vulkan_env
880
0
                       ? _.MissingFeature("maintenance4 feature",
881
0
                                          "--allow-localsizeid", false)
882
0
                       : "");
883
0
      }
884
885
554
      if (!std::all_of(
886
554
              models->begin(), models->end(),
887
554
              [&_](const spv::ExecutionModel& model) {
888
554
                switch (model) {
889
7
                  case spv::ExecutionModel::Kernel:
890
512
                  case spv::ExecutionModel::GLCompute:
891
512
                    return true;
892
0
                  case spv::ExecutionModel::TaskNV:
893
0
                  case spv::ExecutionModel::MeshNV:
894
0
                    return _.HasCapability(spv::Capability::MeshShadingNV);
895
0
                  case spv::ExecutionModel::TaskEXT:
896
0
                  case spv::ExecutionModel::MeshEXT:
897
0
                    return _.HasCapability(spv::Capability::MeshShadingEXT);
898
42
                  default:
899
42
                    return false;
900
554
                }
901
554
              })) {
902
42
        if (_.HasCapability(spv::Capability::MeshShadingNV) ||
903
42
            _.HasCapability(spv::Capability::MeshShadingEXT)) {
904
0
          return _.diag(SPV_ERROR_INVALID_DATA, inst)
905
0
                 << "Execution mode can only be used with a Kernel, GLCompute, "
906
0
                    "MeshNV, MeshEXT, TaskNV or TaskEXT execution model.";
907
42
        } else {
908
42
          return _.diag(SPV_ERROR_INVALID_DATA, inst)
909
42
                 << "Execution mode can only be used with a Kernel or "
910
42
                    "GLCompute "
911
42
                    "execution model.";
912
42
        }
913
42
      }
914
512
    default:
915
512
      break;
916
32.3k
  }
917
918
32.0k
  if (mode == spv::ExecutionMode::FPFastMathDefault) {
919
0
    const auto* modes = _.GetExecutionModes(entry_point_id);
920
0
    if (modes && modes->count(spv::ExecutionMode::ContractionOff)) {
921
0
      return _.diag(SPV_ERROR_INVALID_DATA, inst)
922
0
             << "FPFastMathDefault and ContractionOff execution modes cannot "
923
0
                "be applied to the same entry point";
924
0
    }
925
0
    if (modes && modes->count(spv::ExecutionMode::SignedZeroInfNanPreserve)) {
926
0
      return _.diag(SPV_ERROR_INVALID_DATA, inst)
927
0
             << "FPFastMathDefault and SignedZeroInfNanPreserve execution "
928
0
                "modes cannot be applied to the same entry point";
929
0
    }
930
0
  }
931
932
32.0k
  if (is_vulkan_env) {
933
0
    if (mode == spv::ExecutionMode::OriginLowerLeft) {
934
0
      return _.diag(SPV_ERROR_INVALID_DATA, inst)
935
0
             << _.VkErrorID(4653)
936
0
             << "In the Vulkan environment, the OriginLowerLeft execution mode "
937
0
                "must not be used.";
938
0
    }
939
0
    if (mode == spv::ExecutionMode::PixelCenterInteger) {
940
0
      return _.diag(SPV_ERROR_INVALID_DATA, inst)
941
0
             << _.VkErrorID(4654)
942
0
             << "In the Vulkan environment, the PixelCenterInteger execution "
943
0
                "mode must not be used.";
944
0
    }
945
0
    if (mode == spv::ExecutionMode::TileShadingRateQCOM) {
946
0
      const auto rateX = inst->GetOperandAs<int>(2);
947
0
      const auto rateY = inst->GetOperandAs<int>(3);
948
0
      if ((rateX & (rateX - 1)) != 0 || (rateY & (rateY - 1)) != 0)
949
0
        return _.diag(SPV_ERROR_INVALID_DATA, inst)
950
0
               << "The TileShadingRateQCOM execution mode's x and y values "
951
0
                  "must be powers of 2.";
952
0
    }
953
0
  }
954
955
32.0k
  return SPV_SUCCESS;
956
32.0k
}
957
958
spv_result_t ValidateMemoryModel(ValidationState_t& _,
959
37.6k
                                 const Instruction* inst) {
960
  // Already produced an error if multiple memory model instructions are
961
  // present.
962
37.6k
  if (_.memory_model() != spv::MemoryModel::VulkanKHR &&
963
37.6k
      _.HasCapability(spv::Capability::VulkanMemoryModelKHR)) {
964
0
    return _.diag(SPV_ERROR_INVALID_DATA, inst)
965
0
           << "VulkanMemoryModelKHR capability must only be specified if "
966
0
              "the VulkanKHR memory model is used.";
967
0
  }
968
969
37.6k
  if (spvIsOpenCLEnv(_.context()->target_env)) {
970
0
    if ((_.addressing_model() != spv::AddressingModel::Physical32) &&
971
0
        (_.addressing_model() != spv::AddressingModel::Physical64)) {
972
0
      return _.diag(SPV_ERROR_INVALID_DATA, inst)
973
0
             << "Addressing model must be Physical32 or Physical64 "
974
0
             << "in the OpenCL environment.";
975
0
    }
976
0
    if (_.memory_model() != spv::MemoryModel::OpenCL) {
977
0
      return _.diag(SPV_ERROR_INVALID_DATA, inst)
978
0
             << "Memory model must be OpenCL in the OpenCL environment.";
979
0
    }
980
0
  }
981
982
37.6k
  if (spvIsVulkanEnv(_.context()->target_env)) {
983
0
    if ((_.addressing_model() != spv::AddressingModel::Logical) &&
984
0
        (_.addressing_model() !=
985
0
         spv::AddressingModel::PhysicalStorageBuffer64)) {
986
0
      return _.diag(SPV_ERROR_INVALID_DATA, inst)
987
0
             << _.VkErrorID(4635)
988
0
             << "Addressing model must be Logical or PhysicalStorageBuffer64 "
989
0
             << "in the Vulkan environment.";
990
0
    }
991
0
  }
992
37.6k
  return SPV_SUCCESS;
993
37.6k
}
994
995
17.9k
bool PerEntryExecutionMode(spv::ExecutionMode mode) {
996
17.9k
  switch (mode) {
997
    // These execution modes can be specified multiple times per entry point.
998
0
    case spv::ExecutionMode::DenormPreserve:
999
0
    case spv::ExecutionMode::DenormFlushToZero:
1000
0
    case spv::ExecutionMode::SignedZeroInfNanPreserve:
1001
0
    case spv::ExecutionMode::RoundingModeRTE:
1002
0
    case spv::ExecutionMode::RoundingModeRTZ:
1003
0
    case spv::ExecutionMode::FPFastMathDefault:
1004
0
    case spv::ExecutionMode::RoundingModeRTPINTEL:
1005
0
    case spv::ExecutionMode::RoundingModeRTNINTEL:
1006
0
    case spv::ExecutionMode::FloatingPointModeALTINTEL:
1007
0
    case spv::ExecutionMode::FloatingPointModeIEEEINTEL:
1008
0
      return false;
1009
17.9k
    default:
1010
17.9k
      return true;
1011
17.9k
  }
1012
17.9k
}
1013
1014
50.3k
spv_result_t ValidateCapability(ValidationState_t& _, const Instruction* inst) {
1015
50.3k
  auto cap = inst->GetOperandAs<spv::Capability>(0);
1016
50.3k
  if (cap == spv::Capability::CooperativeMatrixKHR) {
1017
0
    if (_.HasCapability(spv::Capability::Shader) &&
1018
0
        !_.HasCapability(spv::Capability::VulkanMemoryModel)) {
1019
0
      return _.diag(SPV_ERROR_INVALID_CAPABILITY, inst)
1020
0
             << "If the Shader and CooperativeMatrixKHR capabilities are "
1021
0
                "declared, the VulkanMemoryModel capability must also be "
1022
0
                "declared";
1023
0
    }
1024
0
  }
1025
50.3k
  return SPV_SUCCESS;
1026
50.3k
}
1027
1028
}  // namespace
1029
1030
27.0k
spv_result_t ValidateFloatControls2(ValidationState_t& _) {
1031
27.0k
  std::unordered_set<uint32_t> fp_fast_math_default_entry_points;
1032
27.0k
  for (auto entry_point : _.entry_points()) {
1033
20.9k
    const auto* exec_modes = _.GetExecutionModes(entry_point);
1034
20.9k
    if (exec_modes &&
1035
17.6k
        exec_modes->count(spv::ExecutionMode::FPFastMathDefault)) {
1036
0
      fp_fast_math_default_entry_points.insert(entry_point);
1037
0
    }
1038
20.9k
  }
1039
1040
27.0k
  std::vector<std::pair<const Instruction*, spv::Decoration>> worklist;
1041
14.2M
  for (const auto& inst : _.ordered_instructions()) {
1042
14.2M
    if (inst.opcode() != spv::Op::OpDecorate) {
1043
13.7M
      continue;
1044
13.7M
    }
1045
1046
580k
    const auto decoration = inst.GetOperandAs<spv::Decoration>(1);
1047
580k
    const auto target_id = inst.GetOperandAs<uint32_t>(0);
1048
580k
    const auto target = _.FindDef(target_id);
1049
580k
    if (decoration == spv::Decoration::NoContraction) {
1050
28.8k
      worklist.push_back(std::make_pair(target, decoration));
1051
551k
    } else if (decoration == spv::Decoration::FPFastMathMode) {
1052
2.32k
      auto mask = inst.GetOperandAs<spv::FPFastMathModeMask>(2);
1053
2.32k
      if ((mask & spv::FPFastMathModeMask::Fast) !=
1054
2.32k
          spv::FPFastMathModeMask::MaskNone) {
1055
1.47k
        worklist.push_back(std::make_pair(target, decoration));
1056
1.47k
      }
1057
2.32k
    }
1058
580k
  }
1059
1060
27.0k
  std::unordered_set<const Instruction*> visited;
1061
1.60M
  while (!worklist.empty()) {
1062
1.58M
    const auto inst = worklist.back().first;
1063
1.58M
    const auto decoration = worklist.back().second;
1064
1.58M
    worklist.pop_back();
1065
1066
1.58M
    if (!visited.insert(inst).second) {
1067
29.7k
      continue;
1068
29.7k
    }
1069
1070
1.55M
    const auto function = inst->function();
1071
1.55M
    if (function) {
1072
5.02k
      const auto& entry_points = _.FunctionEntryPoints(function->id());
1073
5.02k
      for (auto entry_point : entry_points) {
1074
4.50k
        if (fp_fast_math_default_entry_points.count(entry_point)) {
1075
0
          const std::string dec = decoration == spv::Decoration::NoContraction
1076
0
                                      ? "NoContraction"
1077
0
                                      : "FPFastMathMode Fast";
1078
0
          return _.diag(SPV_ERROR_INVALID_DATA, inst)
1079
0
                 << dec
1080
0
                 << " cannot be used by an entry point with the "
1081
0
                    "FPFastMathDefault execution mode";
1082
0
        }
1083
4.50k
      }
1084
1.54M
    } else {
1085
1.55M
      for (const auto& pair : inst->uses()) {
1086
1.55M
        worklist.push_back(std::make_pair(pair.first, decoration));
1087
1.55M
      }
1088
1.54M
    }
1089
1.55M
  }
1090
1091
27.0k
  return SPV_SUCCESS;
1092
27.0k
}
1093
1094
14.6M
spv_result_t ModeSettingPass(ValidationState_t& _, const Instruction* inst) {
1095
14.6M
  switch (inst->opcode()) {
1096
24.2k
    case spv::Op::OpEntryPoint:
1097
24.2k
      if (auto error = ValidateEntryPoint(_, inst)) return error;
1098
24.1k
      break;
1099
32.3k
    case spv::Op::OpExecutionMode:
1100
32.3k
    case spv::Op::OpExecutionModeId:
1101
32.3k
      if (auto error = ValidateExecutionMode(_, inst)) return error;
1102
32.0k
      break;
1103
37.6k
    case spv::Op::OpMemoryModel:
1104
37.6k
      if (auto error = ValidateMemoryModel(_, inst)) return error;
1105
37.6k
      break;
1106
50.3k
    case spv::Op::OpCapability:
1107
50.3k
      if (auto error = ValidateCapability(_, inst)) return error;
1108
50.3k
      break;
1109
14.5M
    default:
1110
14.5M
      break;
1111
14.6M
  }
1112
14.6M
  return SPV_SUCCESS;
1113
14.6M
}
1114
1115
27.0k
spv_result_t ValidateDuplicateExecutionModes(ValidationState_t& _) {
1116
27.0k
  using PerEntryKey = std::tuple<spv::ExecutionMode, uint32_t>;
1117
27.0k
  using PerOperandKey = std::tuple<spv::ExecutionMode, uint32_t, uint32_t>;
1118
27.0k
  std::set<PerEntryKey> seen_per_entry;
1119
27.0k
  std::set<PerOperandKey> seen_per_operand;
1120
1121
27.0k
  const auto lookupMode = [](spv::ExecutionMode mode) -> std::string {
1122
340
    const spvtools::OperandDesc* desc = nullptr;
1123
340
    if (spvtools::LookupOperand(SPV_OPERAND_TYPE_EXECUTION_MODE,
1124
340
                                static_cast<uint32_t>(mode),
1125
340
                                &desc) == SPV_SUCCESS) {
1126
340
      return std::string(desc->name().data());
1127
340
    }
1128
0
    return "Unknown";
1129
340
  };
1130
1131
14.2M
  for (const auto& inst : _.ordered_instructions()) {
1132
14.2M
    if (inst.opcode() != spv::Op::OpExecutionMode &&
1133
14.2M
        inst.opcode() != spv::Op::OpExecutionModeId) {
1134
14.2M
      continue;
1135
14.2M
    }
1136
1137
17.9k
    const auto entry = inst.GetOperandAs<uint32_t>(0);
1138
17.9k
    const auto mode = inst.GetOperandAs<spv::ExecutionMode>(1);
1139
17.9k
    if (PerEntryExecutionMode(mode)) {
1140
17.9k
      if (!seen_per_entry.insert(std::make_tuple(mode, entry)).second) {
1141
340
        return _.diag(SPV_ERROR_INVALID_ID, &inst)
1142
340
               << lookupMode(mode)
1143
340
               << " execution mode must not be specified multiple times per "
1144
340
                  "entry point";
1145
340
      }
1146
17.9k
    } else {
1147
      // Execution modes allowed multiple times all take a single operand.
1148
0
      const auto operand = inst.GetOperandAs<uint32_t>(2);
1149
0
      if (!seen_per_operand.insert(std::make_tuple(mode, entry, operand))
1150
0
               .second) {
1151
0
        return _.diag(SPV_ERROR_INVALID_ID, &inst)
1152
0
               << lookupMode(mode)
1153
0
               << " execution mode must not be specified multiple times for "
1154
0
                  "the same entry point and operands";
1155
0
      }
1156
0
    }
1157
17.9k
  }
1158
1159
26.6k
  return SPV_SUCCESS;
1160
27.0k
}
1161
1162
}  // namespace val
1163
}  // namespace spvtools