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