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/opcode.h"
20 #include "source/spirv_target_env.h"
21 #include "source/util/bitutils.h"
22 #include "source/val/instruction.h"
23 #include "source/val/validate.h"
24 #include "source/val/validate_memory_semantics.h"
25 #include "source/val/validate_scopes.h"
26 #include "source/val/validation_state.h"
27
28 namespace {
29
IsStorageClassAllowedByUniversalRules(spv::StorageClass storage_class)30 bool IsStorageClassAllowedByUniversalRules(spv::StorageClass storage_class) {
31 switch (storage_class) {
32 case spv::StorageClass::Uniform:
33 case spv::StorageClass::StorageBuffer:
34 case spv::StorageClass::Workgroup:
35 case spv::StorageClass::CrossWorkgroup:
36 case spv::StorageClass::Generic:
37 case spv::StorageClass::AtomicCounter:
38 case spv::StorageClass::Image:
39 case spv::StorageClass::Function:
40 case spv::StorageClass::PhysicalStorageBuffer:
41 case spv::StorageClass::TaskPayloadWorkgroupEXT:
42 return true;
43 break;
44 default:
45 return false;
46 }
47 }
48
HasReturnType(spv::Op opcode)49 bool HasReturnType(spv::Op opcode) {
50 switch (opcode) {
51 case spv::Op::OpAtomicStore:
52 case spv::Op::OpAtomicFlagClear:
53 return false;
54 break;
55 default:
56 return true;
57 }
58 }
59
HasOnlyFloatReturnType(spv::Op opcode)60 bool HasOnlyFloatReturnType(spv::Op opcode) {
61 switch (opcode) {
62 case spv::Op::OpAtomicFAddEXT:
63 case spv::Op::OpAtomicFMinEXT:
64 case spv::Op::OpAtomicFMaxEXT:
65 return true;
66 break;
67 default:
68 return false;
69 }
70 }
71
HasOnlyIntReturnType(spv::Op opcode)72 bool HasOnlyIntReturnType(spv::Op opcode) {
73 switch (opcode) {
74 case spv::Op::OpAtomicCompareExchange:
75 case spv::Op::OpAtomicCompareExchangeWeak:
76 case spv::Op::OpAtomicIIncrement:
77 case spv::Op::OpAtomicIDecrement:
78 case spv::Op::OpAtomicIAdd:
79 case spv::Op::OpAtomicISub:
80 case spv::Op::OpAtomicSMin:
81 case spv::Op::OpAtomicUMin:
82 case spv::Op::OpAtomicSMax:
83 case spv::Op::OpAtomicUMax:
84 case spv::Op::OpAtomicAnd:
85 case spv::Op::OpAtomicOr:
86 case spv::Op::OpAtomicXor:
87 return true;
88 break;
89 default:
90 return false;
91 }
92 }
93
HasIntOrFloatReturnType(spv::Op opcode)94 bool HasIntOrFloatReturnType(spv::Op opcode) {
95 switch (opcode) {
96 case spv::Op::OpAtomicLoad:
97 case spv::Op::OpAtomicExchange:
98 return true;
99 break;
100 default:
101 return false;
102 }
103 }
104
HasOnlyBoolReturnType(spv::Op opcode)105 bool HasOnlyBoolReturnType(spv::Op opcode) {
106 switch (opcode) {
107 case spv::Op::OpAtomicFlagTestAndSet:
108 return true;
109 break;
110 default:
111 return false;
112 }
113 }
114
115 } // namespace
116
117 namespace spvtools {
118 namespace val {
119
120 // Validates correctness of atomic instructions.
AtomicsPass(ValidationState_t & _,const Instruction * inst)121 spv_result_t AtomicsPass(ValidationState_t& _, const Instruction* inst) {
122 const spv::Op opcode = inst->opcode();
123 switch (opcode) {
124 case spv::Op::OpAtomicLoad:
125 case spv::Op::OpAtomicStore:
126 case spv::Op::OpAtomicExchange:
127 case spv::Op::OpAtomicFAddEXT:
128 case spv::Op::OpAtomicCompareExchange:
129 case spv::Op::OpAtomicCompareExchangeWeak:
130 case spv::Op::OpAtomicIIncrement:
131 case spv::Op::OpAtomicIDecrement:
132 case spv::Op::OpAtomicIAdd:
133 case spv::Op::OpAtomicISub:
134 case spv::Op::OpAtomicSMin:
135 case spv::Op::OpAtomicUMin:
136 case spv::Op::OpAtomicFMinEXT:
137 case spv::Op::OpAtomicSMax:
138 case spv::Op::OpAtomicUMax:
139 case spv::Op::OpAtomicFMaxEXT:
140 case spv::Op::OpAtomicAnd:
141 case spv::Op::OpAtomicOr:
142 case spv::Op::OpAtomicXor:
143 case spv::Op::OpAtomicFlagTestAndSet:
144 case spv::Op::OpAtomicFlagClear: {
145 const uint32_t result_type = inst->type_id();
146
147 // All current atomics only are scalar result
148 // Validate return type first so can just check if pointer type is same
149 // (if applicable)
150 if (HasReturnType(opcode)) {
151 if (HasOnlyFloatReturnType(opcode) &&
152 !_.IsFloatScalarType(result_type)) {
153 return _.diag(SPV_ERROR_INVALID_DATA, inst)
154 << spvOpcodeString(opcode)
155 << ": expected Result Type to be float scalar type";
156 } else if (HasOnlyIntReturnType(opcode) &&
157 !_.IsIntScalarType(result_type)) {
158 return _.diag(SPV_ERROR_INVALID_DATA, inst)
159 << spvOpcodeString(opcode)
160 << ": expected Result Type to be integer scalar type";
161 } else if (HasIntOrFloatReturnType(opcode) &&
162 !_.IsFloatScalarType(result_type) &&
163 !_.IsIntScalarType(result_type)) {
164 return _.diag(SPV_ERROR_INVALID_DATA, inst)
165 << spvOpcodeString(opcode)
166 << ": expected Result Type to be integer or float scalar type";
167 } else if (HasOnlyBoolReturnType(opcode) &&
168 !_.IsBoolScalarType(result_type)) {
169 return _.diag(SPV_ERROR_INVALID_DATA, inst)
170 << spvOpcodeString(opcode)
171 << ": expected Result Type to be bool scalar type";
172 }
173 }
174
175 uint32_t operand_index = HasReturnType(opcode) ? 2 : 0;
176 const uint32_t pointer_type = _.GetOperandTypeId(inst, operand_index++);
177 uint32_t data_type = 0;
178 spv::StorageClass storage_class;
179 if (!_.GetPointerTypeInfo(pointer_type, &data_type, &storage_class)) {
180 return _.diag(SPV_ERROR_INVALID_DATA, inst)
181 << spvOpcodeString(opcode)
182 << ": expected Pointer to be of type OpTypePointer";
183 }
184
185 // Can't use result_type because OpAtomicStore doesn't have a result
186 if (_.IsIntScalarType(data_type) && _.GetBitWidth(data_type) == 64 &&
187 !_.HasCapability(spv::Capability::Int64Atomics)) {
188 return _.diag(SPV_ERROR_INVALID_DATA, inst)
189 << spvOpcodeString(opcode)
190 << ": 64-bit atomics require the Int64Atomics capability";
191 }
192
193 // Validate storage class against universal rules
194 if (!IsStorageClassAllowedByUniversalRules(storage_class)) {
195 return _.diag(SPV_ERROR_INVALID_DATA, inst)
196 << spvOpcodeString(opcode)
197 << ": storage class forbidden by universal validation rules.";
198 }
199
200 // Then Shader rules
201 if (_.HasCapability(spv::Capability::Shader)) {
202 // Vulkan environment rule
203 if (spvIsVulkanEnv(_.context()->target_env)) {
204 if ((storage_class != spv::StorageClass::Uniform) &&
205 (storage_class != spv::StorageClass::StorageBuffer) &&
206 (storage_class != spv::StorageClass::Workgroup) &&
207 (storage_class != spv::StorageClass::Image) &&
208 (storage_class != spv::StorageClass::PhysicalStorageBuffer) &&
209 (storage_class != spv::StorageClass::TaskPayloadWorkgroupEXT)) {
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, "
214 "PhysicalStorageBuffer or TaskPayloadWorkgroupEXT.";
215 }
216 } else if (storage_class == spv::StorageClass::Function) {
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 == spv::Op::OpAtomicFAddEXT) {
224 // result type being float checked already
225 if ((_.GetBitWidth(result_type) == 16) &&
226 (!_.HasCapability(spv::Capability::AtomicFloat16AddEXT))) {
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(spv::Capability::AtomicFloat32AddEXT))) {
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(spv::Capability::AtomicFloat64AddEXT))) {
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 == spv::Op::OpAtomicFMinEXT ||
247 opcode == spv::Op::OpAtomicFMaxEXT) {
248 if ((_.GetBitWidth(result_type) == 16) &&
249 (!_.HasCapability(spv::Capability::AtomicFloat16MinMaxEXT))) {
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(spv::Capability::AtomicFloat32MinMaxEXT))) {
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(spv::Capability::AtomicFloat64MinMaxEXT))) {
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 != spv::StorageClass::Function) &&
275 (storage_class != spv::StorageClass::Workgroup) &&
276 (storage_class != spv::StorageClass::CrossWorkgroup) &&
277 (storage_class != spv::StorageClass::Generic)) {
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 == spv::StorageClass::Generic) {
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 == spv::Op::OpAtomicFlagTestAndSet ||
295 opcode == spv::Op::OpAtomicFlagClear) {
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 == spv::Op::OpAtomicStore) {
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 == spv::Op::OpAtomicCompareExchange ||
327 opcode == spv::Op::OpAtomicCompareExchangeWeak) {
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 & uint32_t(spv::MemorySemanticsMask::Volatile)) ^
348 (unequal_value & uint32_t(spv::MemorySemanticsMask::Volatile)))) {
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 == spv::Op::OpAtomicStore) {
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 != spv::Op::OpAtomicLoad &&
364 opcode != spv::Op::OpAtomicIIncrement &&
365 opcode != spv::Op::OpAtomicIDecrement &&
366 opcode != spv::Op::OpAtomicFlagTestAndSet &&
367 opcode != spv::Op::OpAtomicFlagClear) {
368 const uint32_t value_type = _.GetOperandTypeId(inst, operand_index++);
369 if (value_type != result_type) {
370 return _.diag(SPV_ERROR_INVALID_DATA, inst)
371 << spvOpcodeString(opcode)
372 << ": expected Value to be of type Result Type";
373 }
374 }
375
376 if (opcode == spv::Op::OpAtomicCompareExchange ||
377 opcode == spv::Op::OpAtomicCompareExchangeWeak) {
378 const uint32_t comparator_type =
379 _.GetOperandTypeId(inst, operand_index++);
380 if (comparator_type != result_type) {
381 return _.diag(SPV_ERROR_INVALID_DATA, inst)
382 << spvOpcodeString(opcode)
383 << ": expected Comparator to be of type Result Type";
384 }
385 }
386
387 break;
388 }
389
390 default:
391 break;
392 }
393
394 return SPV_SUCCESS;
395 }
396
397 } // namespace val
398 } // namespace spvtools
399