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 | |
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. |
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 | |