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
22namespace spvtools {
23namespace val {
24
25bool 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
43spv_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
79spv_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
179spv_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 return _.diag(SPV_ERROR_INVALID_DATA, inst)
235 << spvOpcodeString(opcode)
236 << ": in Vulkan 1.1 and 1.2 environment Memory Scope is limited "
237 << "to Device, Workgroup and Invocation";
238 }
239 }
240
241 // WebGPU specific rules
242 if (spvIsWebGPUEnv(_.context()->target_env)) {
243 switch (inst->opcode()) {
244 case SpvOpControlBarrier:
245 if (value != SpvScopeWorkgroup) {
246 return _.diag(SPV_ERROR_INVALID_DATA, inst)
247 << spvOpcodeString(opcode)
248 << ": in WebGPU environment Memory Scope is limited to "
249 << "Workgroup for OpControlBarrier";
250 }
251 break;
252 case SpvOpMemoryBarrier:
253 if (value != SpvScopeWorkgroup) {
254 return _.diag(SPV_ERROR_INVALID_DATA, inst)
255 << spvOpcodeString(opcode)
256 << ": in WebGPU environment Memory Scope is limited to "
257 << "Workgroup for OpMemoryBarrier";
258 }
259 break;
260 default:
261 if (spvOpcodeIsAtomicOp(inst->opcode())) {
262 if (value != SpvScopeQueueFamilyKHR) {
263 return _.diag(SPV_ERROR_INVALID_DATA, inst)
264 << spvOpcodeString(opcode)
265 << ": in WebGPU environment Memory Scope is limited to "
266 << "QueueFamilyKHR for OpAtomic* operations";
267 }
268 }
269
270 if (value != SpvScopeWorkgroup && value != SpvScopeInvocation &&
271 value != SpvScopeQueueFamilyKHR) {
272 return _.diag(SPV_ERROR_INVALID_DATA, inst)
273 << spvOpcodeString(opcode)
274 << ": in WebGPU environment Memory Scope is limited to "
275 << "Workgroup, Invocation, and QueueFamilyKHR";
276 }
277 break;
278 }
279
280 if (value == SpvScopeWorkgroup) {
281 _.function(inst->function()->id())
282 ->RegisterExecutionModelLimitation(
283 [](SpvExecutionModel model, std::string* message) {
284 if (model != SpvExecutionModelGLCompute) {
285 if (message) {
286 *message =
287 ": in WebGPU environment, Workgroup Memory Scope is "
288 "limited to GLCompute execution model";
289 }
290 return false;
291 }
292 return true;
293 });
294 }
295 }
296
297 // TODO(atgoo@github.com) Add checks for OpenCL and OpenGL environments.
298
299 return SPV_SUCCESS;
300}
301
302} // namespace val
303} // namespace spvtools
304