/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 |