• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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