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                << spvOpcodeString(opcode)
103                << ": in Vulkan environment Execution scope is limited to "
104                << "Subgroup";
105       }
106     }
107 
108     // If OpControlBarrier is used in fragment, vertex, tessellation evaluation,
109     // or geometry stages, the execution Scope must be Subgroup.
110     if (opcode == SpvOpControlBarrier && value != SpvScopeSubgroup) {
111       _.function(inst->function()->id())
112           ->RegisterExecutionModelLimitation([](SpvExecutionModel model,
113                                                 std::string* message) {
114             if (model == SpvExecutionModelFragment ||
115                 model == SpvExecutionModelVertex ||
116                 model == SpvExecutionModelGeometry ||
117                 model == SpvExecutionModelTessellationEvaluation) {
118               if (message) {
119                 *message =
120                     "in Vulkan evironment, OpControlBarrier execution scope "
121                     "must be Subgroup for Fragment, Vertex, Geometry and "
122                     "TessellationEvaluation execution models";
123               }
124               return false;
125             }
126             return true;
127           });
128     }
129 
130     // Vulkan generic rules
131     // Scope for execution must be limited to Workgroup or Subgroup
132     if (value != SpvScopeWorkgroup && value != SpvScopeSubgroup) {
133       return _.diag(SPV_ERROR_INVALID_DATA, inst)
134              << spvOpcodeString(opcode)
135              << ": in Vulkan environment Execution Scope is limited to "
136              << "Workgroup and Subgroup";
137     }
138   }
139 
140   // WebGPU Specific rules
141   if (spvIsWebGPUEnv(_.context()->target_env)) {
142     if (value != SpvScopeWorkgroup) {
143       return _.diag(SPV_ERROR_INVALID_DATA, inst)
144              << spvOpcodeString(opcode)
145              << ": in WebGPU environment Execution Scope is limited to "
146              << "Workgroup";
147     } else {
148       _.function(inst->function()->id())
149           ->RegisterExecutionModelLimitation(
150               [](SpvExecutionModel model, std::string* message) {
151                 if (model != SpvExecutionModelGLCompute) {
152                   if (message) {
153                     *message =
154                         ": in WebGPU environment, Workgroup Execution Scope is "
155                         "limited to GLCompute execution model";
156                   }
157                   return false;
158                 }
159                 return true;
160               });
161     }
162   }
163 
164   // TODO(atgoo@github.com) Add checks for OpenCL and OpenGL environments.
165 
166   // General SPIRV rules
167   // Scope for execution must be limited to Workgroup or Subgroup for
168   // non-uniform operations
169   if (spvOpcodeIsNonUniformGroupOperation(opcode) &&
170       value != SpvScopeSubgroup && value != SpvScopeWorkgroup) {
171     return _.diag(SPV_ERROR_INVALID_DATA, inst)
172            << spvOpcodeString(opcode)
173            << ": Execution scope is limited to Subgroup or Workgroup";
174   }
175 
176   return SPV_SUCCESS;
177 }
178 
ValidateMemoryScope(ValidationState_t & _,const Instruction * inst,uint32_t scope)179 spv_result_t ValidateMemoryScope(ValidationState_t& _, const Instruction* inst,
180                                  uint32_t scope) {
181   const SpvOp opcode = inst->opcode();
182   bool is_int32 = false, is_const_int32 = false;
183   uint32_t value = 0;
184   std::tie(is_int32, is_const_int32, value) = _.EvalInt32IfConst(scope);
185 
186   if (auto error = ValidateScope(_, inst, scope)) {
187     return error;
188   }
189 
190   if (!is_const_int32) {
191     return SPV_SUCCESS;
192   }
193 
194   if (value == SpvScopeQueueFamilyKHR) {
195     if (_.HasCapability(SpvCapabilityVulkanMemoryModelKHR)) {
196       return SPV_SUCCESS;
197     } else {
198       return _.diag(SPV_ERROR_INVALID_DATA, inst)
199              << spvOpcodeString(opcode)
200              << ": Memory Scope QueueFamilyKHR requires capability "
201              << "VulkanMemoryModelKHR";
202     }
203   }
204 
205   if (value == SpvScopeDevice &&
206       _.HasCapability(SpvCapabilityVulkanMemoryModelKHR) &&
207       !_.HasCapability(SpvCapabilityVulkanMemoryModelDeviceScopeKHR)) {
208     return _.diag(SPV_ERROR_INVALID_DATA, inst)
209            << "Use of device scope with VulkanKHR memory model requires the "
210            << "VulkanMemoryModelDeviceScopeKHR capability";
211   }
212 
213   // Vulkan Specific rules
214   if (spvIsVulkanEnv(_.context()->target_env)) {
215     if (value == SpvScopeCrossDevice) {
216       return _.diag(SPV_ERROR_INVALID_DATA, inst)
217              << spvOpcodeString(opcode)
218              << ": in Vulkan environment, Memory Scope cannot be CrossDevice";
219     }
220     // Vulkan 1.0 specifc rules
221     if (_.context()->target_env == SPV_ENV_VULKAN_1_0 &&
222         value != SpvScopeDevice && value != SpvScopeWorkgroup &&
223         value != SpvScopeInvocation) {
224       return _.diag(SPV_ERROR_INVALID_DATA, inst)
225              << spvOpcodeString(opcode)
226              << ": in Vulkan 1.0 environment Memory Scope is limited to "
227              << "Device, Workgroup and Invocation";
228     }
229     // Vulkan 1.1 specifc rules
230     if ((_.context()->target_env == SPV_ENV_VULKAN_1_1 ||
231          _.context()->target_env == SPV_ENV_VULKAN_1_2) &&
232         value != SpvScopeDevice && value != SpvScopeWorkgroup &&
233         value != SpvScopeSubgroup && value != SpvScopeInvocation &&
234         value != SpvScopeShaderCallKHR) {
235       return _.diag(SPV_ERROR_INVALID_DATA, inst)
236              << spvOpcodeString(opcode)
237              << ": in Vulkan 1.1 and 1.2 environment Memory Scope is limited "
238              << "to Device, Workgroup, Invocation, and ShaderCall";
239     }
240 
241     if (value == SpvScopeShaderCallKHR) {
242       _.function(inst->function()->id())
243           ->RegisterExecutionModelLimitation(
244               [](SpvExecutionModel model, std::string* message) {
245                 if (model != SpvExecutionModelRayGenerationKHR &&
246                     model != SpvExecutionModelIntersectionKHR &&
247                     model != SpvExecutionModelAnyHitKHR &&
248                     model != SpvExecutionModelClosestHitKHR &&
249                     model != SpvExecutionModelMissKHR &&
250                     model != SpvExecutionModelCallableKHR) {
251                   if (message) {
252                     *message =
253                         "ShaderCallKHR Memory Scope requires a ray tracing "
254                         "execution model";
255                   }
256                   return false;
257                 }
258                 return true;
259               });
260     }
261   }
262 
263   // WebGPU specific rules
264   if (spvIsWebGPUEnv(_.context()->target_env)) {
265     switch (inst->opcode()) {
266       case SpvOpControlBarrier:
267         if (value != SpvScopeWorkgroup) {
268           return _.diag(SPV_ERROR_INVALID_DATA, inst)
269                  << spvOpcodeString(opcode)
270                  << ": in WebGPU environment Memory Scope is limited to "
271                  << "Workgroup for OpControlBarrier";
272         }
273         break;
274       case SpvOpMemoryBarrier:
275         if (value != SpvScopeWorkgroup) {
276           return _.diag(SPV_ERROR_INVALID_DATA, inst)
277                  << spvOpcodeString(opcode)
278                  << ": in WebGPU environment Memory Scope is limited to "
279                  << "Workgroup for OpMemoryBarrier";
280         }
281         break;
282       default:
283         if (spvOpcodeIsAtomicOp(inst->opcode())) {
284           if (value != SpvScopeQueueFamilyKHR) {
285             return _.diag(SPV_ERROR_INVALID_DATA, inst)
286                    << spvOpcodeString(opcode)
287                    << ": in WebGPU environment Memory Scope is limited to "
288                    << "QueueFamilyKHR for OpAtomic* operations";
289           }
290         }
291 
292         if (value != SpvScopeWorkgroup && value != SpvScopeInvocation &&
293             value != SpvScopeQueueFamilyKHR) {
294           return _.diag(SPV_ERROR_INVALID_DATA, inst)
295                  << spvOpcodeString(opcode)
296                  << ": in WebGPU environment Memory Scope is limited to "
297                  << "Workgroup, Invocation, and QueueFamilyKHR";
298         }
299         break;
300     }
301 
302     if (value == SpvScopeWorkgroup) {
303       _.function(inst->function()->id())
304           ->RegisterExecutionModelLimitation(
305               [](SpvExecutionModel model, std::string* message) {
306                 if (model != SpvExecutionModelGLCompute) {
307                   if (message) {
308                     *message =
309                         ": in WebGPU environment, Workgroup Memory Scope is "
310                         "limited to GLCompute execution model";
311                   }
312                   return false;
313                 }
314                 return true;
315               });
316     }
317   }
318 
319   // TODO(atgoo@github.com) Add checks for OpenCL and OpenGL environments.
320 
321   return SPV_SUCCESS;
322 }
323 
324 }  // namespace val
325 }  // namespace spvtools
326