1 // Copyright (c) 2018 Google LLC.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "source/val/validate_scopes.h"
16 
17 #include "source/diagnostic.h"
18 #include "source/spirv_target_env.h"
19 #include "source/val/instruction.h"
20 #include "source/val/validation_state.h"
21 
22 namespace spvtools {
23 namespace val {
24 
IsValidScope(uint32_t scope)25 bool IsValidScope(uint32_t scope) {
26   // Deliberately avoid a default case so we have to update the list when the
27   // scopes list changes.
28   switch (static_cast<SpvScope>(scope)) {
29     case SpvScopeCrossDevice:
30     case SpvScopeDevice:
31     case SpvScopeWorkgroup:
32     case SpvScopeSubgroup:
33     case SpvScopeInvocation:
34     case SpvScopeQueueFamilyKHR:
35     case SpvScopeShaderCallKHR:
36       return true;
37     case SpvScopeMax:
38       break;
39   }
40   return false;
41 }
42 
ValidateScope(ValidationState_t & _,const Instruction * inst,uint32_t scope)43 spv_result_t ValidateScope(ValidationState_t& _, const Instruction* inst,
44                            uint32_t scope) {
45   SpvOp opcode = inst->opcode();
46   bool is_int32 = false, is_const_int32 = false;
47   uint32_t value = 0;
48   std::tie(is_int32, is_const_int32, value) = _.EvalInt32IfConst(scope);
49 
50   if (!is_int32) {
51     return _.diag(SPV_ERROR_INVALID_DATA, inst)
52            << spvOpcodeString(opcode) << ": expected scope to be a 32-bit int";
53   }
54 
55   if (!is_const_int32) {
56     if (_.HasCapability(SpvCapabilityShader) &&
57         !_.HasCapability(SpvCapabilityCooperativeMatrixNV)) {
58       return _.diag(SPV_ERROR_INVALID_DATA, inst)
59              << "Scope ids must be OpConstant when Shader capability is "
60              << "present";
61     }
62     if (_.HasCapability(SpvCapabilityShader) &&
63         _.HasCapability(SpvCapabilityCooperativeMatrixNV) &&
64         !spvOpcodeIsConstant(_.GetIdOpcode(scope))) {
65       return _.diag(SPV_ERROR_INVALID_DATA, inst)
66              << "Scope ids must be constant or specialization constant when "
67              << "CooperativeMatrixNV capability is present";
68     }
69   }
70 
71   if (is_const_int32 && !IsValidScope(value)) {
72     return _.diag(SPV_ERROR_INVALID_DATA, inst)
73            << "Invalid scope value:\n " << _.Disassemble(*_.FindDef(scope));
74   }
75 
76   return SPV_SUCCESS;
77 }
78 
ValidateExecutionScope(ValidationState_t & _,const Instruction * inst,uint32_t scope)79 spv_result_t ValidateExecutionScope(ValidationState_t& _,
80                                     const Instruction* inst, uint32_t scope) {
81   SpvOp opcode = inst->opcode();
82   bool is_int32 = false, is_const_int32 = false;
83   uint32_t value = 0;
84   std::tie(is_int32, is_const_int32, value) = _.EvalInt32IfConst(scope);
85 
86   if (auto error = ValidateScope(_, inst, scope)) {
87     return error;
88   }
89 
90   if (!is_const_int32) {
91     return SPV_SUCCESS;
92   }
93 
94   // Vulkan specific rules
95   if (spvIsVulkanEnv(_.context()->target_env)) {
96     // Vulkan 1.1 specific rules
97     if (_.context()->target_env != SPV_ENV_VULKAN_1_0) {
98       // Scope for Non Uniform Group Operations must be limited to Subgroup
99       if (spvOpcodeIsNonUniformGroupOperation(opcode) &&
100           value != SpvScopeSubgroup) {
101         return _.diag(SPV_ERROR_INVALID_DATA, inst)
102                << _.VkErrorID(4642) << spvOpcodeString(opcode)
103                << ": in Vulkan environment Execution scope is limited to "
104                << "Subgroup";
105       }
106     }
107 
108     // OpControlBarrier must only use Subgroup execution scope for a subset of
109     // execution models.
110     if (opcode == SpvOpControlBarrier && value != SpvScopeSubgroup) {
111       std::string errorVUID = _.VkErrorID(4682);
112       _.function(inst->function()->id())
113           ->RegisterExecutionModelLimitation([errorVUID](
114                                                  SpvExecutionModel model,
115                                                  std::string* message) {
116             if (model == SpvExecutionModelFragment ||
117                 model == SpvExecutionModelVertex ||
118                 model == SpvExecutionModelGeometry ||
119                 model == SpvExecutionModelTessellationEvaluation ||
120                 model == SpvExecutionModelRayGenerationKHR ||
121                 model == SpvExecutionModelIntersectionKHR ||
122                 model == SpvExecutionModelAnyHitKHR ||
123                 model == SpvExecutionModelClosestHitKHR ||
124                 model == SpvExecutionModelMissKHR) {
125               if (message) {
126                 *message =
127                     errorVUID +
128                     "in Vulkan environment, OpControlBarrier execution scope "
129                     "must be Subgroup for Fragment, Vertex, Geometry, "
130                     "TessellationEvaluation, RayGeneration, Intersection, "
131                     "AnyHit, ClosestHit, and Miss execution models";
132               }
133               return false;
134             }
135             return true;
136           });
137     }
138 
139     // Only subset of execution models support Workgroup.
140     if (value == SpvScopeWorkgroup) {
141       std::string errorVUID = _.VkErrorID(4637);
142       _.function(inst->function()->id())
143           ->RegisterExecutionModelLimitation(
144               [errorVUID](SpvExecutionModel model, std::string* message) {
145                 if (model != SpvExecutionModelTaskNV &&
146                     model != SpvExecutionModelMeshNV &&
147                     model != SpvExecutionModelTessellationControl &&
148                     model != SpvExecutionModelGLCompute) {
149                   if (message) {
150                     *message =
151                         errorVUID +
152                         "in Vulkan environment, Workgroup execution scope is "
153                         "only for TaskNV, MeshNV, TessellationControl, and "
154                         "GLCompute execution models";
155                   }
156                   return false;
157                 }
158                 return true;
159               });
160     }
161 
162     // Vulkan generic rules
163     // Scope for execution must be limited to Workgroup or Subgroup
164     if (value != SpvScopeWorkgroup && value != SpvScopeSubgroup) {
165       return _.diag(SPV_ERROR_INVALID_DATA, inst)
166              << _.VkErrorID(4636) << spvOpcodeString(opcode)
167              << ": in Vulkan environment Execution Scope is limited to "
168              << "Workgroup and Subgroup";
169     }
170   }
171 
172   // TODO(atgoo@github.com) Add checks for OpenCL and OpenGL environments.
173 
174   // General SPIRV rules
175   // Scope for execution must be limited to Workgroup or Subgroup for
176   // non-uniform operations
177   if (spvOpcodeIsNonUniformGroupOperation(opcode) &&
178       value != SpvScopeSubgroup && value != SpvScopeWorkgroup) {
179     return _.diag(SPV_ERROR_INVALID_DATA, inst)
180            << spvOpcodeString(opcode)
181            << ": Execution scope is limited to Subgroup or Workgroup";
182   }
183 
184   return SPV_SUCCESS;
185 }
186 
ValidateMemoryScope(ValidationState_t & _,const Instruction * inst,uint32_t scope)187 spv_result_t ValidateMemoryScope(ValidationState_t& _, const Instruction* inst,
188                                  uint32_t scope) {
189   const SpvOp opcode = inst->opcode();
190   bool is_int32 = false, is_const_int32 = false;
191   uint32_t value = 0;
192   std::tie(is_int32, is_const_int32, value) = _.EvalInt32IfConst(scope);
193 
194   if (auto error = ValidateScope(_, inst, scope)) {
195     return error;
196   }
197 
198   if (!is_const_int32) {
199     return SPV_SUCCESS;
200   }
201 
202   if (value == SpvScopeQueueFamilyKHR) {
203     if (_.HasCapability(SpvCapabilityVulkanMemoryModelKHR)) {
204       return SPV_SUCCESS;
205     } else {
206       return _.diag(SPV_ERROR_INVALID_DATA, inst)
207              << spvOpcodeString(opcode)
208              << ": Memory Scope QueueFamilyKHR requires capability "
209              << "VulkanMemoryModelKHR";
210     }
211   }
212 
213   if (value == SpvScopeDevice &&
214       _.HasCapability(SpvCapabilityVulkanMemoryModelKHR) &&
215       !_.HasCapability(SpvCapabilityVulkanMemoryModelDeviceScopeKHR)) {
216     return _.diag(SPV_ERROR_INVALID_DATA, inst)
217            << "Use of device scope with VulkanKHR memory model requires the "
218            << "VulkanMemoryModelDeviceScopeKHR capability";
219   }
220 
221   // Vulkan Specific rules
222   if (spvIsVulkanEnv(_.context()->target_env)) {
223     if (value == SpvScopeCrossDevice) {
224       return _.diag(SPV_ERROR_INVALID_DATA, inst)
225              << _.VkErrorID(4638) << spvOpcodeString(opcode)
226              << ": in Vulkan environment, Memory Scope cannot be CrossDevice";
227     }
228     // Vulkan 1.0 specifc rules
229     if (_.context()->target_env == SPV_ENV_VULKAN_1_0 &&
230         value != SpvScopeDevice && value != SpvScopeWorkgroup &&
231         value != SpvScopeInvocation) {
232       return _.diag(SPV_ERROR_INVALID_DATA, inst)
233              << _.VkErrorID(4638) << spvOpcodeString(opcode)
234              << ": in Vulkan 1.0 environment Memory Scope is limited to "
235              << "Device, Workgroup and Invocation";
236     }
237     // Vulkan 1.1 specifc rules
238     if ((_.context()->target_env == SPV_ENV_VULKAN_1_1 ||
239          _.context()->target_env == SPV_ENV_VULKAN_1_2) &&
240         value != SpvScopeDevice && value != SpvScopeWorkgroup &&
241         value != SpvScopeSubgroup && value != SpvScopeInvocation &&
242         value != SpvScopeShaderCallKHR) {
243       return _.diag(SPV_ERROR_INVALID_DATA, inst)
244              << _.VkErrorID(4638) << spvOpcodeString(opcode)
245              << ": in Vulkan 1.1 and 1.2 environment Memory Scope is limited "
246              << "to Device, Workgroup, Invocation, and ShaderCall";
247     }
248 
249     if (value == SpvScopeShaderCallKHR) {
250       std::string errorVUID = _.VkErrorID(4640);
251       _.function(inst->function()->id())
252           ->RegisterExecutionModelLimitation(
253               [errorVUID](SpvExecutionModel model, std::string* message) {
254                 if (model != SpvExecutionModelRayGenerationKHR &&
255                     model != SpvExecutionModelIntersectionKHR &&
256                     model != SpvExecutionModelAnyHitKHR &&
257                     model != SpvExecutionModelClosestHitKHR &&
258                     model != SpvExecutionModelMissKHR &&
259                     model != SpvExecutionModelCallableKHR) {
260                   if (message) {
261                     *message =
262                         errorVUID +
263                         "ShaderCallKHR Memory Scope requires a ray tracing "
264                         "execution model";
265                   }
266                   return false;
267                 }
268                 return true;
269               });
270     }
271 
272     if (value == SpvScopeWorkgroup) {
273       std::string errorVUID = _.VkErrorID(4639);
274       _.function(inst->function()->id())
275           ->RegisterExecutionModelLimitation(
276               [errorVUID](SpvExecutionModel model, std::string* message) {
277                 if (model != SpvExecutionModelGLCompute &&
278                     model != SpvExecutionModelTaskNV &&
279                     model != SpvExecutionModelMeshNV) {
280                   if (message) {
281                     *message = errorVUID +
282                                "Workgroup Memory Scope is limited to MeshNV, "
283                                "TaskNV, and GLCompute execution model";
284                   }
285                   return false;
286                 }
287                 return true;
288               });
289     }
290   }
291 
292   // TODO(atgoo@github.com) Add checks for OpenCL and OpenGL environments.
293 
294   return SPV_SUCCESS;
295 }
296 
297 }  // namespace val
298 }  // namespace spvtools
299