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.h"
16
17#include <algorithm>
18
19#include "source/opcode.h"
20#include "source/spirv_target_env.h"
21#include "source/val/instruction.h"
22#include "source/val/validation_state.h"
23
24namespace spvtools {
25namespace val {
26namespace {
27
28spv_result_t ValidateEntryPoint(ValidationState_t& _, const Instruction* inst) {
29 const auto entry_point_id = inst->GetOperandAs<uint32_t>(1);
30 auto entry_point = _.FindDef(entry_point_id);
31 if (!entry_point || SpvOpFunction != entry_point->opcode()) {
32 return _.diag(SPV_ERROR_INVALID_ID, inst)
33 << "OpEntryPoint Entry Point <id> '" << _.getIdName(entry_point_id)
34 << "' is not a function.";
35 }
36
37 // Only check the shader execution models
38 const SpvExecutionModel execution_model =
39 inst->GetOperandAs<SpvExecutionModel>(0);
40 if (execution_model != SpvExecutionModelKernel) {
41 const auto entry_point_type_id = entry_point->GetOperandAs<uint32_t>(3);
42 const auto entry_point_type = _.FindDef(entry_point_type_id);
43 if (!entry_point_type || 3 != entry_point_type->words().size()) {
44 return _.diag(SPV_ERROR_INVALID_ID, inst)
45 << "OpEntryPoint Entry Point <id> '" << _.getIdName(entry_point_id)
46 << "'s function parameter count is not zero.";
47 }
48 }
49
50 auto return_type = _.FindDef(entry_point->type_id());
51 if (!return_type || SpvOpTypeVoid != return_type->opcode()) {
52 return _.diag(SPV_ERROR_INVALID_ID, inst)
53 << "OpEntryPoint Entry Point <id> '" << _.getIdName(entry_point_id)
54 << "'s function return type is not void.";
55 }
56
57 const auto* execution_modes = _.GetExecutionModes(entry_point_id);
58 if (_.HasCapability(SpvCapabilityShader)) {
59 switch (execution_model) {
60 case SpvExecutionModelFragment:
61 if (execution_modes &&
62 execution_modes->count(SpvExecutionModeOriginUpperLeft) &&
63 execution_modes->count(SpvExecutionModeOriginLowerLeft)) {
64 return _.diag(SPV_ERROR_INVALID_DATA, inst)
65 << "Fragment execution model entry points can only specify "
66 "one of OriginUpperLeft or OriginLowerLeft execution "
67 "modes.";
68 }
69 if (!execution_modes ||
70 (!execution_modes->count(SpvExecutionModeOriginUpperLeft) &&
71 !execution_modes->count(SpvExecutionModeOriginLowerLeft))) {
72 return _.diag(SPV_ERROR_INVALID_DATA, inst)
73 << "Fragment execution model entry points require either an "
74 "OriginUpperLeft or OriginLowerLeft execution mode.";
75 }
76 if (execution_modes &&
77 1 < std::count_if(execution_modes->begin(), execution_modes->end(),
78 [](const SpvExecutionMode& mode) {
79 switch (mode) {
80 case SpvExecutionModeDepthGreater:
81 case SpvExecutionModeDepthLess:
82 case SpvExecutionModeDepthUnchanged:
83 return true;
84 default:
85 return false;
86 }
87 })) {
88 return _.diag(SPV_ERROR_INVALID_DATA, inst)
89 << "Fragment execution model entry points can specify at most "
90 "one of DepthGreater, DepthLess or DepthUnchanged "
91 "execution modes.";
92 }
93 if (execution_modes &&
94 1 < std::count_if(
95 execution_modes->begin(), execution_modes->end(),
96 [](const SpvExecutionMode& mode) {
97 switch (mode) {
98 case SpvExecutionModePixelInterlockOrderedEXT:
99 case SpvExecutionModePixelInterlockUnorderedEXT:
100 case SpvExecutionModeSampleInterlockOrderedEXT:
101 case SpvExecutionModeSampleInterlockUnorderedEXT:
102 case SpvExecutionModeShadingRateInterlockOrderedEXT:
103 case SpvExecutionModeShadingRateInterlockUnorderedEXT:
104 return true;
105 default:
106 return false;
107 }
108 })) {
109 return _.diag(SPV_ERROR_INVALID_DATA, inst)
110 << "Fragment execution model entry points can specify at most "
111 "one fragment shader interlock execution mode.";
112 }
113 break;
114 case SpvExecutionModelTessellationControl:
115 case SpvExecutionModelTessellationEvaluation:
116 if (execution_modes &&
117 1 < std::count_if(execution_modes->begin(), execution_modes->end(),
118 [](const SpvExecutionMode& mode) {
119 switch (mode) {
120 case SpvExecutionModeSpacingEqual:
121 case SpvExecutionModeSpacingFractionalEven:
122 case SpvExecutionModeSpacingFractionalOdd:
123 return true;
124 default:
125 return false;
126 }
127 })) {
128 return _.diag(SPV_ERROR_INVALID_DATA, inst)
129 << "Tessellation execution model entry points can specify at "
130 "most one of SpacingEqual, SpacingFractionalOdd or "
131 "SpacingFractionalEven execution modes.";
132 }
133 if (execution_modes &&
134 1 < std::count_if(execution_modes->begin(), execution_modes->end(),
135 [](const SpvExecutionMode& mode) {
136 switch (mode) {
137 case SpvExecutionModeTriangles:
138 case SpvExecutionModeQuads:
139 case SpvExecutionModeIsolines:
140 return true;
141 default:
142 return false;
143 }
144 })) {
145 return _.diag(SPV_ERROR_INVALID_DATA, inst)
146 << "Tessellation execution model entry points can specify at "
147 "most one of Triangles, Quads or Isolines execution modes.";
148 }
149 if (execution_modes &&
150 1 < std::count_if(execution_modes->begin(), execution_modes->end(),
151 [](const SpvExecutionMode& mode) {
152 switch (mode) {
153 case SpvExecutionModeVertexOrderCw:
154 case SpvExecutionModeVertexOrderCcw:
155 return true;
156 default:
157 return false;
158 }
159 })) {
160 return _.diag(SPV_ERROR_INVALID_DATA, inst)
161 << "Tessellation execution model entry points can specify at "
162 "most one of VertexOrderCw or VertexOrderCcw execution "
163 "modes.";
164 }
165 break;
166 case SpvExecutionModelGeometry:
167 if (!execution_modes ||
168 1 != std::count_if(execution_modes->begin(), execution_modes->end(),
169 [](const SpvExecutionMode& mode) {
170 switch (mode) {
171 case SpvExecutionModeInputPoints:
172 case SpvExecutionModeInputLines:
173 case SpvExecutionModeInputLinesAdjacency:
174 case SpvExecutionModeTriangles:
175 case SpvExecutionModeInputTrianglesAdjacency:
176 return true;
177 default:
178 return false;
179 }
180 })) {
181 return _.diag(SPV_ERROR_INVALID_DATA, inst)
182 << "Geometry execution model entry points must specify "
183 "exactly one of InputPoints, InputLines, "
184 "InputLinesAdjacency, Triangles or InputTrianglesAdjacency "
185 "execution modes.";
186 }
187 if (!execution_modes ||
188 1 != std::count_if(execution_modes->begin(), execution_modes->end(),
189 [](const SpvExecutionMode& mode) {
190 switch (mode) {
191 case SpvExecutionModeOutputPoints:
192 case SpvExecutionModeOutputLineStrip:
193 case SpvExecutionModeOutputTriangleStrip:
194 return true;
195 default:
196 return false;
197 }
198 })) {
199 return _.diag(SPV_ERROR_INVALID_DATA, inst)
200 << "Geometry execution model entry points must specify "
201 "exactly one of OutputPoints, OutputLineStrip or "
202 "OutputTriangleStrip execution modes.";
203 }
204 break;
205 default:
206 break;
207 }
208 }
209
210 if (spvIsVulkanEnv(_.context()->target_env)) {
211 switch (execution_model) {
212 case SpvExecutionModelGLCompute:
213 if (!execution_modes ||
214 !execution_modes->count(SpvExecutionModeLocalSize)) {
215 bool ok = false;
216 for (auto& i : _.ordered_instructions()) {
217 if (i.opcode() == SpvOpDecorate) {
218 if (i.operands().size() > 2) {
219 if (i.GetOperandAs<SpvDecoration>(1) == SpvDecorationBuiltIn &&
220 i.GetOperandAs<SpvBuiltIn>(2) == SpvBuiltInWorkgroupSize) {
221 ok = true;
222 break;
223 }
224 }
225 }
226 }
227 if (!ok) {
228 return _.diag(SPV_ERROR_INVALID_DATA, inst)
229 << "In the Vulkan environment, GLCompute execution model "
230 "entry points require either the LocalSize execution "
231 "mode or an object decorated with WorkgroupSize must be "
232 "specified.";
233 }
234 }
235 break;
236 default:
237 break;
238 }
239 }
240
241 return SPV_SUCCESS;
242}
243
244spv_result_t ValidateExecutionMode(ValidationState_t& _,
245 const Instruction* inst) {
246 const auto entry_point_id = inst->GetOperandAs<uint32_t>(0);
247 const auto found = std::find(_.entry_points().cbegin(),
248 _.entry_points().cend(), entry_point_id);
249 if (found == _.entry_points().cend()) {
250 return _.diag(SPV_ERROR_INVALID_ID, inst)
251 << "OpExecutionMode Entry Point <id> '"
252 << _.getIdName(entry_point_id)
253 << "' is not the Entry Point "
254 "operand of an OpEntryPoint.";
255 }
256
257 const auto mode = inst->GetOperandAs<SpvExecutionMode>(1);
258 if (inst->opcode() == SpvOpExecutionModeId) {
259 size_t operand_count = inst->operands().size();
260 for (size_t i = 2; i < operand_count; ++i) {
261 const auto operand_id = inst->GetOperandAs<uint32_t>(2);
262 const auto* operand_inst = _.FindDef(operand_id);
263 if (mode == SpvExecutionModeSubgroupsPerWorkgroupId ||
264 mode == SpvExecutionModeLocalSizeHintId ||
265 mode == SpvExecutionModeLocalSizeId) {
266 if (!spvOpcodeIsConstant(operand_inst->opcode())) {
267 return _.diag(SPV_ERROR_INVALID_ID, inst)
268 << "For OpExecutionModeId all Extra Operand ids must be "
269 "constant "
270 "instructions.";
271 }
272 } else {
273 return _.diag(SPV_ERROR_INVALID_ID, inst)
274 << "OpExecutionModeId is only valid when the Mode operand is an "
275 "execution mode that takes Extra Operands that are id "
276 "operands.";
277 }
278 }
279 } else if (mode == SpvExecutionModeSubgroupsPerWorkgroupId ||
280 mode == SpvExecutionModeLocalSizeHintId ||
281 mode == SpvExecutionModeLocalSizeId) {
282 return _.diag(SPV_ERROR_INVALID_DATA, inst)
283 << "OpExecutionMode is only valid when the Mode operand is an "
284 "execution mode that takes no Extra Operands, or takes Extra "
285 "Operands that are not id operands.";
286 }
287
288 const auto* models = _.GetExecutionModels(entry_point_id);
289 switch (mode) {
290 case SpvExecutionModeInvocations:
291 case SpvExecutionModeInputPoints:
292 case SpvExecutionModeInputLines:
293 case SpvExecutionModeInputLinesAdjacency:
294 case SpvExecutionModeInputTrianglesAdjacency:
295 case SpvExecutionModeOutputLineStrip:
296 case SpvExecutionModeOutputTriangleStrip:
297 if (!std::all_of(models->begin(), models->end(),
298 [](const SpvExecutionModel& model) {
299 return model == SpvExecutionModelGeometry;
300 })) {
301 return _.diag(SPV_ERROR_INVALID_DATA, inst)
302 << "Execution mode can only be used with the Geometry execution "
303 "model.";
304 }
305 break;
306 case SpvExecutionModeOutputPoints:
307 if (!std::all_of(models->begin(), models->end(),
308 [&_](const SpvExecutionModel& model) {
309 switch (model) {
310 case SpvExecutionModelGeometry:
311 return true;
312 case SpvExecutionModelMeshNV:
313 return _.HasCapability(SpvCapabilityMeshShadingNV);
314 default:
315 return false;
316 }
317 })) {
318 if (_.HasCapability(SpvCapabilityMeshShadingNV)) {
319 return _.diag(SPV_ERROR_INVALID_DATA, inst)
320 << "Execution mode can only be used with the Geometry or "
321 "MeshNV execution model.";
322 } else {
323 return _.diag(SPV_ERROR_INVALID_DATA, inst)
324 << "Execution mode can only be used with the Geometry "
325 "execution "
326 "model.";
327 }
328 }
329 break;
330 case SpvExecutionModeSpacingEqual:
331 case SpvExecutionModeSpacingFractionalEven:
332 case SpvExecutionModeSpacingFractionalOdd:
333 case SpvExecutionModeVertexOrderCw:
334 case SpvExecutionModeVertexOrderCcw:
335 case SpvExecutionModePointMode:
336 case SpvExecutionModeQuads:
337 case SpvExecutionModeIsolines:
338 if (!std::all_of(
339 models->begin(), models->end(),
340 [](const SpvExecutionModel& model) {
341 return (model == SpvExecutionModelTessellationControl) ||
342 (model == SpvExecutionModelTessellationEvaluation);
343 })) {
344 return _.diag(SPV_ERROR_INVALID_DATA, inst)
345 << "Execution mode can only be used with a tessellation "
346 "execution model.";
347 }
348 break;
349 case SpvExecutionModeTriangles:
350 if (!std::all_of(models->begin(), models->end(),
351 [](const SpvExecutionModel& model) {
352 switch (model) {
353 case SpvExecutionModelGeometry:
354 case SpvExecutionModelTessellationControl:
355 case SpvExecutionModelTessellationEvaluation:
356 return true;
357 default:
358 return false;
359 }
360 })) {
361 return _.diag(SPV_ERROR_INVALID_DATA, inst)
362 << "Execution mode can only be used with a Geometry or "
363 "tessellation execution model.";
364 }
365 break;
366 case SpvExecutionModeOutputVertices:
367 if (!std::all_of(models->begin(), models->end(),
368 [&_](const SpvExecutionModel& model) {
369 switch (model) {
370 case SpvExecutionModelGeometry:
371 case SpvExecutionModelTessellationControl:
372 case SpvExecutionModelTessellationEvaluation:
373 return true;
374 case SpvExecutionModelMeshNV:
375 return _.HasCapability(SpvCapabilityMeshShadingNV);
376 default:
377 return false;
378 }
379 })) {
380 if (_.HasCapability(SpvCapabilityMeshShadingNV)) {
381 return _.diag(SPV_ERROR_INVALID_DATA, inst)
382 << "Execution mode can only be used with a Geometry, "
383 "tessellation or MeshNV execution model.";
384 } else {
385 return _.diag(SPV_ERROR_INVALID_DATA, inst)
386 << "Execution mode can only be used with a Geometry or "
387 "tessellation execution model.";
388 }
389 }
390 break;
391 case SpvExecutionModePixelCenterInteger:
392 case SpvExecutionModeOriginUpperLeft:
393 case SpvExecutionModeOriginLowerLeft:
394 case SpvExecutionModeEarlyFragmentTests:
395 case SpvExecutionModeDepthReplacing:
396 case SpvExecutionModeDepthGreater:
397 case SpvExecutionModeDepthLess:
398 case SpvExecutionModeDepthUnchanged:
399 case SpvExecutionModePixelInterlockOrderedEXT:
400 case SpvExecutionModePixelInterlockUnorderedEXT:
401 case SpvExecutionModeSampleInterlockOrderedEXT:
402 case SpvExecutionModeSampleInterlockUnorderedEXT:
403 case SpvExecutionModeShadingRateInterlockOrderedEXT:
404 case SpvExecutionModeShadingRateInterlockUnorderedEXT:
405 if (!std::all_of(models->begin(), models->end(),
406 [](const SpvExecutionModel& model) {
407 return model == SpvExecutionModelFragment;
408 })) {
409 return _.diag(SPV_ERROR_INVALID_DATA, inst)
410 << "Execution mode can only be used with the Fragment execution "
411 "model.";
412 }
413 break;
414 case SpvExecutionModeLocalSizeHint:
415 case SpvExecutionModeVecTypeHint:
416 case SpvExecutionModeContractionOff:
417 case SpvExecutionModeLocalSizeHintId:
418 if (!std::all_of(models->begin(), models->end(),
419 [](const SpvExecutionModel& model) {
420 return model == SpvExecutionModelKernel;
421 })) {
422 return _.diag(SPV_ERROR_INVALID_DATA, inst)
423 << "Execution mode can only be used with the Kernel execution "
424 "model.";
425 }
426 break;
427 case SpvExecutionModeLocalSize:
428 case SpvExecutionModeLocalSizeId:
429 if (!std::all_of(models->begin(), models->end(),
430 [&_](const SpvExecutionModel& model) {
431 switch (model) {
432 case SpvExecutionModelKernel:
433 case SpvExecutionModelGLCompute:
434 return true;
435 case SpvExecutionModelTaskNV:
436 case SpvExecutionModelMeshNV:
437 return _.HasCapability(SpvCapabilityMeshShadingNV);
438 default:
439 return false;
440 }
441 })) {
442 if (_.HasCapability(SpvCapabilityMeshShadingNV)) {
443 return _.diag(SPV_ERROR_INVALID_DATA, inst)
444 << "Execution mode can only be used with a Kernel, GLCompute, "
445 "MeshNV, or TaskNV execution model.";
446 } else {
447 return _.diag(SPV_ERROR_INVALID_DATA, inst)
448 << "Execution mode can only be used with a Kernel or "
449 "GLCompute "
450 "execution model.";
451 }
452 }
453 default:
454 break;
455 }
456
457 if (spvIsVulkanEnv(_.context()->target_env)) {
458 if (mode == SpvExecutionModeOriginLowerLeft) {
459 return _.diag(SPV_ERROR_INVALID_DATA, inst)
460 << "In the Vulkan environment, the OriginLowerLeft execution mode "
461 "must not be used.";
462 }
463 if (mode == SpvExecutionModePixelCenterInteger) {
464 return _.diag(SPV_ERROR_INVALID_DATA, inst)
465 << "In the Vulkan environment, the PixelCenterInteger execution "
466 "mode must not be used.";
467 }
468 }
469
470 if (spvIsWebGPUEnv(_.context()->target_env)) {
471 if (mode != SpvExecutionModeOriginUpperLeft &&
472 mode != SpvExecutionModeDepthReplacing &&
473 mode != SpvExecutionModeDepthGreater &&
474 mode != SpvExecutionModeDepthLess &&
475 mode != SpvExecutionModeDepthUnchanged &&
476 mode != SpvExecutionModeLocalSize &&
477 mode != SpvExecutionModeLocalSizeHint) {
478 return _.diag(SPV_ERROR_INVALID_DATA, inst)
479 << "Execution mode must be one of OriginUpperLeft, "
480 "DepthReplacing, DepthGreater, DepthLess, DepthUnchanged, "
481 "LocalSize, or LocalSizeHint for WebGPU environment.";
482 }
483 }
484
485 return SPV_SUCCESS;
486}
487
488spv_result_t ValidateMemoryModel(ValidationState_t& _,
489 const Instruction* inst) {
490 // Already produced an error if multiple memory model instructions are
491 // present.
492 if (_.memory_model() != SpvMemoryModelVulkanKHR &&
493 _.HasCapability(SpvCapabilityVulkanMemoryModelKHR)) {
494 return _.diag(SPV_ERROR_INVALID_DATA, inst)
495 << "VulkanMemoryModelKHR capability must only be specified if "
496 "the VulkanKHR memory model is used.";
497 }
498
499 if (spvIsWebGPUEnv(_.context()->target_env)) {
500 if (_.addressing_model() != SpvAddressingModelLogical) {
501 return _.diag(SPV_ERROR_INVALID_DATA, inst)
502 << "Addressing model must be Logical for WebGPU environment.";
503 }
504 if (_.memory_model() != SpvMemoryModelVulkanKHR) {
505 return _.diag(SPV_ERROR_INVALID_DATA, inst)
506 << "Memory model must be VulkanKHR for WebGPU environment.";
507 }
508 }
509
510 if (spvIsOpenCLEnv(_.context()->target_env)) {
511 if ((_.addressing_model() != SpvAddressingModelPhysical32) &&
512 (_.addressing_model() != SpvAddressingModelPhysical64)) {
513 return _.diag(SPV_ERROR_INVALID_DATA, inst)
514 << "Addressing model must be Physical32 or Physical64 "
515 << "in the OpenCL environment.";
516 }
517 if (_.memory_model() != SpvMemoryModelOpenCL) {
518 return _.diag(SPV_ERROR_INVALID_DATA, inst)
519 << "Memory model must be OpenCL in the OpenCL environment.";
520 }
521 }
522
523 return SPV_SUCCESS;
524}
525
526} // namespace
527
528spv_result_t ModeSettingPass(ValidationState_t& _, const Instruction* inst) {
529 switch (inst->opcode()) {
530 case SpvOpEntryPoint:
531 if (auto error = ValidateEntryPoint(_, inst)) return error;
532 break;
533 case SpvOpExecutionMode:
534 case SpvOpExecutionModeId:
535 if (auto error = ValidateExecutionMode(_, inst)) return error;
536 break;
537 case SpvOpMemoryModel:
538 if (auto error = ValidateMemoryModel(_, inst)) return error;
539 break;
540 default:
541 break;
542 }
543 return SPV_SUCCESS;
544}
545
546} // namespace val
547} // namespace spvtools
548