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