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