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