• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2017 Google Inc.
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 // Validates correctness of atomic SPIR-V instructions.
16 
17 #include "source/val/validate.h"
18 
19 #include "source/diagnostic.h"
20 #include "source/opcode.h"
21 #include "source/spirv_target_env.h"
22 #include "source/util/bitutils.h"
23 #include "source/val/instruction.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(uint32_t storage_class)30 bool IsStorageClassAllowedByUniversalRules(uint32_t storage_class) {
31   switch (storage_class) {
32     case SpvStorageClassUniform:
33     case SpvStorageClassStorageBuffer:
34     case SpvStorageClassWorkgroup:
35     case SpvStorageClassCrossWorkgroup:
36     case SpvStorageClassGeneric:
37     case SpvStorageClassAtomicCounter:
38     case SpvStorageClassImage:
39     case SpvStorageClassFunction:
40     case SpvStorageClassPhysicalStorageBufferEXT:
41       return true;
42       break;
43     default:
44       return false;
45   }
46 }
47 
48 }  // namespace
49 
50 namespace spvtools {
51 namespace val {
52 
53 // Validates correctness of atomic instructions.
AtomicsPass(ValidationState_t & _,const Instruction * inst)54 spv_result_t AtomicsPass(ValidationState_t& _, const Instruction* inst) {
55   const SpvOp opcode = inst->opcode();
56   const uint32_t result_type = inst->type_id();
57 
58   switch (opcode) {
59     case SpvOpAtomicLoad:
60     case SpvOpAtomicStore:
61     case SpvOpAtomicExchange:
62     case SpvOpAtomicCompareExchange:
63     case SpvOpAtomicCompareExchangeWeak:
64     case SpvOpAtomicIIncrement:
65     case SpvOpAtomicIDecrement:
66     case SpvOpAtomicIAdd:
67     case SpvOpAtomicISub:
68     case SpvOpAtomicSMin:
69     case SpvOpAtomicUMin:
70     case SpvOpAtomicSMax:
71     case SpvOpAtomicUMax:
72     case SpvOpAtomicAnd:
73     case SpvOpAtomicOr:
74     case SpvOpAtomicXor:
75     case SpvOpAtomicFlagTestAndSet:
76     case SpvOpAtomicFlagClear: {
77       if (_.HasCapability(SpvCapabilityKernel) &&
78           (opcode == SpvOpAtomicLoad || opcode == SpvOpAtomicExchange ||
79            opcode == SpvOpAtomicCompareExchange)) {
80         if (!_.IsFloatScalarType(result_type) &&
81             !_.IsIntScalarType(result_type)) {
82           return _.diag(SPV_ERROR_INVALID_DATA, inst)
83                  << spvOpcodeString(opcode)
84                  << ": expected Result Type to be int or float scalar type";
85         }
86       } else if (opcode == SpvOpAtomicFlagTestAndSet) {
87         if (!_.IsBoolScalarType(result_type)) {
88           return _.diag(SPV_ERROR_INVALID_DATA, inst)
89                  << spvOpcodeString(opcode)
90                  << ": expected Result Type to be bool scalar type";
91         }
92       } else if (opcode == SpvOpAtomicFlagClear || opcode == SpvOpAtomicStore) {
93         assert(result_type == 0);
94       } else {
95         if (!_.IsIntScalarType(result_type)) {
96           return _.diag(SPV_ERROR_INVALID_DATA, inst)
97                  << spvOpcodeString(opcode)
98                  << ": expected Result Type to be int scalar type";
99         }
100         if (spvIsVulkanEnv(_.context()->target_env) &&
101             _.GetBitWidth(result_type) != 32) {
102           switch (opcode) {
103             case SpvOpAtomicSMin:
104             case SpvOpAtomicUMin:
105             case SpvOpAtomicSMax:
106             case SpvOpAtomicUMax:
107             case SpvOpAtomicAnd:
108             case SpvOpAtomicOr:
109             case SpvOpAtomicXor:
110             case SpvOpAtomicIAdd:
111             case SpvOpAtomicLoad:
112             case SpvOpAtomicStore:
113             case SpvOpAtomicExchange:
114             case SpvOpAtomicCompareExchange: {
115               if (_.GetBitWidth(result_type) == 64 &&
116                   !_.HasCapability(SpvCapabilityInt64Atomics))
117                 return _.diag(SPV_ERROR_INVALID_DATA, inst)
118                        << spvOpcodeString(opcode)
119                        << ": 64-bit atomics require the Int64Atomics "
120                           "capability";
121             } break;
122             default:
123               return _.diag(SPV_ERROR_INVALID_DATA, inst)
124                      << spvOpcodeString(opcode)
125                      << ": according to the Vulkan spec atomic Result Type "
126                         "needs "
127                         "to be a 32-bit int scalar type";
128           }
129         }
130       }
131 
132       uint32_t operand_index =
133           opcode == SpvOpAtomicFlagClear || opcode == SpvOpAtomicStore ? 0 : 2;
134       const uint32_t pointer_type = _.GetOperandTypeId(inst, operand_index++);
135 
136       uint32_t data_type = 0;
137       uint32_t storage_class = 0;
138       if (!_.GetPointerTypeInfo(pointer_type, &data_type, &storage_class)) {
139         return _.diag(SPV_ERROR_INVALID_DATA, inst)
140                << spvOpcodeString(opcode)
141                << ": expected Pointer to be of type OpTypePointer";
142       }
143 
144       // Validate storage class against universal rules
145       if (!IsStorageClassAllowedByUniversalRules(storage_class)) {
146         return _.diag(SPV_ERROR_INVALID_DATA, inst)
147                << spvOpcodeString(opcode)
148                << ": storage class forbidden by universal validation rules.";
149       }
150 
151       // Then Shader rules
152       if (_.HasCapability(SpvCapabilityShader)) {
153         if (storage_class == SpvStorageClassFunction) {
154           return _.diag(SPV_ERROR_INVALID_DATA, inst)
155                  << spvOpcodeString(opcode)
156                  << ": Function storage class forbidden when the Shader "
157                     "capability is declared.";
158         }
159       }
160 
161       // And finally OpenCL environment rules
162       if (spvIsOpenCLEnv(_.context()->target_env)) {
163         if ((storage_class != SpvStorageClassFunction) &&
164             (storage_class != SpvStorageClassWorkgroup) &&
165             (storage_class != SpvStorageClassCrossWorkgroup) &&
166             (storage_class != SpvStorageClassGeneric)) {
167           return _.diag(SPV_ERROR_INVALID_DATA, inst)
168                  << spvOpcodeString(opcode)
169                  << ": storage class must be Function, Workgroup, "
170                     "CrossWorkGroup or Generic in the OpenCL environment.";
171         }
172 
173         if (_.context()->target_env == SPV_ENV_OPENCL_1_2) {
174           if (storage_class == SpvStorageClassGeneric) {
175             return _.diag(SPV_ERROR_INVALID_DATA, inst)
176                    << "Storage class cannot be Generic in OpenCL 1.2 "
177                       "environment";
178           }
179         }
180       }
181 
182       if (opcode == SpvOpAtomicFlagTestAndSet ||
183           opcode == SpvOpAtomicFlagClear) {
184         if (!_.IsIntScalarType(data_type) || _.GetBitWidth(data_type) != 32) {
185           return _.diag(SPV_ERROR_INVALID_DATA, inst)
186                  << spvOpcodeString(opcode)
187                  << ": expected Pointer to point to a value of 32-bit int type";
188         }
189       } else if (opcode == SpvOpAtomicStore) {
190         if (!_.IsFloatScalarType(data_type) && !_.IsIntScalarType(data_type)) {
191           return _.diag(SPV_ERROR_INVALID_DATA, inst)
192                  << spvOpcodeString(opcode)
193                  << ": expected Pointer to be a pointer to int or float "
194                  << "scalar type";
195         }
196       } else {
197         if (data_type != result_type) {
198           return _.diag(SPV_ERROR_INVALID_DATA, inst)
199                  << spvOpcodeString(opcode)
200                  << ": expected Pointer to point to a value of type Result "
201                     "Type";
202         }
203       }
204 
205       auto memory_scope = inst->GetOperandAs<const uint32_t>(operand_index++);
206       if (auto error = ValidateMemoryScope(_, inst, memory_scope)) {
207         return error;
208       }
209 
210       const auto equal_semantics_index = operand_index++;
211       if (auto error = ValidateMemorySemantics(_, inst, equal_semantics_index))
212         return error;
213 
214       if (opcode == SpvOpAtomicCompareExchange ||
215           opcode == SpvOpAtomicCompareExchangeWeak) {
216         const auto unequal_semantics_index = operand_index++;
217         if (auto error =
218                 ValidateMemorySemantics(_, inst, unequal_semantics_index))
219           return error;
220 
221         // Volatile bits must match for equal and unequal semantics. Previous
222         // checks guarantee they are 32-bit constants, but we need to recheck
223         // whether they are evaluatable constants.
224         bool is_int32 = false;
225         bool is_equal_const = false;
226         bool is_unequal_const = false;
227         uint32_t equal_value = 0;
228         uint32_t unequal_value = 0;
229         std::tie(is_int32, is_equal_const, equal_value) = _.EvalInt32IfConst(
230             inst->GetOperandAs<uint32_t>(equal_semantics_index));
231         std::tie(is_int32, is_unequal_const, unequal_value) =
232             _.EvalInt32IfConst(
233                 inst->GetOperandAs<uint32_t>(unequal_semantics_index));
234         if (is_equal_const && is_unequal_const &&
235             ((equal_value & SpvMemorySemanticsVolatileMask) ^
236              (unequal_value & SpvMemorySemanticsVolatileMask))) {
237           return _.diag(SPV_ERROR_INVALID_ID, inst)
238                  << "Volatile mask setting must match for Equal and Unequal "
239                     "memory semantics";
240         }
241       }
242 
243       if (opcode == SpvOpAtomicStore) {
244         const uint32_t value_type = _.GetOperandTypeId(inst, 3);
245         if (value_type != data_type) {
246           return _.diag(SPV_ERROR_INVALID_DATA, inst)
247                  << spvOpcodeString(opcode)
248                  << ": expected Value type and the type pointed to by "
249                     "Pointer to be the same";
250         }
251       } else if (opcode != SpvOpAtomicLoad && opcode != SpvOpAtomicIIncrement &&
252                  opcode != SpvOpAtomicIDecrement &&
253                  opcode != SpvOpAtomicFlagTestAndSet &&
254                  opcode != SpvOpAtomicFlagClear) {
255         const uint32_t value_type = _.GetOperandTypeId(inst, operand_index++);
256         if (value_type != result_type) {
257           return _.diag(SPV_ERROR_INVALID_DATA, inst)
258                  << spvOpcodeString(opcode)
259                  << ": expected Value to be of type Result Type";
260         }
261       }
262 
263       if (opcode == SpvOpAtomicCompareExchange ||
264           opcode == SpvOpAtomicCompareExchangeWeak) {
265         const uint32_t comparator_type =
266             _.GetOperandTypeId(inst, operand_index++);
267         if (comparator_type != result_type) {
268           return _.diag(SPV_ERROR_INVALID_DATA, inst)
269                  << spvOpcodeString(opcode)
270                  << ": expected Comparator to be of type Result Type";
271         }
272       }
273 
274       break;
275     }
276 
277     default:
278       break;
279   }
280 
281   return SPV_SUCCESS;
282 }
283 
284 }  // namespace val
285 }  // namespace spvtools
286