• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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       return true;
36     case SpvScopeMax:
37       break;
38   }
39   return false;
40 }
41 
ValidateScope(ValidationState_t & _,const Instruction * inst,uint32_t scope)42 spv_result_t ValidateScope(ValidationState_t& _, const Instruction* inst,
43                            uint32_t scope) {
44   SpvOp opcode = inst->opcode();
45   bool is_int32 = false, is_const_int32 = false;
46   uint32_t value = 0;
47   std::tie(is_int32, is_const_int32, value) = _.EvalInt32IfConst(scope);
48 
49   if (!is_int32) {
50     return _.diag(SPV_ERROR_INVALID_DATA, inst)
51            << spvOpcodeString(opcode) << ": expected scope to be a 32-bit int";
52   }
53 
54   if (!is_const_int32) {
55     if (_.HasCapability(SpvCapabilityShader) &&
56         !_.HasCapability(SpvCapabilityCooperativeMatrixNV)) {
57       return _.diag(SPV_ERROR_INVALID_DATA, inst)
58              << "Scope ids must be OpConstant when Shader capability is "
59              << "present";
60     }
61     if (_.HasCapability(SpvCapabilityShader) &&
62         _.HasCapability(SpvCapabilityCooperativeMatrixNV) &&
63         !spvOpcodeIsConstant(_.GetIdOpcode(scope))) {
64       return _.diag(SPV_ERROR_INVALID_DATA, inst)
65              << "Scope ids must be constant or specialization constant when "
66              << "CooperativeMatrixNV capability is present";
67     }
68   }
69 
70   if (is_const_int32 && !IsValidScope(value)) {
71     return _.diag(SPV_ERROR_INVALID_DATA, inst)
72            << "Invalid scope value:\n " << _.Disassemble(*_.FindDef(scope));
73   }
74 
75   return SPV_SUCCESS;
76 }
77 
ValidateExecutionScope(ValidationState_t & _,const Instruction * inst,uint32_t scope)78 spv_result_t ValidateExecutionScope(ValidationState_t& _,
79                                     const Instruction* inst, uint32_t scope) {
80   SpvOp opcode = inst->opcode();
81   bool is_int32 = false, is_const_int32 = false;
82   uint32_t value = 0;
83   std::tie(is_int32, is_const_int32, value) = _.EvalInt32IfConst(scope);
84 
85   if (auto error = ValidateScope(_, inst, scope)) {
86     return error;
87   }
88 
89   if (!is_const_int32) {
90     return SPV_SUCCESS;
91   }
92 
93   // Vulkan specific rules
94   if (spvIsVulkanEnv(_.context()->target_env)) {
95     // Vulkan 1.1 specific rules
96     if (_.context()->target_env != SPV_ENV_VULKAN_1_0) {
97       // Scope for Non Uniform Group Operations must be limited to Subgroup
98       if (spvOpcodeIsNonUniformGroupOperation(opcode) &&
99           value != SpvScopeSubgroup) {
100         return _.diag(SPV_ERROR_INVALID_DATA, inst)
101                << spvOpcodeString(opcode)
102                << ": in Vulkan environment Execution scope is limited to "
103                << "Subgroup";
104       }
105     }
106 
107     // If OpControlBarrier is used in fragment, vertex, tessellation evaluation,
108     // or geometry stages, the execution Scope must be Subgroup.
109     if (opcode == SpvOpControlBarrier && value != SpvScopeSubgroup) {
110       _.function(inst->function()->id())
111           ->RegisterExecutionModelLimitation([](SpvExecutionModel model,
112                                                 std::string* message) {
113             if (model == SpvExecutionModelFragment ||
114                 model == SpvExecutionModelVertex ||
115                 model == SpvExecutionModelGeometry ||
116                 model == SpvExecutionModelTessellationEvaluation) {
117               if (message) {
118                 *message =
119                     "in Vulkan evironment, OpControlBarrier execution scope "
120                     "must be Subgroup for Fragment, Vertex, Geometry and "
121                     "TessellationEvaluation execution models";
122               }
123               return false;
124             }
125             return true;
126           });
127     }
128 
129     // Vulkan generic rules
130     // Scope for execution must be limited to Workgroup or Subgroup
131     if (value != SpvScopeWorkgroup && value != SpvScopeSubgroup) {
132       return _.diag(SPV_ERROR_INVALID_DATA, inst)
133              << spvOpcodeString(opcode)
134              << ": in Vulkan environment Execution Scope is limited to "
135              << "Workgroup and Subgroup";
136     }
137   }
138 
139   // WebGPU Specific rules
140   if (spvIsWebGPUEnv(_.context()->target_env)) {
141     if (value != SpvScopeWorkgroup) {
142       return _.diag(SPV_ERROR_INVALID_DATA, inst)
143              << spvOpcodeString(opcode)
144              << ": in WebGPU environment Execution Scope is limited to "
145              << "Workgroup";
146     }
147   }
148 
149   // TODO(atgoo@github.com) Add checks for OpenCL and OpenGL environments.
150 
151   // General SPIRV rules
152   // Scope for execution must be limited to Workgroup or Subgroup for
153   // non-uniform operations
154   if (spvOpcodeIsNonUniformGroupOperation(opcode) &&
155       value != SpvScopeSubgroup && value != SpvScopeWorkgroup) {
156     return _.diag(SPV_ERROR_INVALID_DATA, inst)
157            << spvOpcodeString(opcode)
158            << ": Execution scope is limited to Subgroup or Workgroup";
159   }
160 
161   return SPV_SUCCESS;
162 }
163 
ValidateMemoryScope(ValidationState_t & _,const Instruction * inst,uint32_t scope)164 spv_result_t ValidateMemoryScope(ValidationState_t& _, const Instruction* inst,
165                                  uint32_t scope) {
166   const SpvOp opcode = inst->opcode();
167   bool is_int32 = false, is_const_int32 = false;
168   uint32_t value = 0;
169   std::tie(is_int32, is_const_int32, value) = _.EvalInt32IfConst(scope);
170 
171   if (auto error = ValidateScope(_, inst, scope)) {
172     return error;
173   }
174 
175   if (!is_const_int32) {
176     return SPV_SUCCESS;
177   }
178 
179   if (value == SpvScopeQueueFamilyKHR) {
180     if (_.HasCapability(SpvCapabilityVulkanMemoryModelKHR)) {
181       return SPV_SUCCESS;
182     } else {
183       return _.diag(SPV_ERROR_INVALID_DATA, inst)
184              << spvOpcodeString(opcode)
185              << ": Memory Scope QueueFamilyKHR requires capability "
186              << "VulkanMemoryModelKHR";
187     }
188   }
189 
190   if (value == SpvScopeDevice &&
191       _.HasCapability(SpvCapabilityVulkanMemoryModelKHR) &&
192       !_.HasCapability(SpvCapabilityVulkanMemoryModelDeviceScopeKHR)) {
193     return _.diag(SPV_ERROR_INVALID_DATA, inst)
194            << "Use of device scope with VulkanKHR memory model requires the "
195            << "VulkanMemoryModelDeviceScopeKHR capability";
196   }
197 
198   // Vulkan Specific rules
199   if (spvIsVulkanEnv(_.context()->target_env)) {
200     if (value == SpvScopeCrossDevice) {
201       return _.diag(SPV_ERROR_INVALID_DATA, inst)
202              << spvOpcodeString(opcode)
203              << ": in Vulkan environment, Memory Scope cannot be CrossDevice";
204     }
205     // Vulkan 1.0 specifc rules
206     if (_.context()->target_env == SPV_ENV_VULKAN_1_0 &&
207         value != SpvScopeDevice && value != SpvScopeWorkgroup &&
208         value != SpvScopeInvocation) {
209       return _.diag(SPV_ERROR_INVALID_DATA, inst)
210              << spvOpcodeString(opcode)
211              << ": in Vulkan 1.0 environment Memory Scope is limited to "
212              << "Device, Workgroup and Invocation";
213     }
214     // Vulkan 1.1 specifc rules
215     if ((_.context()->target_env == SPV_ENV_VULKAN_1_1 ||
216          _.context()->target_env == SPV_ENV_VULKAN_1_2) &&
217         value != SpvScopeDevice && value != SpvScopeWorkgroup &&
218         value != SpvScopeSubgroup && value != SpvScopeInvocation) {
219       return _.diag(SPV_ERROR_INVALID_DATA, inst)
220              << spvOpcodeString(opcode)
221              << ": in Vulkan 1.1 and 1.2 environment Memory Scope is limited "
222              << "to Device, Workgroup and Invocation";
223     }
224   }
225 
226   // WebGPU specific rules
227   if (spvIsWebGPUEnv(_.context()->target_env)) {
228     switch (inst->opcode()) {
229       case SpvOpControlBarrier:
230         if (value != SpvScopeWorkgroup) {
231           return _.diag(SPV_ERROR_INVALID_DATA, inst)
232                  << spvOpcodeString(opcode)
233                  << ": in WebGPU environment Memory Scope is limited to "
234                  << "Workgroup for OpControlBarrier";
235         }
236         break;
237       case SpvOpMemoryBarrier:
238         if (value != SpvScopeWorkgroup) {
239           return _.diag(SPV_ERROR_INVALID_DATA, inst)
240                  << spvOpcodeString(opcode)
241                  << ": in WebGPU environment Memory Scope is limited to "
242                  << "Workgroup for OpMemoryBarrier";
243         }
244         break;
245       default:
246         if (spvOpcodeIsAtomicOp(inst->opcode())) {
247           if (value != SpvScopeQueueFamilyKHR) {
248             return _.diag(SPV_ERROR_INVALID_DATA, inst)
249                    << spvOpcodeString(opcode)
250                    << ": in WebGPU environment Memory Scope is limited to "
251                    << "QueueFamilyKHR for OpAtomic* operations";
252           }
253         }
254 
255         if (value != SpvScopeWorkgroup && value != SpvScopeInvocation &&
256             value != SpvScopeQueueFamilyKHR) {
257           return _.diag(SPV_ERROR_INVALID_DATA, inst)
258                  << spvOpcodeString(opcode)
259                  << ": in WebGPU environment Memory Scope is limited to "
260                  << "Workgroup, Invocation, and QueueFamilyKHR";
261         }
262         break;
263     }
264   }
265 
266   // TODO(atgoo@github.com) Add checks for OpenCL and OpenGL environments.
267 
268   return SPV_SUCCESS;
269 }
270 
271 }  // namespace val
272 }  // namespace spvtools
273