1 // Copyright (c) 2017 Google Inc.
2 // Modifications Copyright (C) 2020 Advanced Micro Devices, Inc. All rights
3 // reserved.
4 //
5 // Licensed under the Apache License, Version 2.0 (the "License");
6 // you may not use this file except in compliance with the License.
7 // You may obtain a copy of the License at
8 //
9 // http://www.apache.org/licenses/LICENSE-2.0
10 //
11 // Unless required by applicable law or agreed to in writing, software
12 // distributed under the License is distributed on an "AS IS" BASIS,
13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 // See the License for the specific language governing permissions and
15 // limitations under the License.
16
17 // Validates correctness of atomic SPIR-V instructions.
18
19 #include "source/val/validate.h"
20
21 #include "source/diagnostic.h"
22 #include "source/opcode.h"
23 #include "source/spirv_target_env.h"
24 #include "source/util/bitutils.h"
25 #include "source/val/instruction.h"
26 #include "source/val/validate_memory_semantics.h"
27 #include "source/val/validate_scopes.h"
28 #include "source/val/validation_state.h"
29
30 namespace {
31
IsStorageClassAllowedByUniversalRules(uint32_t storage_class)32 bool IsStorageClassAllowedByUniversalRules(uint32_t storage_class) {
33 switch (storage_class) {
34 case SpvStorageClassUniform:
35 case SpvStorageClassStorageBuffer:
36 case SpvStorageClassWorkgroup:
37 case SpvStorageClassCrossWorkgroup:
38 case SpvStorageClassGeneric:
39 case SpvStorageClassAtomicCounter:
40 case SpvStorageClassImage:
41 case SpvStorageClassFunction:
42 case SpvStorageClassPhysicalStorageBuffer:
43 case SpvStorageClassTaskPayloadWorkgroupEXT:
44 return true;
45 break;
46 default:
47 return false;
48 }
49 }
50
HasReturnType(uint32_t opcode)51 bool HasReturnType(uint32_t opcode) {
52 switch (opcode) {
53 case SpvOpAtomicStore:
54 case SpvOpAtomicFlagClear:
55 return false;
56 break;
57 default:
58 return true;
59 }
60 }
61
HasOnlyFloatReturnType(uint32_t opcode)62 bool HasOnlyFloatReturnType(uint32_t opcode) {
63 switch (opcode) {
64 case SpvOpAtomicFAddEXT:
65 case SpvOpAtomicFMinEXT:
66 case SpvOpAtomicFMaxEXT:
67 return true;
68 break;
69 default:
70 return false;
71 }
72 }
73
HasOnlyIntReturnType(uint32_t opcode)74 bool HasOnlyIntReturnType(uint32_t opcode) {
75 switch (opcode) {
76 case SpvOpAtomicCompareExchange:
77 case SpvOpAtomicCompareExchangeWeak:
78 case SpvOpAtomicIIncrement:
79 case SpvOpAtomicIDecrement:
80 case SpvOpAtomicIAdd:
81 case SpvOpAtomicISub:
82 case SpvOpAtomicSMin:
83 case SpvOpAtomicUMin:
84 case SpvOpAtomicSMax:
85 case SpvOpAtomicUMax:
86 case SpvOpAtomicAnd:
87 case SpvOpAtomicOr:
88 case SpvOpAtomicXor:
89 return true;
90 break;
91 default:
92 return false;
93 }
94 }
95
HasIntOrFloatReturnType(uint32_t opcode)96 bool HasIntOrFloatReturnType(uint32_t opcode) {
97 switch (opcode) {
98 case SpvOpAtomicLoad:
99 case SpvOpAtomicExchange:
100 return true;
101 break;
102 default:
103 return false;
104 }
105 }
106
HasOnlyBoolReturnType(uint32_t opcode)107 bool HasOnlyBoolReturnType(uint32_t opcode) {
108 switch (opcode) {
109 case SpvOpAtomicFlagTestAndSet:
110 return true;
111 break;
112 default:
113 return false;
114 }
115 }
116
117 } // namespace
118
119 namespace spvtools {
120 namespace val {
121
122 // Validates correctness of atomic instructions.
AtomicsPass(ValidationState_t & _,const Instruction * inst)123 spv_result_t AtomicsPass(ValidationState_t& _, const Instruction* inst) {
124 const SpvOp opcode = inst->opcode();
125 switch (opcode) {
126 case SpvOpAtomicLoad:
127 case SpvOpAtomicStore:
128 case SpvOpAtomicExchange:
129 case SpvOpAtomicFAddEXT:
130 case SpvOpAtomicCompareExchange:
131 case SpvOpAtomicCompareExchangeWeak:
132 case SpvOpAtomicIIncrement:
133 case SpvOpAtomicIDecrement:
134 case SpvOpAtomicIAdd:
135 case SpvOpAtomicISub:
136 case SpvOpAtomicSMin:
137 case SpvOpAtomicUMin:
138 case SpvOpAtomicFMinEXT:
139 case SpvOpAtomicSMax:
140 case SpvOpAtomicUMax:
141 case SpvOpAtomicFMaxEXT:
142 case SpvOpAtomicAnd:
143 case SpvOpAtomicOr:
144 case SpvOpAtomicXor:
145 case SpvOpAtomicFlagTestAndSet:
146 case SpvOpAtomicFlagClear: {
147 const uint32_t result_type = inst->type_id();
148
149 // All current atomics only are scalar result
150 // Validate return type first so can just check if pointer type is same
151 // (if applicable)
152 if (HasReturnType(opcode)) {
153 if (HasOnlyFloatReturnType(opcode) &&
154 !_.IsFloatScalarType(result_type)) {
155 return _.diag(SPV_ERROR_INVALID_DATA, inst)
156 << spvOpcodeString(opcode)
157 << ": expected Result Type to be float scalar type";
158 } else if (HasOnlyIntReturnType(opcode) &&
159 !_.IsIntScalarType(result_type)) {
160 return _.diag(SPV_ERROR_INVALID_DATA, inst)
161 << spvOpcodeString(opcode)
162 << ": expected Result Type to be integer scalar type";
163 } else if (HasIntOrFloatReturnType(opcode) &&
164 !_.IsFloatScalarType(result_type) &&
165 !_.IsIntScalarType(result_type)) {
166 return _.diag(SPV_ERROR_INVALID_DATA, inst)
167 << spvOpcodeString(opcode)
168 << ": expected Result Type to be integer or float scalar type";
169 } else if (HasOnlyBoolReturnType(opcode) &&
170 !_.IsBoolScalarType(result_type)) {
171 return _.diag(SPV_ERROR_INVALID_DATA, inst)
172 << spvOpcodeString(opcode)
173 << ": expected Result Type to be bool scalar type";
174 }
175 }
176
177 uint32_t operand_index = HasReturnType(opcode) ? 2 : 0;
178 const uint32_t pointer_type = _.GetOperandTypeId(inst, operand_index++);
179 uint32_t data_type = 0;
180 uint32_t storage_class = 0;
181 if (!_.GetPointerTypeInfo(pointer_type, &data_type, &storage_class)) {
182 return _.diag(SPV_ERROR_INVALID_DATA, inst)
183 << spvOpcodeString(opcode)
184 << ": expected Pointer to be of type OpTypePointer";
185 }
186
187 // Can't use result_type because OpAtomicStore doesn't have a result
188 if ( _.IsIntScalarType(data_type) &&_.GetBitWidth(data_type) == 64 &&
189 !_.HasCapability(SpvCapabilityInt64Atomics)) {
190 return _.diag(SPV_ERROR_INVALID_DATA, inst)
191 << spvOpcodeString(opcode)
192 << ": 64-bit atomics require the Int64Atomics capability";
193 }
194
195 // Validate storage class against universal rules
196 if (!IsStorageClassAllowedByUniversalRules(storage_class)) {
197 return _.diag(SPV_ERROR_INVALID_DATA, inst)
198 << spvOpcodeString(opcode)
199 << ": storage class forbidden by universal validation rules.";
200 }
201
202 // Then Shader rules
203 if (_.HasCapability(SpvCapabilityShader)) {
204 // Vulkan environment rule
205 if (spvIsVulkanEnv(_.context()->target_env)) {
206 if ((storage_class != SpvStorageClassUniform) &&
207 (storage_class != SpvStorageClassStorageBuffer) &&
208 (storage_class != SpvStorageClassWorkgroup) &&
209 (storage_class != SpvStorageClassImage) &&
210 (storage_class != SpvStorageClassPhysicalStorageBuffer) &&
211 (storage_class != SpvStorageClassTaskPayloadWorkgroupEXT)) {
212 return _.diag(SPV_ERROR_INVALID_DATA, inst)
213 << _.VkErrorID(4686) << spvOpcodeString(opcode)
214 << ": Vulkan spec only allows storage classes for atomic to "
215 "be: Uniform, Workgroup, Image, StorageBuffer, "
216 "PhysicalStorageBuffer or TaskPayloadWorkgroupEXT.";
217 }
218 } else if (storage_class == SpvStorageClassFunction) {
219 return _.diag(SPV_ERROR_INVALID_DATA, inst)
220 << spvOpcodeString(opcode)
221 << ": Function storage class forbidden when the Shader "
222 "capability is declared.";
223 }
224
225 if (opcode == SpvOpAtomicFAddEXT) {
226 // result type being float checked already
227 if ((_.GetBitWidth(result_type) == 16) &&
228 (!_.HasCapability(SpvCapabilityAtomicFloat16AddEXT))) {
229 return _.diag(SPV_ERROR_INVALID_DATA, inst)
230 << spvOpcodeString(opcode)
231 << ": float add atomics require the AtomicFloat32AddEXT "
232 "capability";
233 }
234 if ((_.GetBitWidth(result_type) == 32) &&
235 (!_.HasCapability(SpvCapabilityAtomicFloat32AddEXT))) {
236 return _.diag(SPV_ERROR_INVALID_DATA, inst)
237 << spvOpcodeString(opcode)
238 << ": float add atomics require the AtomicFloat32AddEXT "
239 "capability";
240 }
241 if ((_.GetBitWidth(result_type) == 64) &&
242 (!_.HasCapability(SpvCapabilityAtomicFloat64AddEXT))) {
243 return _.diag(SPV_ERROR_INVALID_DATA, inst)
244 << spvOpcodeString(opcode)
245 << ": float add atomics require the AtomicFloat64AddEXT "
246 "capability";
247 }
248 } else if (opcode == SpvOpAtomicFMinEXT ||
249 opcode == SpvOpAtomicFMaxEXT) {
250 if ((_.GetBitWidth(result_type) == 16) &&
251 (!_.HasCapability(SpvCapabilityAtomicFloat16MinMaxEXT))) {
252 return _.diag(SPV_ERROR_INVALID_DATA, inst)
253 << spvOpcodeString(opcode)
254 << ": float min/max atomics require the "
255 "AtomicFloat16MinMaxEXT capability";
256 }
257 if ((_.GetBitWidth(result_type) == 32) &&
258 (!_.HasCapability(SpvCapabilityAtomicFloat32MinMaxEXT))) {
259 return _.diag(SPV_ERROR_INVALID_DATA, inst)
260 << spvOpcodeString(opcode)
261 << ": float min/max atomics require the "
262 "AtomicFloat32MinMaxEXT capability";
263 }
264 if ((_.GetBitWidth(result_type) == 64) &&
265 (!_.HasCapability(SpvCapabilityAtomicFloat64MinMaxEXT))) {
266 return _.diag(SPV_ERROR_INVALID_DATA, inst)
267 << spvOpcodeString(opcode)
268 << ": float min/max atomics require the "
269 "AtomicFloat64MinMaxEXT capability";
270 }
271 }
272 }
273
274 // And finally OpenCL environment rules
275 if (spvIsOpenCLEnv(_.context()->target_env)) {
276 if ((storage_class != SpvStorageClassFunction) &&
277 (storage_class != SpvStorageClassWorkgroup) &&
278 (storage_class != SpvStorageClassCrossWorkgroup) &&
279 (storage_class != SpvStorageClassGeneric)) {
280 return _.diag(SPV_ERROR_INVALID_DATA, inst)
281 << spvOpcodeString(opcode)
282 << ": storage class must be Function, Workgroup, "
283 "CrossWorkGroup or Generic in the OpenCL environment.";
284 }
285
286 if (_.context()->target_env == SPV_ENV_OPENCL_1_2) {
287 if (storage_class == SpvStorageClassGeneric) {
288 return _.diag(SPV_ERROR_INVALID_DATA, inst)
289 << "Storage class cannot be Generic in OpenCL 1.2 "
290 "environment";
291 }
292 }
293 }
294
295 // If result and pointer type are different, need to do special check here
296 if (opcode == SpvOpAtomicFlagTestAndSet ||
297 opcode == SpvOpAtomicFlagClear) {
298 if (!_.IsIntScalarType(data_type) || _.GetBitWidth(data_type) != 32) {
299 return _.diag(SPV_ERROR_INVALID_DATA, inst)
300 << spvOpcodeString(opcode)
301 << ": expected Pointer to point to a value of 32-bit integer "
302 "type";
303 }
304 } else if (opcode == SpvOpAtomicStore) {
305 if (!_.IsFloatScalarType(data_type) && !_.IsIntScalarType(data_type)) {
306 return _.diag(SPV_ERROR_INVALID_DATA, inst)
307 << spvOpcodeString(opcode)
308 << ": expected Pointer to be a pointer to integer or float "
309 << "scalar type";
310 }
311 } else if (data_type != result_type) {
312 return _.diag(SPV_ERROR_INVALID_DATA, inst)
313 << spvOpcodeString(opcode)
314 << ": expected Pointer to point to a value of type Result "
315 "Type";
316 }
317
318 auto memory_scope = inst->GetOperandAs<const uint32_t>(operand_index++);
319 if (auto error = ValidateMemoryScope(_, inst, memory_scope)) {
320 return error;
321 }
322
323 const auto equal_semantics_index = operand_index++;
324 if (auto error = ValidateMemorySemantics(_, inst, equal_semantics_index,
325 memory_scope))
326 return error;
327
328 if (opcode == SpvOpAtomicCompareExchange ||
329 opcode == SpvOpAtomicCompareExchangeWeak) {
330 const auto unequal_semantics_index = operand_index++;
331 if (auto error = ValidateMemorySemantics(
332 _, inst, unequal_semantics_index, memory_scope))
333 return error;
334
335 // Volatile bits must match for equal and unequal semantics. Previous
336 // checks guarantee they are 32-bit constants, but we need to recheck
337 // whether they are evaluatable constants.
338 bool is_int32 = false;
339 bool is_equal_const = false;
340 bool is_unequal_const = false;
341 uint32_t equal_value = 0;
342 uint32_t unequal_value = 0;
343 std::tie(is_int32, is_equal_const, equal_value) = _.EvalInt32IfConst(
344 inst->GetOperandAs<uint32_t>(equal_semantics_index));
345 std::tie(is_int32, is_unequal_const, unequal_value) =
346 _.EvalInt32IfConst(
347 inst->GetOperandAs<uint32_t>(unequal_semantics_index));
348 if (is_equal_const && is_unequal_const &&
349 ((equal_value & SpvMemorySemanticsVolatileMask) ^
350 (unequal_value & SpvMemorySemanticsVolatileMask))) {
351 return _.diag(SPV_ERROR_INVALID_ID, inst)
352 << "Volatile mask setting must match for Equal and Unequal "
353 "memory semantics";
354 }
355 }
356
357 if (opcode == SpvOpAtomicStore) {
358 const uint32_t value_type = _.GetOperandTypeId(inst, 3);
359 if (value_type != data_type) {
360 return _.diag(SPV_ERROR_INVALID_DATA, inst)
361 << spvOpcodeString(opcode)
362 << ": expected Value type and the type pointed to by "
363 "Pointer to be the same";
364 }
365 } else if (opcode != SpvOpAtomicLoad && opcode != SpvOpAtomicIIncrement &&
366 opcode != SpvOpAtomicIDecrement &&
367 opcode != SpvOpAtomicFlagTestAndSet &&
368 opcode != SpvOpAtomicFlagClear) {
369 const uint32_t value_type = _.GetOperandTypeId(inst, operand_index++);
370 if (value_type != result_type) {
371 return _.diag(SPV_ERROR_INVALID_DATA, inst)
372 << spvOpcodeString(opcode)
373 << ": expected Value to be of type Result Type";
374 }
375 }
376
377 if (opcode == SpvOpAtomicCompareExchange ||
378 opcode == SpvOpAtomicCompareExchangeWeak) {
379 const uint32_t comparator_type =
380 _.GetOperandTypeId(inst, operand_index++);
381 if (comparator_type != result_type) {
382 return _.diag(SPV_ERROR_INVALID_DATA, inst)
383 << spvOpcodeString(opcode)
384 << ": expected Comparator to be of type Result Type";
385 }
386 }
387
388 break;
389 }
390
391 default:
392 break;
393 }
394
395 return SPV_SUCCESS;
396 }
397
398 } // namespace val
399 } // namespace spvtools
400