1// Copyright (c) 2018 Google LLC.
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#include <algorithm>
18#include <string>
19#include <vector>
20
21#include "source/opcode.h"
22#include "source/spirv_target_env.h"
23#include "source/val/instruction.h"
24#include "source/val/validate.h"
25#include "source/val/validate_scopes.h"
26#include "source/val/validation_state.h"
27
28namespace spvtools {
29namespace val {
30namespace {
31
32bool AreLayoutCompatibleStructs(ValidationState_t&, const Instruction*,
33 const Instruction*);
34bool HaveLayoutCompatibleMembers(ValidationState_t&, const Instruction*,
35 const Instruction*);
36bool HaveSameLayoutDecorations(ValidationState_t&, const Instruction*,
37 const Instruction*);
38bool HasConflictingMemberOffsets(const std::vector<Decoration>&,
39 const std::vector<Decoration>&);
40
41bool IsAllowedTypeOrArrayOfSame(ValidationState_t& _, const Instruction* type,
42 std::initializer_list<uint32_t> allowed) {
43 if (std::find(allowed.begin(), allowed.end(), type->opcode()) !=
44 allowed.end()) {
45 return true;
46 }
47 if (type->opcode() == SpvOpTypeArray ||
48 type->opcode() == SpvOpTypeRuntimeArray) {
49 auto elem_type = _.FindDef(type->word(2));
50 return std::find(allowed.begin(), allowed.end(), elem_type->opcode()) !=
51 allowed.end();
52 }
53 return false;
54}
55
56// Returns true if the two instructions represent structs that, as far as the
57// validator can tell, have the exact same data layout.
58bool AreLayoutCompatibleStructs(ValidationState_t& _, const Instruction* type1,
59 const Instruction* type2) {
60 if (type1->opcode() != SpvOpTypeStruct) {
61 return false;
62 }
63 if (type2->opcode() != SpvOpTypeStruct) {
64 return false;
65 }
66
67 if (!HaveLayoutCompatibleMembers(_, type1, type2)) return false;
68
69 return HaveSameLayoutDecorations(_, type1, type2);
70}
71
72// Returns true if the operands to the OpTypeStruct instruction defining the
73// types are the same or are layout compatible types. |type1| and |type2| must
74// be OpTypeStruct instructions.
75bool HaveLayoutCompatibleMembers(ValidationState_t& _, const Instruction* type1,
76 const Instruction* type2) {
77 assert(type1->opcode() == SpvOpTypeStruct &&
78 "type1 must be an OpTypeStruct instruction.");
79 assert(type2->opcode() == SpvOpTypeStruct &&
80 "type2 must be an OpTypeStruct instruction.");
81 const auto& type1_operands = type1->operands();
82 const auto& type2_operands = type2->operands();
83 if (type1_operands.size() != type2_operands.size()) {
84 return false;
85 }
86
87 for (size_t operand = 2; operand < type1_operands.size(); ++operand) {
88 if (type1->word(operand) != type2->word(operand)) {
89 auto def1 = _.FindDef(type1->word(operand));
90 auto def2 = _.FindDef(type2->word(operand));
91 if (!AreLayoutCompatibleStructs(_, def1, def2)) {
92 return false;
93 }
94 }
95 }
96 return true;
97}
98
99// Returns true if all decorations that affect the data layout of the struct
100// (like Offset), are the same for the two types. |type1| and |type2| must be
101// OpTypeStruct instructions.
102bool HaveSameLayoutDecorations(ValidationState_t& _, const Instruction* type1,
103 const Instruction* type2) {
104 assert(type1->opcode() == SpvOpTypeStruct &&
105 "type1 must be an OpTypeStruct instruction.");
106 assert(type2->opcode() == SpvOpTypeStruct &&
107 "type2 must be an OpTypeStruct instruction.");
108 const std::vector<Decoration>& type1_decorations =
109 _.id_decorations(type1->id());
110 const std::vector<Decoration>& type2_decorations =
111 _.id_decorations(type2->id());
112
113 // TODO: Will have to add other check for arrays an matricies if we want to
114 // handle them.
115 if (HasConflictingMemberOffsets(type1_decorations, type2_decorations)) {
116 return false;
117 }
118
119 return true;
120}
121
122bool HasConflictingMemberOffsets(
123 const std::vector<Decoration>& type1_decorations,
124 const std::vector<Decoration>& type2_decorations) {
125 {
126 // We are interested in conflicting decoration. If a decoration is in one
127 // list but not the other, then we will assume the code is correct. We are
128 // looking for things we know to be wrong.
129 //
130 // We do not have to traverse type2_decoration because, after traversing
131 // type1_decorations, anything new will not be found in
132 // type1_decoration. Therefore, it cannot lead to a conflict.
133 for (const Decoration& decoration : type1_decorations) {
134 switch (decoration.dec_type()) {
135 case SpvDecorationOffset: {
136 // Since these affect the layout of the struct, they must be present
137 // in both structs.
138 auto compare = [&decoration](const Decoration& rhs) {
139 if (rhs.dec_type() != SpvDecorationOffset) return false;
140 return decoration.struct_member_index() ==
141 rhs.struct_member_index();
142 };
143 auto i = std::find_if(type2_decorations.begin(),
144 type2_decorations.end(), compare);
145 if (i != type2_decorations.end() &&
146 decoration.params().front() != i->params().front()) {
147 return true;
148 }
149 } break;
150 default:
151 // This decoration does not affect the layout of the structure, so
152 // just moving on.
153 break;
154 }
155 }
156 }
157 return false;
158}
159
160// If |skip_builtin| is true, returns true if |storage| contains bool within
161// it and no storage that contains the bool is builtin.
162// If |skip_builtin| is false, returns true if |storage| contains bool within
163// it.
164bool ContainsInvalidBool(ValidationState_t& _, const Instruction* storage,
165 bool skip_builtin) {
166 if (skip_builtin) {
167 for (const Decoration& decoration : _.id_decorations(storage->id())) {
168 if (decoration.dec_type() == SpvDecorationBuiltIn) return false;
169 }
170 }
171
172 const size_t elem_type_index = 1;
173 uint32_t elem_type_id;
174 Instruction* elem_type;
175
176 switch (storage->opcode()) {
177 case SpvOpTypeBool:
178 return true;
179 case SpvOpTypeVector:
180 case SpvOpTypeMatrix:
181 case SpvOpTypeArray:
182 case SpvOpTypeRuntimeArray:
183 elem_type_id = storage->GetOperandAs<uint32_t>(elem_type_index);
184 elem_type = _.FindDef(elem_type_id);
185 return ContainsInvalidBool(_, elem_type, skip_builtin);
186 case SpvOpTypeStruct:
187 for (size_t member_type_index = 1;
188 member_type_index < storage->operands().size();
189 ++member_type_index) {
190 auto member_type_id =
191 storage->GetOperandAs<uint32_t>(member_type_index);
192 auto member_type = _.FindDef(member_type_id);
193 if (ContainsInvalidBool(_, member_type, skip_builtin)) return true;
194 }
195 default:
196 break;
197 }
198 return false;
199}
200
201bool ContainsCooperativeMatrix(ValidationState_t& _,
202 const Instruction* storage) {
203 const size_t elem_type_index = 1;
204 uint32_t elem_type_id;
205 Instruction* elem_type;
206
207 switch (storage->opcode()) {
208 case SpvOpTypeCooperativeMatrixNV:
209 return true;
210 case SpvOpTypeArray:
211 case SpvOpTypeRuntimeArray:
212 elem_type_id = storage->GetOperandAs<uint32_t>(elem_type_index);
213 elem_type = _.FindDef(elem_type_id);
214 return ContainsCooperativeMatrix(_, elem_type);
215 case SpvOpTypeStruct:
216 for (size_t member_type_index = 1;
217 member_type_index < storage->operands().size();
218 ++member_type_index) {
219 auto member_type_id =
220 storage->GetOperandAs<uint32_t>(member_type_index);
221 auto member_type = _.FindDef(member_type_id);
222 if (ContainsCooperativeMatrix(_, member_type)) return true;
223 }
224 break;
225 default:
226 break;
227 }
228 return false;
229}
230
231std::pair<SpvStorageClass, SpvStorageClass> GetStorageClass(
232 ValidationState_t& _, const Instruction* inst) {
233 SpvStorageClass dst_sc = SpvStorageClassMax;
234 SpvStorageClass src_sc = SpvStorageClassMax;
235 switch (inst->opcode()) {
236 case SpvOpCooperativeMatrixLoadNV:
237 case SpvOpLoad: {
238 auto load_pointer = _.FindDef(inst->GetOperandAs<uint32_t>(2));
239 auto load_pointer_type = _.FindDef(load_pointer->type_id());
240 dst_sc = load_pointer_type->GetOperandAs<SpvStorageClass>(1);
241 break;
242 }
243 case SpvOpCooperativeMatrixStoreNV:
244 case SpvOpStore: {
245 auto store_pointer = _.FindDef(inst->GetOperandAs<uint32_t>(0));
246 auto store_pointer_type = _.FindDef(store_pointer->type_id());
247 dst_sc = store_pointer_type->GetOperandAs<SpvStorageClass>(1);
248 break;
249 }
250 case SpvOpCopyMemory:
251 case SpvOpCopyMemorySized: {
252 auto dst = _.FindDef(inst->GetOperandAs<uint32_t>(0));
253 auto dst_type = _.FindDef(dst->type_id());
254 dst_sc = dst_type->GetOperandAs<SpvStorageClass>(1);
255 auto src = _.FindDef(inst->GetOperandAs<uint32_t>(1));
256 auto src_type = _.FindDef(src->type_id());
257 src_sc = src_type->GetOperandAs<SpvStorageClass>(1);
258 break;
259 }
260 default:
261 break;
262 }
263
264 return std::make_pair(dst_sc, src_sc);
265}
266
267// Returns the number of instruction words taken up by a memory access
268// argument and its implied operands.
269int MemoryAccessNumWords(uint32_t mask) {
270 int result = 1; // Count the mask
271 if (mask & SpvMemoryAccessAlignedMask) ++result;
272 if (mask & SpvMemoryAccessMakePointerAvailableKHRMask) ++result;
273 if (mask & SpvMemoryAccessMakePointerVisibleKHRMask) ++result;
274 return result;
275}
276
277// Returns the scope ID operand for MakeAvailable memory access with mask
278// at the given operand index.
279// This function is only called for OpLoad, OpStore, OpCopyMemory and
280// OpCopyMemorySized, OpCooperativeMatrixLoadNV, and
281// OpCooperativeMatrixStoreNV.
282uint32_t GetMakeAvailableScope(const Instruction* inst, uint32_t mask,
283 uint32_t mask_index) {
284 assert(mask & SpvMemoryAccessMakePointerAvailableKHRMask);
285 uint32_t this_bit = uint32_t(SpvMemoryAccessMakePointerAvailableKHRMask);
286 uint32_t index =
287 mask_index - 1 + MemoryAccessNumWords(mask & (this_bit | (this_bit - 1)));
288 return inst->GetOperandAs<uint32_t>(index);
289}
290
291// This function is only called for OpLoad, OpStore, OpCopyMemory,
292// OpCopyMemorySized, OpCooperativeMatrixLoadNV, and
293// OpCooperativeMatrixStoreNV.
294uint32_t GetMakeVisibleScope(const Instruction* inst, uint32_t mask,
295 uint32_t mask_index) {
296 assert(mask & SpvMemoryAccessMakePointerVisibleKHRMask);
297 uint32_t this_bit = uint32_t(SpvMemoryAccessMakePointerVisibleKHRMask);
298 uint32_t index =
299 mask_index - 1 + MemoryAccessNumWords(mask & (this_bit | (this_bit - 1)));
300 return inst->GetOperandAs<uint32_t>(index);
301}
302
303bool DoesStructContainRTA(const ValidationState_t& _, const Instruction* inst) {
304 for (size_t member_index = 1; member_index < inst->operands().size();
305 ++member_index) {
306 const auto member_id = inst->GetOperandAs<uint32_t>(member_index);
307 const auto member_type = _.FindDef(member_id);
308 if (member_type->opcode() == SpvOpTypeRuntimeArray) return true;
309 }
310 return false;
311}
312
313spv_result_t CheckMemoryAccess(ValidationState_t& _, const Instruction* inst,
314 uint32_t index) {
315 SpvStorageClass dst_sc, src_sc;
316 std::tie(dst_sc, src_sc) = GetStorageClass(_, inst);
317 if (inst->operands().size() <= index) {
318 if (src_sc == SpvStorageClassPhysicalStorageBufferEXT ||
319 dst_sc == SpvStorageClassPhysicalStorageBufferEXT) {
320 return _.diag(SPV_ERROR_INVALID_ID, inst)
321 << "Memory accesses with PhysicalStorageBufferEXT must use "
322 "Aligned.";
323 }
324 return SPV_SUCCESS;
325 }
326
327 const uint32_t mask = inst->GetOperandAs<uint32_t>(index);
328 if (mask & SpvMemoryAccessMakePointerAvailableKHRMask) {
329 if (inst->opcode() == SpvOpLoad ||
330 inst->opcode() == SpvOpCooperativeMatrixLoadNV) {
331 return _.diag(SPV_ERROR_INVALID_ID, inst)
332 << "MakePointerAvailableKHR cannot be used with OpLoad.";
333 }
334
335 if (!(mask & SpvMemoryAccessNonPrivatePointerKHRMask)) {
336 return _.diag(SPV_ERROR_INVALID_ID, inst)
337 << "NonPrivatePointerKHR must be specified if "
338 "MakePointerAvailableKHR is specified.";
339 }
340
341 // Check the associated scope for MakeAvailableKHR.
342 const auto available_scope = GetMakeAvailableScope(inst, mask, index);
343 if (auto error = ValidateMemoryScope(_, inst, available_scope))
344 return error;
345 }
346
347 if (mask & SpvMemoryAccessMakePointerVisibleKHRMask) {
348 if (inst->opcode() == SpvOpStore ||
349 inst->opcode() == SpvOpCooperativeMatrixStoreNV) {
350 return _.diag(SPV_ERROR_INVALID_ID, inst)
351 << "MakePointerVisibleKHR cannot be used with OpStore.";
352 }
353
354 if (!(mask & SpvMemoryAccessNonPrivatePointerKHRMask)) {
355 return _.diag(SPV_ERROR_INVALID_ID, inst)
356 << "NonPrivatePointerKHR must be specified if "
357 << "MakePointerVisibleKHR is specified.";
358 }
359
360 // Check the associated scope for MakeVisibleKHR.
361 const auto visible_scope = GetMakeVisibleScope(inst, mask, index);
362 if (auto error = ValidateMemoryScope(_, inst, visible_scope)) return error;
363 }
364
365 if (mask & SpvMemoryAccessNonPrivatePointerKHRMask) {
366 if (dst_sc != SpvStorageClassUniform &&
367 dst_sc != SpvStorageClassWorkgroup &&
368 dst_sc != SpvStorageClassCrossWorkgroup &&
369 dst_sc != SpvStorageClassGeneric && dst_sc != SpvStorageClassImage &&
370 dst_sc != SpvStorageClassStorageBuffer &&
371 dst_sc != SpvStorageClassPhysicalStorageBufferEXT) {
372 return _.diag(SPV_ERROR_INVALID_ID, inst)
373 << "NonPrivatePointerKHR requires a pointer in Uniform, "
374 << "Workgroup, CrossWorkgroup, Generic, Image or StorageBuffer "
375 << "storage classes.";
376 }
377 if (src_sc != SpvStorageClassMax && src_sc != SpvStorageClassUniform &&
378 src_sc != SpvStorageClassWorkgroup &&
379 src_sc != SpvStorageClassCrossWorkgroup &&
380 src_sc != SpvStorageClassGeneric && src_sc != SpvStorageClassImage &&
381 src_sc != SpvStorageClassStorageBuffer &&
382 src_sc != SpvStorageClassPhysicalStorageBufferEXT) {
383 return _.diag(SPV_ERROR_INVALID_ID, inst)
384 << "NonPrivatePointerKHR requires a pointer in Uniform, "
385 << "Workgroup, CrossWorkgroup, Generic, Image or StorageBuffer "
386 << "storage classes.";
387 }
388 }
389
390 if (!(mask & SpvMemoryAccessAlignedMask)) {
391 if (src_sc == SpvStorageClassPhysicalStorageBufferEXT ||
392 dst_sc == SpvStorageClassPhysicalStorageBufferEXT) {
393 return _.diag(SPV_ERROR_INVALID_ID, inst)
394 << "Memory accesses with PhysicalStorageBufferEXT must use "
395 "Aligned.";
396 }
397 }
398
399 return SPV_SUCCESS;
400}
401
402spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) {
403 auto result_type = _.FindDef(inst->type_id());
404 if (!result_type || result_type->opcode() != SpvOpTypePointer) {
405 return _.diag(SPV_ERROR_INVALID_ID, inst)
406 << "OpVariable Result Type <id> '" << _.getIdName(inst->type_id())
407 << "' is not a pointer type.";
408 }
409
410 const auto initializer_index = 3;
411 const auto storage_class_index = 2;
412 if (initializer_index < inst->operands().size()) {
413 const auto initializer_id = inst->GetOperandAs<uint32_t>(initializer_index);
414 const auto initializer = _.FindDef(initializer_id);
415 const auto is_module_scope_var =
416 initializer && (initializer->opcode() == SpvOpVariable) &&
417 (initializer->GetOperandAs<SpvStorageClass>(storage_class_index) !=
418 SpvStorageClassFunction);
419 const auto is_constant =
420 initializer && spvOpcodeIsConstant(initializer->opcode());
421 if (!initializer || !(is_constant || is_module_scope_var)) {
422 return _.diag(SPV_ERROR_INVALID_ID, inst)
423 << "OpVariable Initializer <id> '" << _.getIdName(initializer_id)
424 << "' is not a constant or module-scope variable.";
425 }
426 if (initializer->type_id() != result_type->GetOperandAs<uint32_t>(2u)) {
427 return _.diag(SPV_ERROR_INVALID_ID, inst)
428 << "Initializer type must match the type pointed to by the Result "
429 "Type";
430 }
431 }
432
433 auto storage_class = inst->GetOperandAs<SpvStorageClass>(storage_class_index);
434 if (storage_class != SpvStorageClassWorkgroup &&
435 storage_class != SpvStorageClassCrossWorkgroup &&
436 storage_class != SpvStorageClassPrivate &&
437 storage_class != SpvStorageClassFunction &&
438 storage_class != SpvStorageClassRayPayloadNV &&
439 storage_class != SpvStorageClassIncomingRayPayloadNV &&
440 storage_class != SpvStorageClassHitAttributeNV &&
441 storage_class != SpvStorageClassCallableDataNV &&
442 storage_class != SpvStorageClassIncomingCallableDataNV) {
443 const auto storage_index = 2;
444 const auto storage_id = result_type->GetOperandAs<uint32_t>(storage_index);
445 const auto storage = _.FindDef(storage_id);
446 bool storage_input_or_output = storage_class == SpvStorageClassInput ||
447 storage_class == SpvStorageClassOutput;
448 bool builtin = false;
449 if (storage_input_or_output) {
450 for (const Decoration& decoration : _.id_decorations(inst->id())) {
451 if (decoration.dec_type() == SpvDecorationBuiltIn) {
452 builtin = true;
453 break;
454 }
455 }
456 }
457 if (!(storage_input_or_output && builtin) &&
458 ContainsInvalidBool(_, storage, storage_input_or_output)) {
459 return _.diag(SPV_ERROR_INVALID_ID, inst)
460 << "If OpTypeBool is stored in conjunction with OpVariable, it "
461 << "can only be used with non-externally visible shader Storage "
462 << "Classes: Workgroup, CrossWorkgroup, Private, and Function";
463 }
464 }
465
466 if (!_.IsValidStorageClass(storage_class)) {
467 return _.diag(SPV_ERROR_INVALID_BINARY, inst)
468 << "Invalid storage class for target environment";
469 }
470
471 if (storage_class == SpvStorageClassGeneric) {
472 return _.diag(SPV_ERROR_INVALID_BINARY, inst)
473 << "OpVariable storage class cannot be Generic";
474 }
475
476 if (inst->function() && storage_class != SpvStorageClassFunction) {
477 return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
478 << "Variables must have a function[7] storage class inside"
479 " of a function";
480 }
481
482 if (!inst->function() && storage_class == SpvStorageClassFunction) {
483 return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
484 << "Variables can not have a function[7] storage class "
485 "outside of a function";
486 }
487
488 // SPIR-V 3.32.8: Check that pointer type and variable type have the same
489 // storage class.
490 const auto result_storage_class_index = 1;
491 const auto result_storage_class =
492 result_type->GetOperandAs<uint32_t>(result_storage_class_index);
493 if (storage_class != result_storage_class) {
494 return _.diag(SPV_ERROR_INVALID_ID, inst)
495 << "From SPIR-V spec, section 3.32.8 on OpVariable:\n"
496 << "Its Storage Class operand must be the same as the Storage Class "
497 << "operand of the result type.";
498 }
499
500 // Variable pointer related restrictions.
501 const auto pointee = _.FindDef(result_type->word(3));
502 if (_.addressing_model() == SpvAddressingModelLogical &&
503 !_.options()->relax_logical_pointer) {
504 // VariablePointersStorageBuffer is implied by VariablePointers.
505 if (pointee->opcode() == SpvOpTypePointer) {
506 if (!_.HasCapability(SpvCapabilityVariablePointersStorageBuffer)) {
507 return _.diag(SPV_ERROR_INVALID_ID, inst)
508 << "In Logical addressing, variables may not allocate a pointer "
509 << "type";
510 } else if (storage_class != SpvStorageClassFunction &&
511 storage_class != SpvStorageClassPrivate) {
512 return _.diag(SPV_ERROR_INVALID_ID, inst)
513 << "In Logical addressing with variable pointers, variables "
514 << "that allocate pointers must be in Function or Private "
515 << "storage classes";
516 }
517 }
518 }
519
520 // Vulkan 14.5.1: Check type of PushConstant variables.
521 // Vulkan 14.5.2: Check type of UniformConstant and Uniform variables.
522 if (spvIsVulkanEnv(_.context()->target_env)) {
523 if (storage_class == SpvStorageClassPushConstant) {
524 if (!IsAllowedTypeOrArrayOfSame(_, pointee, {SpvOpTypeStruct})) {
525 return _.diag(SPV_ERROR_INVALID_ID, inst)
526 << "PushConstant OpVariable <id> '" << _.getIdName(inst->id())
527 << "' has illegal type.\n"
528 << "From Vulkan spec, section 14.5.1:\n"
529 << "Such variables must be typed as OpTypeStruct, "
530 << "or an array of this type";
531 }
532 }
533
534 if (storage_class == SpvStorageClassUniformConstant) {
535 if (!IsAllowedTypeOrArrayOfSame(
536 _, pointee,
537 {SpvOpTypeImage, SpvOpTypeSampler, SpvOpTypeSampledImage,
538 SpvOpTypeAccelerationStructureNV,
539 SpvOpTypeAccelerationStructureKHR,
540 SpvOpTypeRayQueryProvisionalKHR})) {
541 return _.diag(SPV_ERROR_INVALID_ID, inst)
542 << "UniformConstant OpVariable <id> '" << _.getIdName(inst->id())
543 << "' has illegal type.\n"
544 << "From Vulkan spec, section 14.5.2:\n"
545 << "Variables identified with the UniformConstant storage class "
546 << "are used only as handles to refer to opaque resources. Such "
547 << "variables must be typed as OpTypeImage, OpTypeSampler, "
548 << "OpTypeSampledImage, OpTypeAccelerationStructureNV, "
549 "OpTypeAccelerationStructureKHR, "
550 "OpTypeRayQueryProvisionalKHR, "
551 << "or an array of one of these types.";
552 }
553 }
554
555 if (storage_class == SpvStorageClassUniform) {
556 if (!IsAllowedTypeOrArrayOfSame(_, pointee, {SpvOpTypeStruct})) {
557 return _.diag(SPV_ERROR_INVALID_ID, inst)
558 << "Uniform OpVariable <id> '" << _.getIdName(inst->id())
559 << "' has illegal type.\n"
560 << "From Vulkan spec, section 14.5.2:\n"
561 << "Variables identified with the Uniform storage class are "
562 << "used to access transparent buffer backed resources. Such "
563 << "variables must be typed as OpTypeStruct, or an array of "
564 << "this type";
565 }
566 }
567
568 if (storage_class == SpvStorageClassStorageBuffer) {
569 if (!IsAllowedTypeOrArrayOfSame(_, pointee, {SpvOpTypeStruct})) {
570 return _.diag(SPV_ERROR_INVALID_ID, inst)
571 << "StorageBuffer OpVariable <id> '" << _.getIdName(inst->id())
572 << "' has illegal type.\n"
573 << "From Vulkan spec, section 14.5.2:\n"
574 << "Variables identified with the StorageBuffer storage class "
575 "are used to access transparent buffer backed resources. "
576 "Such variables must be typed as OpTypeStruct, or an array "
577 "of this type";
578 }
579 }
580 }
581
582 // WebGPU & Vulkan Appendix A: Check that if contains initializer, then
583 // storage class is Output, Private, or Function.
584 if (inst->operands().size() > 3 && storage_class != SpvStorageClassOutput &&
585 storage_class != SpvStorageClassPrivate &&
586 storage_class != SpvStorageClassFunction) {
587 if (spvIsVulkanOrWebGPUEnv(_.context()->target_env)) {
588 return _.diag(SPV_ERROR_INVALID_ID, inst)
589 << "OpVariable, <id> '" << _.getIdName(inst->id())
590 << "', has a disallowed initializer & storage class "
591 << "combination.\n"
592 << "From " << spvLogStringForEnv(_.context()->target_env)
593 << " spec:\n"
594 << "Variable declarations that include initializers must have "
595 << "one of the following storage classes: Output, Private, or "
596 << "Function";
597 }
598 }
599
600 // WebGPU: All variables with storage class Output, Private, or Function MUST
601 // have an initializer.
602 if (spvIsWebGPUEnv(_.context()->target_env) && inst->operands().size() <= 3 &&
603 (storage_class == SpvStorageClassOutput ||
604 storage_class == SpvStorageClassPrivate ||
605 storage_class == SpvStorageClassFunction)) {
606 return _.diag(SPV_ERROR_INVALID_ID, inst)
607 << "OpVariable, <id> '" << _.getIdName(inst->id())
608 << "', must have an initializer.\n"
609 << "From WebGPU execution environment spec:\n"
610 << "All variables in the following storage classes must have an "
611 << "initializer: Output, Private, or Function";
612 }
613
614 if (storage_class == SpvStorageClassPhysicalStorageBufferEXT) {
615 return _.diag(SPV_ERROR_INVALID_ID, inst)
616 << "PhysicalStorageBufferEXT must not be used with OpVariable.";
617 }
618
619 auto pointee_base = pointee;
620 while (pointee_base->opcode() == SpvOpTypeArray) {
621 pointee_base = _.FindDef(pointee_base->GetOperandAs<uint32_t>(1u));
622 }
623 if (pointee_base->opcode() == SpvOpTypePointer) {
624 if (pointee_base->GetOperandAs<uint32_t>(1u) ==
625 SpvStorageClassPhysicalStorageBufferEXT) {
626 // check for AliasedPointerEXT/RestrictPointerEXT
627 bool foundAliased =
628 _.HasDecoration(inst->id(), SpvDecorationAliasedPointerEXT);
629 bool foundRestrict =
630 _.HasDecoration(inst->id(), SpvDecorationRestrictPointerEXT);
631 if (!foundAliased && !foundRestrict) {
632 return _.diag(SPV_ERROR_INVALID_ID, inst)
633 << "OpVariable " << inst->id()
634 << ": expected AliasedPointerEXT or RestrictPointerEXT for "
635 << "PhysicalStorageBufferEXT pointer.";
636 }
637 if (foundAliased && foundRestrict) {
638 return _.diag(SPV_ERROR_INVALID_ID, inst)
639 << "OpVariable " << inst->id()
640 << ": can't specify both AliasedPointerEXT and "
641 << "RestrictPointerEXT for PhysicalStorageBufferEXT pointer.";
642 }
643 }
644 }
645
646 // Vulkan specific validation rules for OpTypeRuntimeArray
647 const auto type_index = 2;
648 const auto value_id = result_type->GetOperandAs<uint32_t>(type_index);
649 auto value_type = _.FindDef(value_id);
650 if (spvIsVulkanEnv(_.context()->target_env)) {
651 // OpTypeRuntimeArray should only ever be in a container like OpTypeStruct,
652 // so should never appear as a bare variable.
653 // Unless the module has the RuntimeDescriptorArrayEXT capability.
654 if (value_type && value_type->opcode() == SpvOpTypeRuntimeArray) {
655 if (!_.HasCapability(SpvCapabilityRuntimeDescriptorArrayEXT)) {
656 return _.diag(SPV_ERROR_INVALID_ID, inst)
657 << "OpVariable, <id> '" << _.getIdName(inst->id())
658 << "', is attempting to create memory for an illegal type, "
659 << "OpTypeRuntimeArray.\nFor Vulkan OpTypeRuntimeArray can only "
660 << "appear as the final member of an OpTypeStruct, thus cannot "
661 << "be instantiated via OpVariable";
662 } else {
663 // A bare variable OpTypeRuntimeArray is allowed in this context, but
664 // still need to check the storage class.
665 if (storage_class != SpvStorageClassStorageBuffer &&
666 storage_class != SpvStorageClassUniform &&
667 storage_class != SpvStorageClassUniformConstant) {
668 return _.diag(SPV_ERROR_INVALID_ID, inst)
669 << "For Vulkan with RuntimeDescriptorArrayEXT, a variable "
670 << "containing OpTypeRuntimeArray must have storage class of "
671 << "StorageBuffer, Uniform, or UniformConstant.";
672 }
673 }
674 }
675
676 // If an OpStruct has an OpTypeRuntimeArray somewhere within it, then it
677 // must either have the storage class StorageBuffer and be decorated
678 // with Block, or it must be in the Uniform storage class and be decorated
679 // as BufferBlock.
680 if (value_type && value_type->opcode() == SpvOpTypeStruct) {
681 if (DoesStructContainRTA(_, value_type)) {
682 if (storage_class == SpvStorageClassStorageBuffer) {
683 if (!_.HasDecoration(value_id, SpvDecorationBlock)) {
684 return _.diag(SPV_ERROR_INVALID_ID, inst)
685 << "For Vulkan, an OpTypeStruct variable containing an "
686 << "OpTypeRuntimeArray must be decorated with Block if it "
687 << "has storage class StorageBuffer.";
688 }
689 } else if (storage_class == SpvStorageClassUniform) {
690 if (!_.HasDecoration(value_id, SpvDecorationBufferBlock)) {
691 return _.diag(SPV_ERROR_INVALID_ID, inst)
692 << "For Vulkan, an OpTypeStruct variable containing an "
693 << "OpTypeRuntimeArray must be decorated with BufferBlock "
694 << "if it has storage class Uniform.";
695 }
696 } else {
697 return _.diag(SPV_ERROR_INVALID_ID, inst)
698 << "For Vulkan, OpTypeStruct variables containing "
699 << "OpTypeRuntimeArray must have storage class of "
700 << "StorageBuffer or Uniform.";
701 }
702 }
703 }
704 }
705
706 // WebGPU specific validation rules for OpTypeRuntimeArray
707 if (spvIsWebGPUEnv(_.context()->target_env)) {
708 // OpTypeRuntimeArray should only ever be in an OpTypeStruct,
709 // so should never appear as a bare variable.
710 if (value_type && value_type->opcode() == SpvOpTypeRuntimeArray) {
711 return _.diag(SPV_ERROR_INVALID_ID, inst)
712 << "OpVariable, <id> '" << _.getIdName(inst->id())
713 << "', is attempting to create memory for an illegal type, "
714 << "OpTypeRuntimeArray.\nFor WebGPU OpTypeRuntimeArray can only "
715 << "appear as the final member of an OpTypeStruct, thus cannot "
716 << "be instantiated via OpVariable";
717 }
718
719 // If an OpStruct has an OpTypeRuntimeArray somewhere within it, then it
720 // must have the storage class StorageBuffer and be decorated
721 // with Block.
722 if (value_type && value_type->opcode() == SpvOpTypeStruct) {
723 if (DoesStructContainRTA(_, value_type)) {
724 if (storage_class == SpvStorageClassStorageBuffer) {
725 if (!_.HasDecoration(value_id, SpvDecorationBlock)) {
726 return _.diag(SPV_ERROR_INVALID_ID, inst)
727 << "For WebGPU, an OpTypeStruct variable containing an "
728 << "OpTypeRuntimeArray must be decorated with Block if it "
729 << "has storage class StorageBuffer.";
730 }
731 } else {
732 return _.diag(SPV_ERROR_INVALID_ID, inst)
733 << "For WebGPU, OpTypeStruct variables containing "
734 << "OpTypeRuntimeArray must have storage class of "
735 << "StorageBuffer";
736 }
737 }
738 }
739 }
740
741 // Cooperative matrix types can only be allocated in Function or Private
742 if ((storage_class != SpvStorageClassFunction &&
743 storage_class != SpvStorageClassPrivate) &&
744 ContainsCooperativeMatrix(_, pointee)) {
745 return _.diag(SPV_ERROR_INVALID_ID, inst)
746 << "Cooperative matrix types (or types containing them) can only be "
747 "allocated "
748 << "in Function or Private storage classes or as function "
749 "parameters";
750 }
751
752 if (_.HasCapability(SpvCapabilityShader)) {
753 // Don't allow variables containing 16-bit elements without the appropriate
754 // capabilities.
755 if ((!_.HasCapability(SpvCapabilityInt16) &&
756 _.ContainsSizedIntOrFloatType(value_id, SpvOpTypeInt, 16)) ||
757 (!_.HasCapability(SpvCapabilityFloat16) &&
758 _.ContainsSizedIntOrFloatType(value_id, SpvOpTypeFloat, 16))) {
759 auto underlying_type = value_type;
760 while (underlying_type->opcode() == SpvOpTypePointer) {
761 storage_class = underlying_type->GetOperandAs<SpvStorageClass>(1u);
762 underlying_type =
763 _.FindDef(underlying_type->GetOperandAs<uint32_t>(2u));
764 }
765 bool storage_class_ok = true;
766 std::string sc_name = _.grammar().lookupOperandName(
767 SPV_OPERAND_TYPE_STORAGE_CLASS, storage_class);
768 switch (storage_class) {
769 case SpvStorageClassStorageBuffer:
770 case SpvStorageClassPhysicalStorageBufferEXT:
771 if (!_.HasCapability(SpvCapabilityStorageBuffer16BitAccess)) {
772 storage_class_ok = false;
773 }
774 break;
775 case SpvStorageClassUniform:
776 if (!_.HasCapability(
777 SpvCapabilityUniformAndStorageBuffer16BitAccess)) {
778 if (underlying_type->opcode() == SpvOpTypeArray ||
779 underlying_type->opcode() == SpvOpTypeRuntimeArray) {
780 underlying_type =
781 _.FindDef(underlying_type->GetOperandAs<uint32_t>(1u));
782 }
783 if (!_.HasCapability(SpvCapabilityStorageBuffer16BitAccess) ||
784 !_.HasDecoration(underlying_type->id(),
785 SpvDecorationBufferBlock)) {
786 storage_class_ok = false;
787 }
788 }
789 break;
790 case SpvStorageClassPushConstant:
791 if (!_.HasCapability(SpvCapabilityStoragePushConstant16)) {
792 storage_class_ok = false;
793 }
794 break;
795 case SpvStorageClassInput:
796 case SpvStorageClassOutput:
797 if (!_.HasCapability(SpvCapabilityStorageInputOutput16)) {
798 storage_class_ok = false;
799 }
800 break;
801 default:
802 return _.diag(SPV_ERROR_INVALID_ID, inst)
803 << "Cannot allocate a variable containing a 16-bit type in "
804 << sc_name << " storage class";
805 }
806 if (!storage_class_ok) {
807 return _.diag(SPV_ERROR_INVALID_ID, inst)
808 << "Allocating a variable containing a 16-bit element in "
809 << sc_name << " storage class requires an additional capability";
810 }
811 }
812 // Don't allow variables containing 8-bit elements without the appropriate
813 // capabilities.
814 if (!_.HasCapability(SpvCapabilityInt8) &&
815 _.ContainsSizedIntOrFloatType(value_id, SpvOpTypeInt, 8)) {
816 auto underlying_type = value_type;
817 while (underlying_type->opcode() == SpvOpTypePointer) {
818 storage_class = underlying_type->GetOperandAs<SpvStorageClass>(1u);
819 underlying_type =
820 _.FindDef(underlying_type->GetOperandAs<uint32_t>(2u));
821 }
822 bool storage_class_ok = true;
823 std::string sc_name = _.grammar().lookupOperandName(
824 SPV_OPERAND_TYPE_STORAGE_CLASS, storage_class);
825 switch (storage_class) {
826 case SpvStorageClassStorageBuffer:
827 case SpvStorageClassPhysicalStorageBufferEXT:
828 if (!_.HasCapability(SpvCapabilityStorageBuffer8BitAccess)) {
829 storage_class_ok = false;
830 }
831 break;
832 case SpvStorageClassUniform:
833 if (!_.HasCapability(
834 SpvCapabilityUniformAndStorageBuffer8BitAccess)) {
835 if (underlying_type->opcode() == SpvOpTypeArray ||
836 underlying_type->opcode() == SpvOpTypeRuntimeArray) {
837 underlying_type =
838 _.FindDef(underlying_type->GetOperandAs<uint32_t>(1u));
839 }
840 if (!_.HasCapability(SpvCapabilityStorageBuffer8BitAccess) ||
841 !_.HasDecoration(underlying_type->id(),
842 SpvDecorationBufferBlock)) {
843 storage_class_ok = false;
844 }
845 }
846 break;
847 case SpvStorageClassPushConstant:
848 if (!_.HasCapability(SpvCapabilityStoragePushConstant8)) {
849 storage_class_ok = false;
850 }
851 break;
852 default:
853 return _.diag(SPV_ERROR_INVALID_ID, inst)
854 << "Cannot allocate a variable containing a 8-bit type in "
855 << sc_name << " storage class";
856 }
857 if (!storage_class_ok) {
858 return _.diag(SPV_ERROR_INVALID_ID, inst)
859 << "Allocating a variable containing a 8-bit element in "
860 << sc_name << " storage class requires an additional capability";
861 }
862 }
863 }
864
865 return SPV_SUCCESS;
866}
867
868spv_result_t ValidateLoad(ValidationState_t& _, const Instruction* inst) {
869 const auto result_type = _.FindDef(inst->type_id());
870 if (!result_type) {
871 return _.diag(SPV_ERROR_INVALID_ID, inst)
872 << "OpLoad Result Type <id> '" << _.getIdName(inst->type_id())
873 << "' is not defined.";
874 }
875
876 const bool uses_variable_pointers =
877 _.features().variable_pointers ||
878 _.features().variable_pointers_storage_buffer;
879 const auto pointer_index = 2;
880 const auto pointer_id = inst->GetOperandAs<uint32_t>(pointer_index);
881 const auto pointer = _.FindDef(pointer_id);
882 if (!pointer ||
883 ((_.addressing_model() == SpvAddressingModelLogical) &&
884 ((!uses_variable_pointers &&
885 !spvOpcodeReturnsLogicalPointer(pointer->opcode())) ||
886 (uses_variable_pointers &&
887 !spvOpcodeReturnsLogicalVariablePointer(pointer->opcode()))))) {
888 return _.diag(SPV_ERROR_INVALID_ID, inst)
889 << "OpLoad Pointer <id> '" << _.getIdName(pointer_id)
890 << "' is not a logical pointer.";
891 }
892
893 const auto pointer_type = _.FindDef(pointer->type_id());
894 if (!pointer_type || pointer_type->opcode() != SpvOpTypePointer) {
895 return _.diag(SPV_ERROR_INVALID_ID, inst)
896 << "OpLoad type for pointer <id> '" << _.getIdName(pointer_id)
897 << "' is not a pointer type.";
898 }
899
900 const auto pointee_type = _.FindDef(pointer_type->GetOperandAs<uint32_t>(2));
901 if (!pointee_type || result_type->id() != pointee_type->id()) {
902 return _.diag(SPV_ERROR_INVALID_ID, inst)
903 << "OpLoad Result Type <id> '" << _.getIdName(inst->type_id())
904 << "' does not match Pointer <id> '" << _.getIdName(pointer->id())
905 << "'s type.";
906 }
907
908 if (auto error = CheckMemoryAccess(_, inst, 3)) return error;
909
910 if (_.HasCapability(SpvCapabilityShader) &&
911 _.ContainsLimitedUseIntOrFloatType(inst->type_id()) &&
912 result_type->opcode() != SpvOpTypePointer) {
913 if (result_type->opcode() != SpvOpTypeInt &&
914 result_type->opcode() != SpvOpTypeFloat &&
915 result_type->opcode() != SpvOpTypeVector &&
916 result_type->opcode() != SpvOpTypeMatrix) {
917 return _.diag(SPV_ERROR_INVALID_ID, inst)
918 << "8- or 16-bit loads must be a scalar, vector or matrix type";
919 }
920 }
921
922 return SPV_SUCCESS;
923}
924
925spv_result_t ValidateStore(ValidationState_t& _, const Instruction* inst) {
926 const bool uses_variable_pointer =
927 _.features().variable_pointers ||
928 _.features().variable_pointers_storage_buffer;
929 const auto pointer_index = 0;
930 const auto pointer_id = inst->GetOperandAs<uint32_t>(pointer_index);
931 const auto pointer = _.FindDef(pointer_id);
932 if (!pointer ||
933 (_.addressing_model() == SpvAddressingModelLogical &&
934 ((!uses_variable_pointer &&
935 !spvOpcodeReturnsLogicalPointer(pointer->opcode())) ||
936 (uses_variable_pointer &&
937 !spvOpcodeReturnsLogicalVariablePointer(pointer->opcode()))))) {
938 return _.diag(SPV_ERROR_INVALID_ID, inst)
939 << "OpStore Pointer <id> '" << _.getIdName(pointer_id)
940 << "' is not a logical pointer.";
941 }
942 const auto pointer_type = _.FindDef(pointer->type_id());
943 if (!pointer_type || pointer_type->opcode() != SpvOpTypePointer) {
944 return _.diag(SPV_ERROR_INVALID_ID, inst)
945 << "OpStore type for pointer <id> '" << _.getIdName(pointer_id)
946 << "' is not a pointer type.";
947 }
948 const auto type_id = pointer_type->GetOperandAs<uint32_t>(2);
949 const auto type = _.FindDef(type_id);
950 if (!type || SpvOpTypeVoid == type->opcode()) {
951 return _.diag(SPV_ERROR_INVALID_ID, inst)
952 << "OpStore Pointer <id> '" << _.getIdName(pointer_id)
953 << "'s type is void.";
954 }
955
956 // validate storage class
957 {
958 uint32_t data_type;
959 uint32_t storage_class;
960 if (!_.GetPointerTypeInfo(pointer_type->id(), &data_type, &storage_class)) {
961 return _.diag(SPV_ERROR_INVALID_ID, inst)
962 << "OpStore Pointer <id> '" << _.getIdName(pointer_id)
963 << "' is not pointer type";
964 }
965
966 if (storage_class == SpvStorageClassUniformConstant ||
967 storage_class == SpvStorageClassInput ||
968 storage_class == SpvStorageClassPushConstant) {
969 return _.diag(SPV_ERROR_INVALID_ID, inst)
970 << "OpStore Pointer <id> '" << _.getIdName(pointer_id)
971 << "' storage class is read-only";
972 }
973
974 if (spvIsVulkanEnv(_.context()->target_env) &&
975 storage_class == SpvStorageClassUniform) {
976 auto base_ptr = _.TracePointer(pointer);
977 if (base_ptr->opcode() == SpvOpVariable) {
978 // If it's not a variable a different check should catch the problem.
979 auto base_type = _.FindDef(base_ptr->GetOperandAs<uint32_t>(0));
980 // Get the pointed-to type.
981 base_type = _.FindDef(base_type->GetOperandAs<uint32_t>(2u));
982 if (base_type->opcode() == SpvOpTypeArray ||
983 base_type->opcode() == SpvOpTypeRuntimeArray) {
984 base_type = _.FindDef(base_type->GetOperandAs<uint32_t>(1u));
985 }
986 if (_.HasDecoration(base_type->id(), SpvDecorationBlock)) {
987 return _.diag(SPV_ERROR_INVALID_ID, inst)
988 << "In the Vulkan environment, cannot store to Uniform Blocks";
989 }
990 }
991 }
992 }
993
994 const auto object_index = 1;
995 const auto object_id = inst->GetOperandAs<uint32_t>(object_index);
996 const auto object = _.FindDef(object_id);
997 if (!object || !object->type_id()) {
998 return _.diag(SPV_ERROR_INVALID_ID, inst)
999 << "OpStore Object <id> '" << _.getIdName(object_id)
1000 << "' is not an object.";
1001 }
1002 const auto object_type = _.FindDef(object->type_id());
1003 if (!object_type || SpvOpTypeVoid == object_type->opcode()) {
1004 return _.diag(SPV_ERROR_INVALID_ID, inst)
1005 << "OpStore Object <id> '" << _.getIdName(object_id)
1006 << "'s type is void.";
1007 }
1008
1009 if (type->id() != object_type->id()) {
1010 if (!_.options()->relax_struct_store || type->opcode() != SpvOpTypeStruct ||
1011 object_type->opcode() != SpvOpTypeStruct) {
1012 return _.diag(SPV_ERROR_INVALID_ID, inst)
1013 << "OpStore Pointer <id> '" << _.getIdName(pointer_id)
1014 << "'s type does not match Object <id> '"
1015 << _.getIdName(object->id()) << "'s type.";
1016 }
1017
1018 // TODO: Check for layout compatible matricies and arrays as well.
1019 if (!AreLayoutCompatibleStructs(_, type, object_type)) {
1020 return _.diag(SPV_ERROR_INVALID_ID, inst)
1021 << "OpStore Pointer <id> '" << _.getIdName(pointer_id)
1022 << "'s layout does not match Object <id> '"
1023 << _.getIdName(object->id()) << "'s layout.";
1024 }
1025 }
1026
1027 if (auto error = CheckMemoryAccess(_, inst, 2)) return error;
1028
1029 if (_.HasCapability(SpvCapabilityShader) &&
1030 _.ContainsLimitedUseIntOrFloatType(inst->type_id()) &&
1031 object_type->opcode() != SpvOpTypePointer) {
1032 if (object_type->opcode() != SpvOpTypeInt &&
1033 object_type->opcode() != SpvOpTypeFloat &&
1034 object_type->opcode() != SpvOpTypeVector &&
1035 object_type->opcode() != SpvOpTypeMatrix) {
1036 return _.diag(SPV_ERROR_INVALID_ID, inst)
1037 << "8- or 16-bit stores must be a scalar, vector or matrix type";
1038 }
1039 }
1040
1041 return SPV_SUCCESS;
1042}
1043
1044spv_result_t ValidateCopyMemoryMemoryAccess(ValidationState_t& _,
1045 const Instruction* inst) {
1046 assert(inst->opcode() == SpvOpCopyMemory ||
1047 inst->opcode() == SpvOpCopyMemorySized);
1048 const uint32_t first_access_index = inst->opcode() == SpvOpCopyMemory ? 2 : 3;
1049 if (inst->operands().size() > first_access_index) {
1050 if (auto error = CheckMemoryAccess(_, inst, first_access_index))
1051 return error;
1052
1053 const auto first_access = inst->GetOperandAs<uint32_t>(first_access_index);
1054 const uint32_t second_access_index =
1055 first_access_index + MemoryAccessNumWords(first_access);
1056 if (inst->operands().size() > second_access_index) {
1057 if (_.features().copy_memory_permits_two_memory_accesses) {
1058 if (auto error = CheckMemoryAccess(_, inst, second_access_index))
1059 return error;
1060
1061 // In the two-access form in SPIR-V 1.4 and later:
1062 // - the first is the target (write) access and it can't have
1063 // make-visible.
1064 // - the second is the source (read) access and it can't have
1065 // make-available.
1066 if (first_access & SpvMemoryAccessMakePointerVisibleKHRMask) {
1067 return _.diag(SPV_ERROR_INVALID_DATA, inst)
1068 << "Target memory access must not include "
1069 "MakePointerVisibleKHR";
1070 }
1071 const auto second_access =
1072 inst->GetOperandAs<uint32_t>(second_access_index);
1073 if (second_access & SpvMemoryAccessMakePointerAvailableKHRMask) {
1074 return _.diag(SPV_ERROR_INVALID_DATA, inst)
1075 << "Source memory access must not include "
1076 "MakePointerAvailableKHR";
1077 }
1078 } else {
1079 return _.diag(SPV_ERROR_INVALID_DATA, inst)
1080 << spvOpcodeString(static_cast<SpvOp>(inst->opcode()))
1081 << " with two memory access operands requires SPIR-V 1.4 or "
1082 "later";
1083 }
1084 }
1085 }
1086 return SPV_SUCCESS;
1087}
1088
1089spv_result_t ValidateCopyMemory(ValidationState_t& _, const Instruction* inst) {
1090 const auto target_index = 0;
1091 const auto target_id = inst->GetOperandAs<uint32_t>(target_index);
1092 const auto target = _.FindDef(target_id);
1093 if (!target) {
1094 return _.diag(SPV_ERROR_INVALID_ID, inst)
1095 << "Target operand <id> '" << _.getIdName(target_id)
1096 << "' is not defined.";
1097 }
1098
1099 const auto source_index = 1;
1100 const auto source_id = inst->GetOperandAs<uint32_t>(source_index);
1101 const auto source = _.FindDef(source_id);
1102 if (!source) {
1103 return _.diag(SPV_ERROR_INVALID_ID, inst)
1104 << "Source operand <id> '" << _.getIdName(source_id)
1105 << "' is not defined.";
1106 }
1107
1108 const auto target_pointer_type = _.FindDef(target->type_id());
1109 if (!target_pointer_type ||
1110 target_pointer_type->opcode() != SpvOpTypePointer) {
1111 return _.diag(SPV_ERROR_INVALID_ID, inst)
1112 << "Target operand <id> '" << _.getIdName(target_id)
1113 << "' is not a pointer.";
1114 }
1115
1116 const auto source_pointer_type = _.FindDef(source->type_id());
1117 if (!source_pointer_type ||
1118 source_pointer_type->opcode() != SpvOpTypePointer) {
1119 return _.diag(SPV_ERROR_INVALID_ID, inst)
1120 << "Source operand <id> '" << _.getIdName(source_id)
1121 << "' is not a pointer.";
1122 }
1123
1124 if (inst->opcode() == SpvOpCopyMemory) {
1125 const auto target_type =
1126 _.FindDef(target_pointer_type->GetOperandAs<uint32_t>(2));
1127 if (!target_type || target_type->opcode() == SpvOpTypeVoid) {
1128 return _.diag(SPV_ERROR_INVALID_ID, inst)
1129 << "Target operand <id> '" << _.getIdName(target_id)
1130 << "' cannot be a void pointer.";
1131 }
1132
1133 const auto source_type =
1134 _.FindDef(source_pointer_type->GetOperandAs<uint32_t>(2));
1135 if (!source_type || source_type->opcode() == SpvOpTypeVoid) {
1136 return _.diag(SPV_ERROR_INVALID_ID, inst)
1137 << "Source operand <id> '" << _.getIdName(source_id)
1138 << "' cannot be a void pointer.";
1139 }
1140
1141 if (target_type->id() != source_type->id()) {
1142 return _.diag(SPV_ERROR_INVALID_ID, inst)
1143 << "Target <id> '" << _.getIdName(source_id)
1144 << "'s type does not match Source <id> '"
1145 << _.getIdName(source_type->id()) << "'s type.";
1146 }
1147
1148 if (auto error = CheckMemoryAccess(_, inst, 2)) return error;
1149 } else {
1150 const auto size_id = inst->GetOperandAs<uint32_t>(2);
1151 const auto size = _.FindDef(size_id);
1152 if (!size) {
1153 return _.diag(SPV_ERROR_INVALID_ID, inst)
1154 << "Size operand <id> '" << _.getIdName(size_id)
1155 << "' is not defined.";
1156 }
1157
1158 const auto size_type = _.FindDef(size->type_id());
1159 if (!_.IsIntScalarType(size_type->id())) {
1160 return _.diag(SPV_ERROR_INVALID_ID, inst)
1161 << "Size operand <id> '" << _.getIdName(size_id)
1162 << "' must be a scalar integer type.";
1163 }
1164
1165 bool is_zero = true;
1166 switch (size->opcode()) {
1167 case SpvOpConstantNull:
1168 return _.diag(SPV_ERROR_INVALID_ID, inst)
1169 << "Size operand <id> '" << _.getIdName(size_id)
1170 << "' cannot be a constant zero.";
1171 case SpvOpConstant:
1172 if (size_type->word(3) == 1 &&
1173 size->word(size->words().size() - 1) & 0x80000000) {
1174 return _.diag(SPV_ERROR_INVALID_ID, inst)
1175 << "Size operand <id> '" << _.getIdName(size_id)
1176 << "' cannot have the sign bit set to 1.";
1177 }
1178 for (size_t i = 3; is_zero && i < size->words().size(); ++i) {
1179 is_zero &= (size->word(i) == 0);
1180 }
1181 if (is_zero) {
1182 return _.diag(SPV_ERROR_INVALID_ID, inst)
1183 << "Size operand <id> '" << _.getIdName(size_id)
1184 << "' cannot be a constant zero.";
1185 }
1186 break;
1187 default:
1188 // Cannot infer any other opcodes.
1189 break;
1190 }
1191
1192 if (auto error = CheckMemoryAccess(_, inst, 3)) return error;
1193 }
1194 if (auto error = ValidateCopyMemoryMemoryAccess(_, inst)) return error;
1195
1196 // Get past the pointers to avoid checking a pointer copy.
1197 auto sub_type = _.FindDef(target_pointer_type->GetOperandAs<uint32_t>(2));
1198 while (sub_type->opcode() == SpvOpTypePointer) {
1199 sub_type = _.FindDef(sub_type->GetOperandAs<uint32_t>(2));
1200 }
1201 if (_.HasCapability(SpvCapabilityShader) &&
1202 _.ContainsLimitedUseIntOrFloatType(sub_type->id())) {
1203 return _.diag(SPV_ERROR_INVALID_ID, inst)
1204 << "Cannot copy memory of objects containing 8- or 16-bit types";
1205 }
1206
1207 return SPV_SUCCESS;
1208}
1209
1210spv_result_t ValidateAccessChain(ValidationState_t& _,
1211 const Instruction* inst) {
1212 std::string instr_name =
1213 "Op" + std::string(spvOpcodeString(static_cast<SpvOp>(inst->opcode())));
1214
1215 // The result type must be OpTypePointer.
1216 auto result_type = _.FindDef(inst->type_id());
1217 if (SpvOpTypePointer != result_type->opcode()) {
1218 return _.diag(SPV_ERROR_INVALID_ID, inst)
1219 << "The Result Type of " << instr_name << " <id> '"
1220 << _.getIdName(inst->id()) << "' must be OpTypePointer. Found Op"
1221 << spvOpcodeString(static_cast<SpvOp>(result_type->opcode())) << ".";
1222 }
1223
1224 // Result type is a pointer. Find out what it's pointing to.
1225 // This will be used to make sure the indexing results in the same type.
1226 // OpTypePointer word 3 is the type being pointed to.
1227 const auto result_type_pointee = _.FindDef(result_type->word(3));
1228
1229 // Base must be a pointer, pointing to the base of a composite object.
1230 const auto base_index = 2;
1231 const auto base_id = inst->GetOperandAs<uint32_t>(base_index);
1232 const auto base = _.FindDef(base_id);
1233 const auto base_type = _.FindDef(base->type_id());
1234 if (!base_type || SpvOpTypePointer != base_type->opcode()) {
1235 return _.diag(SPV_ERROR_INVALID_ID, inst)
1236 << "The Base <id> '" << _.getIdName(base_id) << "' in " << instr_name
1237 << " instruction must be a pointer.";
1238 }
1239
1240 // The result pointer storage class and base pointer storage class must match.
1241 // Word 2 of OpTypePointer is the Storage Class.
1242 auto result_type_storage_class = result_type->word(2);
1243 auto base_type_storage_class = base_type->word(2);
1244 if (result_type_storage_class != base_type_storage_class) {
1245 return _.diag(SPV_ERROR_INVALID_ID, inst)
1246 << "The result pointer storage class and base "
1247 "pointer storage class in "
1248 << instr_name << " do not match.";
1249 }
1250
1251 // The type pointed to by OpTypePointer (word 3) must be a composite type.
1252 auto type_pointee = _.FindDef(base_type->word(3));
1253
1254 // Check Universal Limit (SPIR-V Spec. Section 2.17).
1255 // The number of indexes passed to OpAccessChain may not exceed 255
1256 // The instruction includes 4 words + N words (for N indexes)
1257 size_t num_indexes = inst->words().size() - 4;
1258 if (inst->opcode() == SpvOpPtrAccessChain ||
1259 inst->opcode() == SpvOpInBoundsPtrAccessChain) {
1260 // In pointer access chains, the element operand is required, but not
1261 // counted as an index.
1262 --num_indexes;
1263 }
1264 const size_t num_indexes_limit =
1265 _.options()->universal_limits_.max_access_chain_indexes;
1266 if (num_indexes > num_indexes_limit) {
1267 return _.diag(SPV_ERROR_INVALID_ID, inst)
1268 << "The number of indexes in " << instr_name << " may not exceed "
1269 << num_indexes_limit << ". Found " << num_indexes << " indexes.";
1270 }
1271 // Indexes walk the type hierarchy to the desired depth, potentially down to
1272 // scalar granularity. The first index in Indexes will select the top-level
1273 // member/element/component/element of the base composite. All composite
1274 // constituents use zero-based numbering, as described by their OpType...
1275 // instruction. The second index will apply similarly to that result, and so
1276 // on. Once any non-composite type is reached, there must be no remaining
1277 // (unused) indexes.
1278 auto starting_index = 4;
1279 if (inst->opcode() == SpvOpPtrAccessChain ||
1280 inst->opcode() == SpvOpInBoundsPtrAccessChain) {
1281 ++starting_index;
1282 }
1283 for (size_t i = starting_index; i < inst->words().size(); ++i) {
1284 const uint32_t cur_word = inst->words()[i];
1285 // Earlier ID checks ensure that cur_word definition exists.
1286 auto cur_word_instr = _.FindDef(cur_word);
1287 // The index must be a scalar integer type (See OpAccessChain in the Spec.)
1288 auto index_type = _.FindDef(cur_word_instr->type_id());
1289 if (!index_type || SpvOpTypeInt != index_type->opcode()) {
1290 return _.diag(SPV_ERROR_INVALID_ID, inst)
1291 << "Indexes passed to " << instr_name
1292 << " must be of type integer.";
1293 }
1294 switch (type_pointee->opcode()) {
1295 case SpvOpTypeMatrix:
1296 case SpvOpTypeVector:
1297 case SpvOpTypeCooperativeMatrixNV:
1298 case SpvOpTypeArray:
1299 case SpvOpTypeRuntimeArray: {
1300 // In OpTypeMatrix, OpTypeVector, SpvOpTypeCooperativeMatrixNV,
1301 // OpTypeArray, and OpTypeRuntimeArray, word 2 is the Element Type.
1302 type_pointee = _.FindDef(type_pointee->word(2));
1303 break;
1304 }
1305 case SpvOpTypeStruct: {
1306 // In case of structures, there is an additional constraint on the
1307 // index: the index must be an OpConstant.
1308 if (SpvOpConstant != cur_word_instr->opcode()) {
1309 return _.diag(SPV_ERROR_INVALID_ID, cur_word_instr)
1310 << "The <id> passed to " << instr_name
1311 << " to index into a "
1312 "structure must be an OpConstant.";
1313 }
1314 // Get the index value from the OpConstant (word 3 of OpConstant).
1315 // OpConstant could be a signed integer. But it's okay to treat it as
1316 // unsigned because a negative constant int would never be seen as
1317 // correct as a struct offset, since structs can't have more than 2
1318 // billion members.
1319 const uint32_t cur_index = cur_word_instr->word(3);
1320 // The index points to the struct member we want, therefore, the index
1321 // should be less than the number of struct members.
1322 const uint32_t num_struct_members =
1323 static_cast<uint32_t>(type_pointee->words().size() - 2);
1324 if (cur_index >= num_struct_members) {
1325 return _.diag(SPV_ERROR_INVALID_ID, cur_word_instr)
1326 << "Index is out of bounds: " << instr_name
1327 << " can not find index " << cur_index
1328 << " into the structure <id> '"
1329 << _.getIdName(type_pointee->id()) << "'. This structure has "
1330 << num_struct_members << " members. Largest valid index is "
1331 << num_struct_members - 1 << ".";
1332 }
1333 // Struct members IDs start at word 2 of OpTypeStruct.
1334 auto structMemberId = type_pointee->word(cur_index + 2);
1335 type_pointee = _.FindDef(structMemberId);
1336 break;
1337 }
1338 default: {
1339 // Give an error. reached non-composite type while indexes still remain.
1340 return _.diag(SPV_ERROR_INVALID_ID, cur_word_instr)
1341 << instr_name
1342 << " reached non-composite type while indexes "
1343 "still remain to be traversed.";
1344 }
1345 }
1346 }
1347 // At this point, we have fully walked down from the base using the indeces.
1348 // The type being pointed to should be the same as the result type.
1349 if (type_pointee->id() != result_type_pointee->id()) {
1350 return _.diag(SPV_ERROR_INVALID_ID, inst)
1351 << instr_name << " result type (Op"
1352 << spvOpcodeString(static_cast<SpvOp>(result_type_pointee->opcode()))
1353 << ") does not match the type that results from indexing into the "
1354 "base "
1355 "<id> (Op"
1356 << spvOpcodeString(static_cast<SpvOp>(type_pointee->opcode()))
1357 << ").";
1358 }
1359
1360 return SPV_SUCCESS;
1361}
1362
1363spv_result_t ValidatePtrAccessChain(ValidationState_t& _,
1364 const Instruction* inst) {
1365 if (_.addressing_model() == SpvAddressingModelLogical) {
1366 if (!_.features().variable_pointers &&
1367 !_.features().variable_pointers_storage_buffer) {
1368 return _.diag(SPV_ERROR_INVALID_DATA, inst)
1369 << "Generating variable pointers requires capability "
1370 << "VariablePointers or VariablePointersStorageBuffer";
1371 }
1372 }
1373 return ValidateAccessChain(_, inst);
1374}
1375
1376spv_result_t ValidateArrayLength(ValidationState_t& state,
1377 const Instruction* inst) {
1378 std::string instr_name =
1379 "Op" + std::string(spvOpcodeString(static_cast<SpvOp>(inst->opcode())));
1380
1381 // Result type must be a 32-bit unsigned int.
1382 auto result_type = state.FindDef(inst->type_id());
1383 if (result_type->opcode() != SpvOpTypeInt ||
1384 result_type->GetOperandAs<uint32_t>(1) != 32 ||
1385 result_type->GetOperandAs<uint32_t>(2) != 0) {
1386 return state.diag(SPV_ERROR_INVALID_ID, inst)
1387 << "The Result Type of " << instr_name << " <id> '"
1388 << state.getIdName(inst->id())
1389 << "' must be OpTypeInt with width 32 and signedness 0.";
1390 }
1391
1392 // The structure that is passed in must be an pointer to a structure, whose
1393 // last element is a runtime array.
1394 auto pointer = state.FindDef(inst->GetOperandAs<uint32_t>(2));
1395 auto pointer_type = state.FindDef(pointer->type_id());
1396 if (pointer_type->opcode() != SpvOpTypePointer) {
1397 return state.diag(SPV_ERROR_INVALID_ID, inst)
1398 << "The Struture's type in " << instr_name << " <id> '"
1399 << state.getIdName(inst->id())
1400 << "' must be a pointer to an OpTypeStruct.";
1401 }
1402
1403 auto structure_type = state.FindDef(pointer_type->GetOperandAs<uint32_t>(2));
1404 if (structure_type->opcode() != SpvOpTypeStruct) {
1405 return state.diag(SPV_ERROR_INVALID_ID, inst)
1406 << "The Struture's type in " << instr_name << " <id> '"
1407 << state.getIdName(inst->id())
1408 << "' must be a pointer to an OpTypeStruct.";
1409 }
1410
1411 auto num_of_members = structure_type->operands().size() - 1;
1412 auto last_member =
1413 state.FindDef(structure_type->GetOperandAs<uint32_t>(num_of_members));
1414 if (last_member->opcode() != SpvOpTypeRuntimeArray) {
1415 return state.diag(SPV_ERROR_INVALID_ID, inst)
1416 << "The Struture's last member in " << instr_name << " <id> '"
1417 << state.getIdName(inst->id()) << "' must be an OpTypeRuntimeArray.";
1418 }
1419
1420 // The array member must the the index of the last element (the run time
1421 // array).
1422 if (inst->GetOperandAs<uint32_t>(3) != num_of_members - 1) {
1423 return state.diag(SPV_ERROR_INVALID_ID, inst)
1424 << "The array member in " << instr_name << " <id> '"
1425 << state.getIdName(inst->id())
1426 << "' must be an the last member of the struct.";
1427 }
1428 return SPV_SUCCESS;
1429}
1430
1431spv_result_t ValidateCooperativeMatrixLengthNV(ValidationState_t& state,
1432 const Instruction* inst) {
1433 std::string instr_name =
1434 "Op" + std::string(spvOpcodeString(static_cast<SpvOp>(inst->opcode())));
1435
1436 // Result type must be a 32-bit unsigned int.
1437 auto result_type = state.FindDef(inst->type_id());
1438 if (result_type->opcode() != SpvOpTypeInt ||
1439 result_type->GetOperandAs<uint32_t>(1) != 32 ||
1440 result_type->GetOperandAs<uint32_t>(2) != 0) {
1441 return state.diag(SPV_ERROR_INVALID_ID, inst)
1442 << "The Result Type of " << instr_name << " <id> '"
1443 << state.getIdName(inst->id())
1444 << "' must be OpTypeInt with width 32 and signedness 0.";
1445 }
1446
1447 auto type_id = inst->GetOperandAs<uint32_t>(2);
1448 auto type = state.FindDef(type_id);
1449 if (type->opcode() != SpvOpTypeCooperativeMatrixNV) {
1450 return state.diag(SPV_ERROR_INVALID_ID, inst)
1451 << "The type in " << instr_name << " <id> '"
1452 << state.getIdName(type_id)
1453 << "' must be OpTypeCooperativeMatrixNV.";
1454 }
1455 return SPV_SUCCESS;
1456}
1457
1458spv_result_t ValidateCooperativeMatrixLoadStoreNV(ValidationState_t& _,
1459 const Instruction* inst) {
1460 uint32_t type_id;
1461 const char* opname;
1462 if (inst->opcode() == SpvOpCooperativeMatrixLoadNV) {
1463 type_id = inst->type_id();
1464 opname = "SpvOpCooperativeMatrixLoadNV";
1465 } else {
1466 // get Object operand's type
1467 type_id = _.FindDef(inst->GetOperandAs<uint32_t>(1))->type_id();
1468 opname = "SpvOpCooperativeMatrixStoreNV";
1469 }
1470
1471 auto matrix_type = _.FindDef(type_id);
1472
1473 if (matrix_type->opcode() != SpvOpTypeCooperativeMatrixNV) {
1474 if (inst->opcode() == SpvOpCooperativeMatrixLoadNV) {
1475 return _.diag(SPV_ERROR_INVALID_ID, inst)
1476 << "SpvOpCooperativeMatrixLoadNV Result Type <id> '"
1477 << _.getIdName(type_id) << "' is not a cooperative matrix type.";
1478 } else {
1479 return _.diag(SPV_ERROR_INVALID_ID, inst)
1480 << "SpvOpCooperativeMatrixStoreNV Object type <id> '"
1481 << _.getIdName(type_id) << "' is not a cooperative matrix type.";
1482 }
1483 }
1484
1485 const bool uses_variable_pointers =
1486 _.features().variable_pointers ||
1487 _.features().variable_pointers_storage_buffer;
1488 const auto pointer_index =
1489 (inst->opcode() == SpvOpCooperativeMatrixLoadNV) ? 2u : 0u;
1490 const auto pointer_id = inst->GetOperandAs<uint32_t>(pointer_index);
1491 const auto pointer = _.FindDef(pointer_id);
1492 if (!pointer ||
1493 ((_.addressing_model() == SpvAddressingModelLogical) &&
1494 ((!uses_variable_pointers &&
1495 !spvOpcodeReturnsLogicalPointer(pointer->opcode())) ||
1496 (uses_variable_pointers &&
1497 !spvOpcodeReturnsLogicalVariablePointer(pointer->opcode()))))) {
1498 return _.diag(SPV_ERROR_INVALID_ID, inst)
1499 << opname << " Pointer <id> '" << _.getIdName(pointer_id)
1500 << "' is not a logical pointer.";
1501 }
1502
1503 const auto pointer_type_id = pointer->type_id();
1504 const auto pointer_type = _.FindDef(pointer_type_id);
1505 if (!pointer_type || pointer_type->opcode() != SpvOpTypePointer) {
1506 return _.diag(SPV_ERROR_INVALID_ID, inst)
1507 << opname << " type for pointer <id> '" << _.getIdName(pointer_id)
1508 << "' is not a pointer type.";
1509 }
1510
1511 const auto storage_class_index = 1u;
1512 const auto storage_class =
1513 pointer_type->GetOperandAs<uint32_t>(storage_class_index);
1514
1515 if (storage_class != SpvStorageClassWorkgroup &&
1516 storage_class != SpvStorageClassStorageBuffer &&
1517 storage_class != SpvStorageClassPhysicalStorageBufferEXT) {
1518 return _.diag(SPV_ERROR_INVALID_ID, inst)
1519 << opname << " storage class for pointer type <id> '"
1520 << _.getIdName(pointer_type_id)
1521 << "' is not Workgroup or StorageBuffer.";
1522 }
1523
1524 const auto pointee_id = pointer_type->GetOperandAs<uint32_t>(2);
1525 const auto pointee_type = _.FindDef(pointee_id);
1526 if (!pointee_type || !(_.IsIntScalarOrVectorType(pointee_id) ||
1527 _.IsFloatScalarOrVectorType(pointee_id))) {
1528 return _.diag(SPV_ERROR_INVALID_ID, inst)
1529 << opname << " Pointer <id> '" << _.getIdName(pointer->id())
1530 << "'s Type must be a scalar or vector type.";
1531 }
1532
1533 const auto stride_index =
1534 (inst->opcode() == SpvOpCooperativeMatrixLoadNV) ? 3u : 2u;
1535 const auto stride_id = inst->GetOperandAs<uint32_t>(stride_index);
1536 const auto stride = _.FindDef(stride_id);
1537 if (!stride || !_.IsIntScalarType(stride->type_id())) {
1538 return _.diag(SPV_ERROR_INVALID_ID, inst)
1539 << "Stride operand <id> '" << _.getIdName(stride_id)
1540 << "' must be a scalar integer type.";
1541 }
1542
1543 const auto colmajor_index =
1544 (inst->opcode() == SpvOpCooperativeMatrixLoadNV) ? 4u : 3u;
1545 const auto colmajor_id = inst->GetOperandAs<uint32_t>(colmajor_index);
1546 const auto colmajor = _.FindDef(colmajor_id);
1547 if (!colmajor || !_.IsBoolScalarType(colmajor->type_id()) ||
1548 !(spvOpcodeIsConstant(colmajor->opcode()) ||
1549 spvOpcodeIsSpecConstant(colmajor->opcode()))) {
1550 return _.diag(SPV_ERROR_INVALID_ID, inst)
1551 << "Column Major operand <id> '" << _.getIdName(colmajor_id)
1552 << "' must be a boolean constant instruction.";
1553 }
1554
1555 const auto memory_access_index =
1556 (inst->opcode() == SpvOpCooperativeMatrixLoadNV) ? 5u : 4u;
1557 if (inst->operands().size() > memory_access_index) {
1558 if (auto error = CheckMemoryAccess(_, inst, memory_access_index))
1559 return error;
1560 }
1561
1562 return SPV_SUCCESS;
1563}
1564
1565spv_result_t ValidatePtrComparison(ValidationState_t& _,
1566 const Instruction* inst) {
1567 if (_.addressing_model() == SpvAddressingModelLogical &&
1568 !_.features().variable_pointers_storage_buffer) {
1569 return _.diag(SPV_ERROR_INVALID_ID, inst)
1570 << "Instruction cannot be used without a variable pointers "
1571 "capability";
1572 }
1573
1574 const auto result_type = _.FindDef(inst->type_id());
1575 if (inst->opcode() == SpvOpPtrDiff) {
1576 if (!result_type || result_type->opcode() != SpvOpTypeInt) {
1577 return _.diag(SPV_ERROR_INVALID_ID, inst)
1578 << "Result Type must be an integer scalar";
1579 }
1580 } else {
1581 if (!result_type || result_type->opcode() != SpvOpTypeBool) {
1582 return _.diag(SPV_ERROR_INVALID_ID, inst)
1583 << "Result Type must be OpTypeBool";
1584 }
1585 }
1586
1587 const auto op1 = _.FindDef(inst->GetOperandAs<uint32_t>(2u));
1588 const auto op2 = _.FindDef(inst->GetOperandAs<uint32_t>(3u));
1589 if (!op1 || !op2 || op1->type_id() != op2->type_id()) {
1590 return _.diag(SPV_ERROR_INVALID_ID, inst)
1591 << "The types of Operand 1 and Operand 2 must match";
1592 }
1593 const auto op1_type = _.FindDef(op1->type_id());
1594 if (!op1_type || op1_type->opcode() != SpvOpTypePointer) {
1595 return _.diag(SPV_ERROR_INVALID_ID, inst)
1596 << "Operand type must be a pointer";
1597 }
1598
1599 SpvStorageClass sc = op1_type->GetOperandAs<SpvStorageClass>(1u);
1600 if (_.addressing_model() == SpvAddressingModelLogical) {
1601 if (sc != SpvStorageClassWorkgroup && sc != SpvStorageClassStorageBuffer) {
1602 return _.diag(SPV_ERROR_INVALID_ID, inst)
1603 << "Invalid pointer storage class";
1604 }
1605
1606 if (sc == SpvStorageClassWorkgroup && !_.features().variable_pointers) {
1607 return _.diag(SPV_ERROR_INVALID_ID, inst)
1608 << "Workgroup storage class pointer requires VariablePointers "
1609 "capability to be specified";
1610 }
1611 } else if (sc == SpvStorageClassPhysicalStorageBuffer) {
1612 return _.diag(SPV_ERROR_INVALID_ID, inst)
1613 << "Cannot use a pointer in the PhysicalStorageBuffer storage class";
1614 }
1615
1616 return SPV_SUCCESS;
1617}
1618
1619} // namespace
1620
1621spv_result_t MemoryPass(ValidationState_t& _, const Instruction* inst) {
1622 switch (inst->opcode()) {
1623 case SpvOpVariable:
1624 if (auto error = ValidateVariable(_, inst)) return error;
1625 break;
1626 case SpvOpLoad:
1627 if (auto error = ValidateLoad(_, inst)) return error;
1628 break;
1629 case SpvOpStore:
1630 if (auto error = ValidateStore(_, inst)) return error;
1631 break;
1632 case SpvOpCopyMemory:
1633 case SpvOpCopyMemorySized:
1634 if (auto error = ValidateCopyMemory(_, inst)) return error;
1635 break;
1636 case SpvOpPtrAccessChain:
1637 if (auto error = ValidatePtrAccessChain(_, inst)) return error;
1638 break;
1639 case SpvOpAccessChain:
1640 case SpvOpInBoundsAccessChain:
1641 case SpvOpInBoundsPtrAccessChain:
1642 if (auto error = ValidateAccessChain(_, inst)) return error;
1643 break;
1644 case SpvOpArrayLength:
1645 if (auto error = ValidateArrayLength(_, inst)) return error;
1646 break;
1647 case SpvOpCooperativeMatrixLoadNV:
1648 case SpvOpCooperativeMatrixStoreNV:
1649 if (auto error = ValidateCooperativeMatrixLoadStoreNV(_, inst))
1650 return error;
1651 break;
1652 case SpvOpCooperativeMatrixLengthNV:
1653 if (auto error = ValidateCooperativeMatrixLengthNV(_, inst)) return error;
1654 break;
1655 case SpvOpPtrEqual:
1656 case SpvOpPtrNotEqual:
1657 case SpvOpPtrDiff:
1658 if (auto error = ValidatePtrComparison(_, inst)) return error;
1659 break;
1660 case SpvOpImageTexelPointer:
1661 case SpvOpGenericPtrMemSemantics:
1662 default:
1663 break;
1664 }
1665
1666 return SPV_SUCCESS;
1667}
1668} // namespace val
1669} // namespace spvtools
1670