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/spirv_target_env.h"
18 #include "source/val/instruction.h"
19 #include "source/val/validation_state.h"
20
21 namespace spvtools {
22 namespace val {
23
IsValidScope(uint32_t scope)24 bool IsValidScope(uint32_t scope) {
25 // Deliberately avoid a default case so we have to update the list when the
26 // scopes list changes.
27 switch (static_cast<spv::Scope>(scope)) {
28 case spv::Scope::CrossDevice:
29 case spv::Scope::Device:
30 case spv::Scope::Workgroup:
31 case spv::Scope::Subgroup:
32 case spv::Scope::Invocation:
33 case spv::Scope::QueueFamilyKHR:
34 case spv::Scope::ShaderCallKHR:
35 return true;
36 case spv::Scope::Max:
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 spv::Op 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(spv::Capability::Shader) &&
56 !_.HasCapability(spv::Capability::CooperativeMatrixNV)) {
57 return _.diag(SPV_ERROR_INVALID_DATA, inst)
58 << "Scope ids must be OpConstant when Shader capability is "
59 << "present";
60 }
61 if (_.HasCapability(spv::Capability::Shader) &&
62 _.HasCapability(spv::Capability::CooperativeMatrixNV) &&
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 spv::Op opcode = inst->opcode();
81 bool is_int32 = false, is_const_int32 = false;
82 uint32_t tmp_value = 0;
83 std::tie(is_int32, is_const_int32, tmp_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 spv::Scope value = spv::Scope(tmp_value);
94
95 // Vulkan specific rules
96 if (spvIsVulkanEnv(_.context()->target_env)) {
97 // Vulkan 1.1 specific rules
98 if (_.context()->target_env != SPV_ENV_VULKAN_1_0) {
99 // Scope for Non Uniform Group Operations must be limited to Subgroup
100 if (spvOpcodeIsNonUniformGroupOperation(opcode) &&
101 value != spv::Scope::Subgroup) {
102 return _.diag(SPV_ERROR_INVALID_DATA, inst)
103 << _.VkErrorID(4642) << spvOpcodeString(opcode)
104 << ": in Vulkan environment Execution scope is limited to "
105 << "Subgroup";
106 }
107 }
108
109 // OpControlBarrier must only use Subgroup execution scope for a subset of
110 // execution models.
111 if (opcode == spv::Op::OpControlBarrier && value != spv::Scope::Subgroup) {
112 std::string errorVUID = _.VkErrorID(4682);
113 _.function(inst->function()->id())
114 ->RegisterExecutionModelLimitation([errorVUID](
115 spv::ExecutionModel model,
116 std::string* message) {
117 if (model == spv::ExecutionModel::Fragment ||
118 model == spv::ExecutionModel::Vertex ||
119 model == spv::ExecutionModel::Geometry ||
120 model == spv::ExecutionModel::TessellationEvaluation ||
121 model == spv::ExecutionModel::RayGenerationKHR ||
122 model == spv::ExecutionModel::IntersectionKHR ||
123 model == spv::ExecutionModel::AnyHitKHR ||
124 model == spv::ExecutionModel::ClosestHitKHR ||
125 model == spv::ExecutionModel::MissKHR) {
126 if (message) {
127 *message =
128 errorVUID +
129 "in Vulkan environment, OpControlBarrier execution scope "
130 "must be Subgroup for Fragment, Vertex, Geometry, "
131 "TessellationEvaluation, RayGeneration, Intersection, "
132 "AnyHit, ClosestHit, and Miss execution models";
133 }
134 return false;
135 }
136 return true;
137 });
138 }
139
140 // Only subset of execution models support Workgroup.
141 if (value == spv::Scope::Workgroup) {
142 std::string errorVUID = _.VkErrorID(4637);
143 _.function(inst->function()->id())
144 ->RegisterExecutionModelLimitation(
145 [errorVUID](spv::ExecutionModel model, std::string* message) {
146 if (model != spv::ExecutionModel::TaskNV &&
147 model != spv::ExecutionModel::MeshNV &&
148 model != spv::ExecutionModel::TaskEXT &&
149 model != spv::ExecutionModel::MeshEXT &&
150 model != spv::ExecutionModel::TessellationControl &&
151 model != spv::ExecutionModel::GLCompute) {
152 if (message) {
153 *message =
154 errorVUID +
155 "in Vulkan environment, Workgroup execution scope is "
156 "only for TaskNV, MeshNV, TaskEXT, MeshEXT, "
157 "TessellationControl, and GLCompute execution models";
158 }
159 return false;
160 }
161 return true;
162 });
163 }
164
165 // Vulkan generic rules
166 // Scope for execution must be limited to Workgroup or Subgroup
167 if (value != spv::Scope::Workgroup && value != spv::Scope::Subgroup) {
168 return _.diag(SPV_ERROR_INVALID_DATA, inst)
169 << _.VkErrorID(4636) << spvOpcodeString(opcode)
170 << ": in Vulkan environment Execution Scope is limited to "
171 << "Workgroup and Subgroup";
172 }
173 }
174
175 // TODO(atgoo@github.com) Add checks for OpenCL and OpenGL environments.
176
177 // General SPIRV rules
178 // Scope for execution must be limited to Workgroup or Subgroup for
179 // non-uniform operations
180 if (spvOpcodeIsNonUniformGroupOperation(opcode) &&
181 value != spv::Scope::Subgroup && value != spv::Scope::Workgroup) {
182 return _.diag(SPV_ERROR_INVALID_DATA, inst)
183 << spvOpcodeString(opcode)
184 << ": Execution scope is limited to Subgroup or Workgroup";
185 }
186
187 return SPV_SUCCESS;
188 }
189
ValidateMemoryScope(ValidationState_t & _,const Instruction * inst,uint32_t scope)190 spv_result_t ValidateMemoryScope(ValidationState_t& _, const Instruction* inst,
191 uint32_t scope) {
192 const spv::Op opcode = inst->opcode();
193 bool is_int32 = false, is_const_int32 = false;
194 uint32_t tmp_value = 0;
195 std::tie(is_int32, is_const_int32, tmp_value) = _.EvalInt32IfConst(scope);
196
197 if (auto error = ValidateScope(_, inst, scope)) {
198 return error;
199 }
200
201 if (!is_const_int32) {
202 return SPV_SUCCESS;
203 }
204
205 spv::Scope value = spv::Scope(tmp_value);
206
207 if (value == spv::Scope::QueueFamilyKHR) {
208 if (_.HasCapability(spv::Capability::VulkanMemoryModelKHR)) {
209 return SPV_SUCCESS;
210 } else {
211 return _.diag(SPV_ERROR_INVALID_DATA, inst)
212 << spvOpcodeString(opcode)
213 << ": Memory Scope QueueFamilyKHR requires capability "
214 << "VulkanMemoryModelKHR";
215 }
216 }
217
218 if (value == spv::Scope::Device &&
219 _.HasCapability(spv::Capability::VulkanMemoryModelKHR) &&
220 !_.HasCapability(spv::Capability::VulkanMemoryModelDeviceScopeKHR)) {
221 return _.diag(SPV_ERROR_INVALID_DATA, inst)
222 << "Use of device scope with VulkanKHR memory model requires the "
223 << "VulkanMemoryModelDeviceScopeKHR capability";
224 }
225
226 // Vulkan Specific rules
227 if (spvIsVulkanEnv(_.context()->target_env)) {
228 if (value != spv::Scope::Device && value != spv::Scope::Workgroup &&
229 value != spv::Scope::Subgroup && value != spv::Scope::Invocation &&
230 value != spv::Scope::ShaderCallKHR &&
231 value != spv::Scope::QueueFamily) {
232 return _.diag(SPV_ERROR_INVALID_DATA, inst)
233 << _.VkErrorID(4638) << spvOpcodeString(opcode)
234 << ": in Vulkan environment Memory Scope is limited to Device, "
235 "QueueFamily, Workgroup, ShaderCallKHR, Subgroup, or "
236 "Invocation";
237 } else if (_.context()->target_env == SPV_ENV_VULKAN_1_0 &&
238 value == spv::Scope::Subgroup &&
239 !_.HasCapability(spv::Capability::SubgroupBallotKHR) &&
240 !_.HasCapability(spv::Capability::SubgroupVoteKHR)) {
241 return _.diag(SPV_ERROR_INVALID_DATA, inst)
242 << _.VkErrorID(7951) << spvOpcodeString(opcode)
243 << ": in Vulkan 1.0 environment Memory Scope is can not be "
244 "Subgroup without SubgroupBallotKHR or SubgroupVoteKHR "
245 "declared";
246 }
247
248 if (value == spv::Scope::ShaderCallKHR) {
249 std::string errorVUID = _.VkErrorID(4640);
250 _.function(inst->function()->id())
251 ->RegisterExecutionModelLimitation(
252 [errorVUID](spv::ExecutionModel model, std::string* message) {
253 if (model != spv::ExecutionModel::RayGenerationKHR &&
254 model != spv::ExecutionModel::IntersectionKHR &&
255 model != spv::ExecutionModel::AnyHitKHR &&
256 model != spv::ExecutionModel::ClosestHitKHR &&
257 model != spv::ExecutionModel::MissKHR &&
258 model != spv::ExecutionModel::CallableKHR) {
259 if (message) {
260 *message =
261 errorVUID +
262 "ShaderCallKHR Memory Scope requires a ray tracing "
263 "execution model";
264 }
265 return false;
266 }
267 return true;
268 });
269 }
270
271 if (value == spv::Scope::Workgroup) {
272 std::string errorVUID = _.VkErrorID(7321);
273 _.function(inst->function()->id())
274 ->RegisterExecutionModelLimitation(
275 [errorVUID](spv::ExecutionModel model, std::string* message) {
276 if (model != spv::ExecutionModel::GLCompute &&
277 model != spv::ExecutionModel::TessellationControl &&
278 model != spv::ExecutionModel::TaskNV &&
279 model != spv::ExecutionModel::MeshNV &&
280 model != spv::ExecutionModel::TaskEXT &&
281 model != spv::ExecutionModel::MeshEXT) {
282 if (message) {
283 *message = errorVUID +
284 "Workgroup Memory Scope is limited to MeshNV, "
285 "TaskNV, MeshEXT, TaskEXT, TessellationControl, "
286 "and GLCompute execution model";
287 }
288 return false;
289 }
290 return true;
291 });
292
293 if (_.memory_model() == spv::MemoryModel::GLSL450) {
294 errorVUID = _.VkErrorID(7320);
295 _.function(inst->function()->id())
296 ->RegisterExecutionModelLimitation(
297 [errorVUID](spv::ExecutionModel model, std::string* message) {
298 if (model == spv::ExecutionModel::TessellationControl) {
299 if (message) {
300 *message =
301 errorVUID +
302 "Workgroup Memory Scope can't be used with "
303 "TessellationControl using GLSL450 Memory Model";
304 }
305 return false;
306 }
307 return true;
308 });
309 }
310 }
311 }
312
313 // TODO(atgoo@github.com) Add checks for OpenCL and OpenGL environments.
314
315 return SPV_SUCCESS;
316 }
317
318 } // namespace val
319 } // namespace spvtools
320