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 #include <algorithm>
16 #include <cassert>
17 #include <string>
18 #include <tuple>
19 #include <unordered_map>
20 #include <unordered_set>
21 #include <utility>
22 #include <vector>
23
24 #include "source/diagnostic.h"
25 #include "source/opcode.h"
26 #include "source/spirv_constant.h"
27 #include "source/spirv_target_env.h"
28 #include "source/spirv_validator_options.h"
29 #include "source/util/string_utils.h"
30 #include "source/val/validate_scopes.h"
31 #include "source/val/validation_state.h"
32
33 namespace spvtools {
34 namespace val {
35 namespace {
36
37 // Distinguish between row and column major matrix layouts.
38 enum MatrixLayout { kRowMajor, kColumnMajor };
39
40 // A functor for hashing a pair of integers.
41 struct PairHash {
operator ()spvtools::val::__anon8a60fb0c0111::PairHash42 std::size_t operator()(const std::pair<uint32_t, uint32_t> pair) const {
43 const uint32_t a = pair.first;
44 const uint32_t b = pair.second;
45 const uint32_t rotated_b = (b >> 2) | ((b & 3) << 30);
46 return a ^ rotated_b;
47 }
48 };
49
50 // Struct member layout attributes that are inherited through arrays.
51 struct LayoutConstraints {
LayoutConstraintsspvtools::val::__anon8a60fb0c0111::LayoutConstraints52 explicit LayoutConstraints(
53 MatrixLayout the_majorness = MatrixLayout::kColumnMajor,
54 uint32_t stride = 0)
55 : majorness(the_majorness), matrix_stride(stride) {}
56 MatrixLayout majorness;
57 uint32_t matrix_stride;
58 };
59
60 // A type for mapping (struct id, member id) to layout constraints.
61 using MemberConstraints = std::unordered_map<std::pair<uint32_t, uint32_t>,
62 LayoutConstraints, PairHash>;
63
64 // Returns the array stride of the given array type.
GetArrayStride(uint32_t array_id,ValidationState_t & vstate)65 uint32_t GetArrayStride(uint32_t array_id, ValidationState_t& vstate) {
66 for (auto& decoration : vstate.id_decorations(array_id)) {
67 if (spv::Decoration::ArrayStride == decoration.dec_type()) {
68 return decoration.params()[0];
69 }
70 }
71 return 0;
72 }
73
74 // Returns true if the given variable has a BuiltIn decoration.
isBuiltInVar(uint32_t var_id,ValidationState_t & vstate)75 bool isBuiltInVar(uint32_t var_id, ValidationState_t& vstate) {
76 const auto& decorations = vstate.id_decorations(var_id);
77 return std::any_of(decorations.begin(), decorations.end(),
78 [](const Decoration& d) {
79 return spv::Decoration::BuiltIn == d.dec_type();
80 });
81 }
82
83 // Returns true if the given structure type has any members with BuiltIn
84 // decoration.
isBuiltInStruct(uint32_t struct_id,ValidationState_t & vstate)85 bool isBuiltInStruct(uint32_t struct_id, ValidationState_t& vstate) {
86 const auto& decorations = vstate.id_decorations(struct_id);
87 return std::any_of(
88 decorations.begin(), decorations.end(), [](const Decoration& d) {
89 return spv::Decoration::BuiltIn == d.dec_type() &&
90 Decoration::kInvalidMember != d.struct_member_index();
91 });
92 }
93
94 // Returns true if the given structure type has a Block decoration.
isBlock(uint32_t struct_id,ValidationState_t & vstate)95 bool isBlock(uint32_t struct_id, ValidationState_t& vstate) {
96 const auto& decorations = vstate.id_decorations(struct_id);
97 return std::any_of(decorations.begin(), decorations.end(),
98 [](const Decoration& d) {
99 return spv::Decoration::Block == d.dec_type();
100 });
101 }
102
103 // Returns true if the given ID has the Import LinkageAttributes decoration.
hasImportLinkageAttribute(uint32_t id,ValidationState_t & vstate)104 bool hasImportLinkageAttribute(uint32_t id, ValidationState_t& vstate) {
105 const auto& decorations = vstate.id_decorations(id);
106 return std::any_of(
107 decorations.begin(), decorations.end(), [](const Decoration& d) {
108 return spv::Decoration::LinkageAttributes == d.dec_type() &&
109 d.params().size() >= 2u &&
110 spv::LinkageType(d.params().back()) == spv::LinkageType::Import;
111 });
112 }
113
114 // Returns a vector of all members of a structure.
getStructMembers(uint32_t struct_id,ValidationState_t & vstate)115 std::vector<uint32_t> getStructMembers(uint32_t struct_id,
116 ValidationState_t& vstate) {
117 const auto inst = vstate.FindDef(struct_id);
118 return std::vector<uint32_t>(inst->words().begin() + 2, inst->words().end());
119 }
120
121 // Returns a vector of all members of a structure that have specific type.
getStructMembers(uint32_t struct_id,spv::Op type,ValidationState_t & vstate)122 std::vector<uint32_t> getStructMembers(uint32_t struct_id, spv::Op type,
123 ValidationState_t& vstate) {
124 std::vector<uint32_t> members;
125 for (auto id : getStructMembers(struct_id, vstate)) {
126 if (type == vstate.FindDef(id)->opcode()) {
127 members.push_back(id);
128 }
129 }
130 return members;
131 }
132
133 // Returns whether the given structure is missing Offset decoration for any
134 // member. Handles also nested structures.
isMissingOffsetInStruct(uint32_t struct_id,ValidationState_t & vstate)135 bool isMissingOffsetInStruct(uint32_t struct_id, ValidationState_t& vstate) {
136 const auto* inst = vstate.FindDef(struct_id);
137 std::vector<bool> hasOffset;
138 std::vector<uint32_t> struct_members;
139 if (inst->opcode() == spv::Op::OpTypeStruct) {
140 // Check offsets of member decorations.
141 struct_members = getStructMembers(struct_id, vstate);
142 hasOffset.resize(struct_members.size(), false);
143
144 for (auto& decoration : vstate.id_decorations(struct_id)) {
145 if (spv::Decoration::Offset == decoration.dec_type() &&
146 Decoration::kInvalidMember != decoration.struct_member_index()) {
147 // Offset 0xffffffff is not valid so ignore it for simplicity's sake.
148 if (decoration.params()[0] == 0xffffffff) return true;
149 hasOffset[decoration.struct_member_index()] = true;
150 }
151 }
152 } else if (inst->opcode() == spv::Op::OpTypeArray ||
153 inst->opcode() == spv::Op::OpTypeRuntimeArray) {
154 hasOffset.resize(1, true);
155 struct_members.push_back(inst->GetOperandAs<uint32_t>(1u));
156 }
157 // Look through nested structs (which may be in an array).
158 bool nestedStructsMissingOffset = false;
159 for (auto id : struct_members) {
160 if (isMissingOffsetInStruct(id, vstate)) {
161 nestedStructsMissingOffset = true;
162 break;
163 }
164 }
165 return nestedStructsMissingOffset ||
166 !std::all_of(hasOffset.begin(), hasOffset.end(),
167 [](const bool b) { return b; });
168 }
169
170 // Rounds x up to the next alignment. Assumes alignment is a power of two.
align(uint32_t x,uint32_t alignment)171 uint32_t align(uint32_t x, uint32_t alignment) {
172 return (x + alignment - 1) & ~(alignment - 1);
173 }
174
175 // Returns base alignment of struct member. If |roundUp| is true, also
176 // ensure that structs, arrays, and matrices are aligned at least to a
177 // multiple of 16 bytes. (That is, when roundUp is true, this function
178 // returns the *extended* alignment as it's called by the Vulkan spec.)
getBaseAlignment(uint32_t member_id,bool roundUp,const LayoutConstraints & inherited,MemberConstraints & constraints,ValidationState_t & vstate)179 uint32_t getBaseAlignment(uint32_t member_id, bool roundUp,
180 const LayoutConstraints& inherited,
181 MemberConstraints& constraints,
182 ValidationState_t& vstate) {
183 const auto inst = vstate.FindDef(member_id);
184 const auto& words = inst->words();
185 // Minimal alignment is byte-aligned.
186 uint32_t baseAlignment = 1;
187 switch (inst->opcode()) {
188 case spv::Op::OpTypeSampledImage:
189 case spv::Op::OpTypeSampler:
190 case spv::Op::OpTypeImage:
191 if (vstate.HasCapability(spv::Capability::BindlessTextureNV))
192 return baseAlignment = vstate.samplerimage_variable_address_mode() / 8;
193 assert(0);
194 return 0;
195 case spv::Op::OpTypeInt:
196 case spv::Op::OpTypeFloat:
197 baseAlignment = words[2] / 8;
198 break;
199 case spv::Op::OpTypeVector: {
200 const auto componentId = words[2];
201 const auto numComponents = words[3];
202 const auto componentAlignment = getBaseAlignment(
203 componentId, roundUp, inherited, constraints, vstate);
204 baseAlignment =
205 componentAlignment * (numComponents == 3 ? 4 : numComponents);
206 break;
207 }
208 case spv::Op::OpTypeMatrix: {
209 const auto column_type = words[2];
210 if (inherited.majorness == kColumnMajor) {
211 baseAlignment = getBaseAlignment(column_type, roundUp, inherited,
212 constraints, vstate);
213 } else {
214 // A row-major matrix of C columns has a base alignment equal to the
215 // base alignment of a vector of C matrix components.
216 const auto num_columns = words[3];
217 const auto component_inst = vstate.FindDef(column_type);
218 const auto component_id = component_inst->words()[2];
219 const auto componentAlignment = getBaseAlignment(
220 component_id, roundUp, inherited, constraints, vstate);
221 baseAlignment =
222 componentAlignment * (num_columns == 3 ? 4 : num_columns);
223 }
224 if (roundUp) baseAlignment = align(baseAlignment, 16u);
225 } break;
226 case spv::Op::OpTypeArray:
227 case spv::Op::OpTypeRuntimeArray:
228 baseAlignment =
229 getBaseAlignment(words[2], roundUp, inherited, constraints, vstate);
230 if (roundUp) baseAlignment = align(baseAlignment, 16u);
231 break;
232 case spv::Op::OpTypeStruct: {
233 const auto members = getStructMembers(member_id, vstate);
234 for (uint32_t memberIdx = 0, numMembers = uint32_t(members.size());
235 memberIdx < numMembers; ++memberIdx) {
236 const auto id = members[memberIdx];
237 const auto& constraint =
238 constraints[std::make_pair(member_id, memberIdx)];
239 baseAlignment = std::max(
240 baseAlignment,
241 getBaseAlignment(id, roundUp, constraint, constraints, vstate));
242 }
243 if (roundUp) baseAlignment = align(baseAlignment, 16u);
244 break;
245 }
246 case spv::Op::OpTypePointer:
247 baseAlignment = vstate.pointer_size_and_alignment();
248 break;
249 default:
250 assert(0);
251 break;
252 }
253
254 return baseAlignment;
255 }
256
257 // Returns scalar alignment of a type.
getScalarAlignment(uint32_t type_id,ValidationState_t & vstate)258 uint32_t getScalarAlignment(uint32_t type_id, ValidationState_t& vstate) {
259 const auto inst = vstate.FindDef(type_id);
260 const auto& words = inst->words();
261 switch (inst->opcode()) {
262 case spv::Op::OpTypeSampledImage:
263 case spv::Op::OpTypeSampler:
264 case spv::Op::OpTypeImage:
265 if (vstate.HasCapability(spv::Capability::BindlessTextureNV))
266 return vstate.samplerimage_variable_address_mode() / 8;
267 assert(0);
268 return 0;
269 case spv::Op::OpTypeInt:
270 case spv::Op::OpTypeFloat:
271 return words[2] / 8;
272 case spv::Op::OpTypeVector:
273 case spv::Op::OpTypeMatrix:
274 case spv::Op::OpTypeArray:
275 case spv::Op::OpTypeRuntimeArray: {
276 const auto compositeMemberTypeId = words[2];
277 return getScalarAlignment(compositeMemberTypeId, vstate);
278 }
279 case spv::Op::OpTypeStruct: {
280 const auto members = getStructMembers(type_id, vstate);
281 uint32_t max_member_alignment = 1;
282 for (uint32_t memberIdx = 0, numMembers = uint32_t(members.size());
283 memberIdx < numMembers; ++memberIdx) {
284 const auto id = members[memberIdx];
285 uint32_t member_alignment = getScalarAlignment(id, vstate);
286 if (member_alignment > max_member_alignment) {
287 max_member_alignment = member_alignment;
288 }
289 }
290 return max_member_alignment;
291 } break;
292 case spv::Op::OpTypePointer:
293 return vstate.pointer_size_and_alignment();
294 default:
295 assert(0);
296 break;
297 }
298
299 return 1;
300 }
301
302 // Returns size of a struct member. Doesn't include padding at the end of struct
303 // or array. Assumes that in the struct case, all members have offsets.
getSize(uint32_t member_id,const LayoutConstraints & inherited,MemberConstraints & constraints,ValidationState_t & vstate)304 uint32_t getSize(uint32_t member_id, const LayoutConstraints& inherited,
305 MemberConstraints& constraints, ValidationState_t& vstate) {
306 const auto inst = vstate.FindDef(member_id);
307 const auto& words = inst->words();
308 switch (inst->opcode()) {
309 case spv::Op::OpTypeSampledImage:
310 case spv::Op::OpTypeSampler:
311 case spv::Op::OpTypeImage:
312 if (vstate.HasCapability(spv::Capability::BindlessTextureNV))
313 return vstate.samplerimage_variable_address_mode() / 8;
314 assert(0);
315 return 0;
316 case spv::Op::OpTypeInt:
317 case spv::Op::OpTypeFloat:
318 return words[2] / 8;
319 case spv::Op::OpTypeVector: {
320 const auto componentId = words[2];
321 const auto numComponents = words[3];
322 const auto componentSize =
323 getSize(componentId, inherited, constraints, vstate);
324 const auto size = componentSize * numComponents;
325 return size;
326 }
327 case spv::Op::OpTypeArray: {
328 const auto sizeInst = vstate.FindDef(words[3]);
329 if (spvOpcodeIsSpecConstant(sizeInst->opcode())) return 0;
330 assert(spv::Op::OpConstant == sizeInst->opcode());
331 const uint32_t num_elem = sizeInst->words()[3];
332 const uint32_t elem_type = words[2];
333 const uint32_t elem_size =
334 getSize(elem_type, inherited, constraints, vstate);
335 // Account for gaps due to alignments in the first N-1 elements,
336 // then add the size of the last element.
337 const auto size =
338 (num_elem - 1) * GetArrayStride(member_id, vstate) + elem_size;
339 return size;
340 }
341 case spv::Op::OpTypeRuntimeArray:
342 return 0;
343 case spv::Op::OpTypeMatrix: {
344 const auto num_columns = words[3];
345 if (inherited.majorness == kColumnMajor) {
346 return num_columns * inherited.matrix_stride;
347 } else {
348 // Row major case.
349 const auto column_type = words[2];
350 const auto component_inst = vstate.FindDef(column_type);
351 const auto num_rows = component_inst->words()[3];
352 const auto scalar_elem_type = component_inst->words()[2];
353 const uint32_t scalar_elem_size =
354 getSize(scalar_elem_type, inherited, constraints, vstate);
355 return (num_rows - 1) * inherited.matrix_stride +
356 num_columns * scalar_elem_size;
357 }
358 }
359 case spv::Op::OpTypeStruct: {
360 const auto& members = getStructMembers(member_id, vstate);
361 if (members.empty()) return 0;
362 const auto lastIdx = uint32_t(members.size() - 1);
363 const auto& lastMember = members.back();
364 uint32_t offset = 0xffffffff;
365 // Find the offset of the last element and add the size.
366 auto member_decorations =
367 vstate.id_member_decorations(member_id, lastIdx);
368 for (auto decoration = member_decorations.begin;
369 decoration != member_decorations.end; ++decoration) {
370 assert(decoration->struct_member_index() == (int)lastIdx);
371 if (spv::Decoration::Offset == decoration->dec_type()) {
372 offset = decoration->params()[0];
373 }
374 }
375 // This check depends on the fact that all members have offsets. This
376 // has been checked earlier in the flow.
377 assert(offset != 0xffffffff);
378 const auto& constraint = constraints[std::make_pair(lastMember, lastIdx)];
379 return offset + getSize(lastMember, constraint, constraints, vstate);
380 }
381 case spv::Op::OpTypePointer:
382 return vstate.pointer_size_and_alignment();
383 default:
384 assert(0);
385 return 0;
386 }
387 }
388
389 // A member is defined to improperly straddle if either of the following are
390 // true:
391 // - It is a vector with total size less than or equal to 16 bytes, and has
392 // Offset decorations placing its first byte at F and its last byte at L, where
393 // floor(F / 16) != floor(L / 16).
394 // - It is a vector with total size greater than 16 bytes and has its Offset
395 // decorations placing its first byte at a non-integer multiple of 16.
hasImproperStraddle(uint32_t id,uint32_t offset,const LayoutConstraints & inherited,MemberConstraints & constraints,ValidationState_t & vstate)396 bool hasImproperStraddle(uint32_t id, uint32_t offset,
397 const LayoutConstraints& inherited,
398 MemberConstraints& constraints,
399 ValidationState_t& vstate) {
400 const auto size = getSize(id, inherited, constraints, vstate);
401 const auto F = offset;
402 const auto L = offset + size - 1;
403 if (size <= 16) {
404 if ((F >> 4) != (L >> 4)) return true;
405 } else {
406 if (F % 16 != 0) return true;
407 }
408 return false;
409 }
410
411 // Returns true if |offset| satsifies an alignment to |alignment|. In the case
412 // of |alignment| of zero, the |offset| must also be zero.
IsAlignedTo(uint32_t offset,uint32_t alignment)413 bool IsAlignedTo(uint32_t offset, uint32_t alignment) {
414 if (alignment == 0) return offset == 0;
415 return 0 == (offset % alignment);
416 }
417
418 // Returns SPV_SUCCESS if the given struct satisfies standard layout rules for
419 // Block or BufferBlocks in Vulkan. Otherwise emits a diagnostic and returns
420 // something other than SPV_SUCCESS. Matrices inherit the specified column
421 // or row major-ness.
checkLayout(uint32_t struct_id,const char * storage_class_str,const char * decoration_str,bool blockRules,bool scalar_block_layout,uint32_t incoming_offset,MemberConstraints & constraints,ValidationState_t & vstate)422 spv_result_t checkLayout(uint32_t struct_id, const char* storage_class_str,
423 const char* decoration_str, bool blockRules,
424 bool scalar_block_layout,
425 uint32_t incoming_offset,
426 MemberConstraints& constraints,
427 ValidationState_t& vstate) {
428 if (vstate.options()->skip_block_layout) return SPV_SUCCESS;
429
430 // blockRules are the same as bufferBlock rules if the uniform buffer
431 // standard layout extension is being used.
432 if (vstate.options()->uniform_buffer_standard_layout) blockRules = false;
433
434 // Relaxed layout and scalar layout can both be in effect at the same time.
435 // For example, relaxed layout is implied by Vulkan 1.1. But scalar layout
436 // is more permissive than relaxed layout.
437 const bool relaxed_block_layout = vstate.IsRelaxedBlockLayout();
438
439 auto fail = [&vstate, struct_id, storage_class_str, decoration_str,
440 blockRules, relaxed_block_layout,
441 scalar_block_layout](uint32_t member_idx) -> DiagnosticStream {
442 DiagnosticStream ds =
443 std::move(vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(struct_id))
444 << "Structure id " << struct_id << " decorated as "
445 << decoration_str << " for variable in " << storage_class_str
446 << " storage class must follow "
447 << (scalar_block_layout
448 ? "scalar "
449 : (relaxed_block_layout ? "relaxed " : "standard "))
450 << (blockRules ? "uniform buffer" : "storage buffer")
451 << " layout rules: member " << member_idx << " ");
452 return ds;
453 };
454
455 // If we are checking physical storage buffer pointers, we may not actually
456 // have a struct here. Instead, pretend we have a struct with a single member
457 // at offset 0.
458 const auto& struct_type = vstate.FindDef(struct_id);
459 std::vector<uint32_t> members;
460 if (struct_type->opcode() == spv::Op::OpTypeStruct) {
461 members = getStructMembers(struct_id, vstate);
462 } else {
463 members.push_back(struct_id);
464 }
465
466 // To check for member overlaps, we want to traverse the members in
467 // offset order.
468 struct MemberOffsetPair {
469 uint32_t member;
470 uint32_t offset;
471 };
472 std::vector<MemberOffsetPair> member_offsets;
473
474 // With physical storage buffers, we might be checking layouts that do not
475 // originate from a structure.
476 if (struct_type->opcode() == spv::Op::OpTypeStruct) {
477 member_offsets.reserve(members.size());
478 for (uint32_t memberIdx = 0, numMembers = uint32_t(members.size());
479 memberIdx < numMembers; memberIdx++) {
480 uint32_t offset = 0xffffffff;
481 auto member_decorations =
482 vstate.id_member_decorations(struct_id, memberIdx);
483 for (auto decoration = member_decorations.begin;
484 decoration != member_decorations.end; ++decoration) {
485 assert(decoration->struct_member_index() == (int)memberIdx);
486 switch (decoration->dec_type()) {
487 case spv::Decoration::Offset:
488 offset = decoration->params()[0];
489 break;
490 default:
491 break;
492 }
493 }
494 member_offsets.push_back(
495 MemberOffsetPair{memberIdx, incoming_offset + offset});
496 }
497 std::stable_sort(
498 member_offsets.begin(), member_offsets.end(),
499 [](const MemberOffsetPair& lhs, const MemberOffsetPair& rhs) {
500 return lhs.offset < rhs.offset;
501 });
502 } else {
503 member_offsets.push_back({0, 0});
504 }
505
506 // Now scan from lowest offset to highest offset.
507 uint32_t nextValidOffset = 0;
508 for (size_t ordered_member_idx = 0;
509 ordered_member_idx < member_offsets.size(); ordered_member_idx++) {
510 const auto& member_offset = member_offsets[ordered_member_idx];
511 const auto memberIdx = member_offset.member;
512 const auto offset = member_offset.offset;
513 auto id = members[member_offset.member];
514 const LayoutConstraints& constraint =
515 constraints[std::make_pair(struct_id, uint32_t(memberIdx))];
516 // Scalar layout takes precedence because it's more permissive, and implying
517 // an alignment that divides evenly into the alignment that would otherwise
518 // be used.
519 const auto alignment =
520 scalar_block_layout
521 ? getScalarAlignment(id, vstate)
522 : getBaseAlignment(id, blockRules, constraint, constraints, vstate);
523 const auto inst = vstate.FindDef(id);
524 const auto opcode = inst->opcode();
525 const auto size = getSize(id, constraint, constraints, vstate);
526 // Check offset.
527 if (offset == 0xffffffff)
528 return fail(memberIdx) << "is missing an Offset decoration";
529 if (!scalar_block_layout && relaxed_block_layout &&
530 opcode == spv::Op::OpTypeVector) {
531 // In relaxed block layout, the vector offset must be aligned to the
532 // vector's scalar element type.
533 const auto componentId = inst->words()[2];
534 const auto scalar_alignment = getScalarAlignment(componentId, vstate);
535 if (!IsAlignedTo(offset, scalar_alignment)) {
536 return fail(memberIdx)
537 << "at offset " << offset
538 << " is not aligned to scalar element size " << scalar_alignment;
539 }
540 } else {
541 // Without relaxed block layout, the offset must be divisible by the
542 // alignment requirement.
543 if (!IsAlignedTo(offset, alignment)) {
544 return fail(memberIdx)
545 << "at offset " << offset << " is not aligned to " << alignment;
546 }
547 }
548 if (offset < nextValidOffset)
549 return fail(memberIdx) << "at offset " << offset
550 << " overlaps previous member ending at offset "
551 << nextValidOffset - 1;
552 if (!scalar_block_layout && relaxed_block_layout) {
553 // Check improper straddle of vectors.
554 if (spv::Op::OpTypeVector == opcode &&
555 hasImproperStraddle(id, offset, constraint, constraints, vstate))
556 return fail(memberIdx)
557 << "is an improperly straddling vector at offset " << offset;
558 }
559 // Check struct members recursively.
560 spv_result_t recursive_status = SPV_SUCCESS;
561 if (spv::Op::OpTypeStruct == opcode &&
562 SPV_SUCCESS != (recursive_status = checkLayout(
563 id, storage_class_str, decoration_str, blockRules,
564 scalar_block_layout, offset, constraints, vstate)))
565 return recursive_status;
566 // Check matrix stride.
567 if (spv::Op::OpTypeMatrix == opcode) {
568 const auto stride = constraint.matrix_stride;
569 if (!IsAlignedTo(stride, alignment)) {
570 return fail(memberIdx) << "is a matrix with stride " << stride
571 << " not satisfying alignment to " << alignment;
572 }
573 }
574
575 // Check arrays and runtime arrays recursively.
576 auto array_inst = inst;
577 auto array_alignment = alignment;
578 while (array_inst->opcode() == spv::Op::OpTypeArray ||
579 array_inst->opcode() == spv::Op::OpTypeRuntimeArray) {
580 const auto typeId = array_inst->word(2);
581 const auto element_inst = vstate.FindDef(typeId);
582 // Check array stride.
583 uint32_t array_stride = 0;
584 for (auto& decoration : vstate.id_decorations(array_inst->id())) {
585 if (spv::Decoration::ArrayStride == decoration.dec_type()) {
586 array_stride = decoration.params()[0];
587 if (array_stride == 0) {
588 return fail(memberIdx) << "contains an array with stride 0";
589 }
590 if (!IsAlignedTo(array_stride, array_alignment))
591 return fail(memberIdx)
592 << "contains an array with stride " << decoration.params()[0]
593 << " not satisfying alignment to " << alignment;
594 }
595 }
596
597 bool is_int32 = false;
598 bool is_const = false;
599 uint32_t num_elements = 0;
600 if (array_inst->opcode() == spv::Op::OpTypeArray) {
601 std::tie(is_int32, is_const, num_elements) =
602 vstate.EvalInt32IfConst(array_inst->word(3));
603 }
604 num_elements = std::max(1u, num_elements);
605 // Check each element recursively if it is a struct. There is a
606 // limitation to this check if the array size is a spec constant or is a
607 // runtime array then we will only check a single element. This means
608 // some improper straddles might be missed.
609 if (spv::Op::OpTypeStruct == element_inst->opcode()) {
610 std::vector<bool> seen(16, false);
611 for (uint32_t i = 0; i < num_elements; ++i) {
612 uint32_t next_offset = i * array_stride + offset;
613 // Stop checking if offsets repeat in terms of 16-byte multiples.
614 if (seen[next_offset % 16]) {
615 break;
616 }
617
618 if (SPV_SUCCESS !=
619 (recursive_status = checkLayout(
620 typeId, storage_class_str, decoration_str, blockRules,
621 scalar_block_layout, next_offset, constraints, vstate)))
622 return recursive_status;
623
624 seen[next_offset % 16] = true;
625 }
626 }
627
628 // Proceed to the element in case it is an array.
629 array_inst = element_inst;
630 array_alignment = scalar_block_layout
631 ? getScalarAlignment(array_inst->id(), vstate)
632 : getBaseAlignment(array_inst->id(), blockRules,
633 constraint, constraints, vstate);
634
635 const auto element_size =
636 getSize(element_inst->id(), constraint, constraints, vstate);
637 if (element_size > array_stride) {
638 return fail(memberIdx)
639 << "contains an array with stride " << array_stride
640 << ", but with an element size of " << element_size;
641 }
642 }
643 nextValidOffset = offset + size;
644 if (!scalar_block_layout &&
645 (spv::Op::OpTypeArray == opcode || spv::Op::OpTypeStruct == opcode)) {
646 // Non-scalar block layout rules don't permit anything in the padding of
647 // a struct or array.
648 nextValidOffset = align(nextValidOffset, alignment);
649 }
650 }
651 return SPV_SUCCESS;
652 }
653
654 // Returns true if variable or structure id has given decoration. Handles also
655 // nested structures.
hasDecoration(uint32_t id,spv::Decoration decoration,ValidationState_t & vstate)656 bool hasDecoration(uint32_t id, spv::Decoration decoration,
657 ValidationState_t& vstate) {
658 for (auto& dec : vstate.id_decorations(id)) {
659 if (decoration == dec.dec_type()) return true;
660 }
661 if (spv::Op::OpTypeStruct != vstate.FindDef(id)->opcode()) {
662 return false;
663 }
664 for (auto member_id : getStructMembers(id, spv::Op::OpTypeStruct, vstate)) {
665 if (hasDecoration(member_id, decoration, vstate)) {
666 return true;
667 }
668 }
669 return false;
670 }
671
672 // Returns true if all ids of given type have a specified decoration.
checkForRequiredDecoration(uint32_t struct_id,std::function<bool (spv::Decoration)> checker,spv::Op type,ValidationState_t & vstate)673 bool checkForRequiredDecoration(uint32_t struct_id,
674 std::function<bool(spv::Decoration)> checker,
675 spv::Op type, ValidationState_t& vstate) {
676 const auto& members = getStructMembers(struct_id, vstate);
677 for (size_t memberIdx = 0; memberIdx < members.size(); memberIdx++) {
678 const auto id = members[memberIdx];
679 if (type != vstate.FindDef(id)->opcode()) continue;
680 bool found = false;
681 for (auto& dec : vstate.id_decorations(id)) {
682 if (checker(dec.dec_type())) found = true;
683 }
684 for (auto& dec : vstate.id_decorations(struct_id)) {
685 if (checker(dec.dec_type()) &&
686 (int)memberIdx == dec.struct_member_index()) {
687 found = true;
688 }
689 }
690 if (!found) {
691 return false;
692 }
693 }
694 for (auto id : getStructMembers(struct_id, spv::Op::OpTypeStruct, vstate)) {
695 if (!checkForRequiredDecoration(id, checker, type, vstate)) {
696 return false;
697 }
698 }
699 return true;
700 }
701
CheckLinkageAttrOfFunctions(ValidationState_t & vstate)702 spv_result_t CheckLinkageAttrOfFunctions(ValidationState_t& vstate) {
703 for (const auto& function : vstate.functions()) {
704 if (function.block_count() == 0u) {
705 // A function declaration (an OpFunction with no basic blocks), must have
706 // a Linkage Attributes Decoration with the Import Linkage Type.
707 if (!hasImportLinkageAttribute(function.id(), vstate)) {
708 return vstate.diag(SPV_ERROR_INVALID_BINARY,
709 vstate.FindDef(function.id()))
710 << "Function declaration (id " << function.id()
711 << ") must have a LinkageAttributes decoration with the Import "
712 "Linkage type.";
713 }
714 } else {
715 if (hasImportLinkageAttribute(function.id(), vstate)) {
716 return vstate.diag(SPV_ERROR_INVALID_BINARY,
717 vstate.FindDef(function.id()))
718 << "Function definition (id " << function.id()
719 << ") may not be decorated with Import Linkage type.";
720 }
721 }
722 }
723 return SPV_SUCCESS;
724 }
725
726 // Checks whether an imported variable is initialized by this module.
CheckImportedVariableInitialization(ValidationState_t & vstate)727 spv_result_t CheckImportedVariableInitialization(ValidationState_t& vstate) {
728 // According the SPIR-V Spec 2.16.1, it is illegal to initialize an imported
729 // variable. This means that a module-scope OpVariable with initialization
730 // value cannot be marked with the Import Linkage Type (import type id = 1).
731 for (auto global_var_id : vstate.global_vars()) {
732 // Initializer <id> is an optional argument for OpVariable. If initializer
733 // <id> is present, the instruction will have 5 words.
734 auto variable_instr = vstate.FindDef(global_var_id);
735 if (variable_instr->words().size() == 5u &&
736 hasImportLinkageAttribute(global_var_id, vstate)) {
737 return vstate.diag(SPV_ERROR_INVALID_ID, variable_instr)
738 << "A module-scope OpVariable with initialization value "
739 "cannot be marked with the Import Linkage Type.";
740 }
741 }
742 return SPV_SUCCESS;
743 }
744
745 // Checks whether a builtin variable is valid.
CheckBuiltInVariable(uint32_t var_id,ValidationState_t & vstate)746 spv_result_t CheckBuiltInVariable(uint32_t var_id, ValidationState_t& vstate) {
747 const auto& decorations = vstate.id_decorations(var_id);
748 for (const auto& d : decorations) {
749 if (spvIsVulkanEnv(vstate.context()->target_env)) {
750 if (d.dec_type() == spv::Decoration::Location ||
751 d.dec_type() == spv::Decoration::Component) {
752 return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(var_id))
753 << vstate.VkErrorID(4915) << "A BuiltIn variable (id " << var_id
754 << ") cannot have any Location or Component decorations";
755 }
756 }
757 }
758 return SPV_SUCCESS;
759 }
760
761 // Checks whether proper decorations have been applied to the entry points.
CheckDecorationsOfEntryPoints(ValidationState_t & vstate)762 spv_result_t CheckDecorationsOfEntryPoints(ValidationState_t& vstate) {
763 for (uint32_t entry_point : vstate.entry_points()) {
764 const auto& descs = vstate.entry_point_descriptions(entry_point);
765 int num_builtin_block_inputs = 0;
766 int num_builtin_block_outputs = 0;
767 int num_workgroup_variables = 0;
768 int num_workgroup_variables_with_block = 0;
769 int num_workgroup_variables_with_aliased = 0;
770 for (const auto& desc : descs) {
771 std::unordered_set<Instruction*> seen_vars;
772 for (auto interface : desc.interfaces) {
773 Instruction* var_instr = vstate.FindDef(interface);
774 if (!var_instr || spv::Op::OpVariable != var_instr->opcode()) {
775 return vstate.diag(SPV_ERROR_INVALID_ID, var_instr)
776 << "Interfaces passed to OpEntryPoint must be of type "
777 "OpTypeVariable. Found Op"
778 << spvOpcodeString(var_instr->opcode()) << ".";
779 }
780 const spv::StorageClass storage_class =
781 var_instr->GetOperandAs<spv::StorageClass>(2);
782 if (vstate.version() >= SPV_SPIRV_VERSION_WORD(1, 4)) {
783 // Starting in 1.4, OpEntryPoint must list all global variables
784 // it statically uses and those interfaces must be unique.
785 if (storage_class == spv::StorageClass::Function) {
786 return vstate.diag(SPV_ERROR_INVALID_ID, var_instr)
787 << "OpEntryPoint interfaces should only list global "
788 "variables";
789 }
790
791 if (!seen_vars.insert(var_instr).second) {
792 return vstate.diag(SPV_ERROR_INVALID_ID, var_instr)
793 << "Non-unique OpEntryPoint interface "
794 << vstate.getIdName(interface) << " is disallowed";
795 }
796 } else {
797 if (storage_class != spv::StorageClass::Input &&
798 storage_class != spv::StorageClass::Output) {
799 return vstate.diag(SPV_ERROR_INVALID_ID, var_instr)
800 << "OpEntryPoint interfaces must be OpVariables with "
801 "Storage Class of Input(1) or Output(3). Found Storage "
802 "Class "
803 << uint32_t(storage_class) << " for Entry Point id "
804 << entry_point << ".";
805 }
806 }
807
808 const uint32_t ptr_id = var_instr->word(1);
809 Instruction* ptr_instr = vstate.FindDef(ptr_id);
810 // It is guaranteed (by validator ID checks) that ptr_instr is
811 // OpTypePointer. Word 3 of this instruction is the type being pointed
812 // to.
813 const uint32_t type_id = ptr_instr->word(3);
814 Instruction* type_instr = vstate.FindDef(type_id);
815 if (type_instr && spv::Op::OpTypeStruct == type_instr->opcode() &&
816 isBuiltInStruct(type_id, vstate)) {
817 if (!isBlock(type_id, vstate)) {
818 return vstate.diag(SPV_ERROR_INVALID_DATA, vstate.FindDef(type_id))
819 << vstate.VkErrorID(4919)
820 << "Interface struct has no Block decoration but has "
821 "BuiltIn members. "
822 "Location decorations must be used on each member of "
823 "OpVariable with a structure type that is a block not "
824 "decorated with Location.";
825 }
826 if (storage_class == spv::StorageClass::Input)
827 ++num_builtin_block_inputs;
828 if (storage_class == spv::StorageClass::Output)
829 ++num_builtin_block_outputs;
830 if (num_builtin_block_inputs > 1 || num_builtin_block_outputs > 1)
831 break;
832 if (auto error = CheckBuiltInVariable(interface, vstate))
833 return error;
834 } else if (isBuiltInVar(interface, vstate)) {
835 if (auto error = CheckBuiltInVariable(interface, vstate))
836 return error;
837 }
838
839 if (storage_class == spv::StorageClass::Workgroup) {
840 ++num_workgroup_variables;
841 if (type_instr && spv::Op::OpTypeStruct == type_instr->opcode()) {
842 if (hasDecoration(type_id, spv::Decoration::Block, vstate))
843 ++num_workgroup_variables_with_block;
844 if (hasDecoration(var_instr->id(), spv::Decoration::Aliased,
845 vstate))
846 ++num_workgroup_variables_with_aliased;
847 }
848 }
849
850 if (spvIsVulkanEnv(vstate.context()->target_env)) {
851 const auto* models = vstate.GetExecutionModels(entry_point);
852 const bool has_frag =
853 models->find(spv::ExecutionModel::Fragment) != models->end();
854 const bool has_vert =
855 models->find(spv::ExecutionModel::Vertex) != models->end();
856 for (const auto& decoration :
857 vstate.id_decorations(var_instr->id())) {
858 if (decoration == spv::Decoration::Flat ||
859 decoration == spv::Decoration::NoPerspective ||
860 decoration == spv::Decoration::Sample ||
861 decoration == spv::Decoration::Centroid) {
862 // VUID 04670 already validates these decorations are input/output
863 if (storage_class == spv::StorageClass::Input &&
864 (models->size() > 1 || has_vert)) {
865 return vstate.diag(SPV_ERROR_INVALID_ID, var_instr)
866 << vstate.VkErrorID(6202)
867 << vstate.SpvDecorationString(decoration.dec_type())
868 << " decorated variable must not be used in vertex "
869 "execution model as an Input storage class for Entry "
870 "Point id "
871 << entry_point << ".";
872 } else if (storage_class == spv::StorageClass::Output &&
873 (models->size() > 1 || has_frag)) {
874 return vstate.diag(SPV_ERROR_INVALID_ID, var_instr)
875 << vstate.VkErrorID(6201)
876 << vstate.SpvDecorationString(decoration.dec_type())
877 << " decorated variable must not be used in fragment "
878 "execution model as an Output storage class for "
879 "Entry Point id "
880 << entry_point << ".";
881 }
882 }
883 }
884
885 const bool has_flat =
886 hasDecoration(var_instr->id(), spv::Decoration::Flat, vstate);
887 if (has_frag && storage_class == spv::StorageClass::Input &&
888 !has_flat &&
889 ((vstate.IsFloatScalarType(type_id) &&
890 vstate.GetBitWidth(type_id) == 64) ||
891 vstate.IsIntScalarOrVectorType(type_id))) {
892 return vstate.diag(SPV_ERROR_INVALID_ID, var_instr)
893 << vstate.VkErrorID(4744)
894 << "Fragment OpEntryPoint operand "
895 << interface << " with Input interfaces with integer or "
896 "float type must have a Flat decoration "
897 "for Entry Point id "
898 << entry_point << ".";
899 }
900 }
901 }
902 if (num_builtin_block_inputs > 1 || num_builtin_block_outputs > 1) {
903 return vstate.diag(SPV_ERROR_INVALID_BINARY,
904 vstate.FindDef(entry_point))
905 << "There must be at most one object per Storage Class that can "
906 "contain a structure type containing members decorated with "
907 "BuiltIn, consumed per entry-point. Entry Point id "
908 << entry_point << " does not meet this requirement.";
909 }
910 // The LinkageAttributes Decoration cannot be applied to functions
911 // targeted by an OpEntryPoint instruction
912 for (auto& decoration : vstate.id_decorations(entry_point)) {
913 if (spv::Decoration::LinkageAttributes == decoration.dec_type()) {
914 const std::string linkage_name =
915 spvtools::utils::MakeString(decoration.params());
916 return vstate.diag(SPV_ERROR_INVALID_BINARY,
917 vstate.FindDef(entry_point))
918 << "The LinkageAttributes Decoration (Linkage name: "
919 << linkage_name << ") cannot be applied to function id "
920 << entry_point
921 << " because it is targeted by an OpEntryPoint instruction.";
922 }
923 }
924
925 const bool workgroup_blocks_allowed = vstate.HasCapability(
926 spv::Capability::WorkgroupMemoryExplicitLayoutKHR);
927 if (workgroup_blocks_allowed && num_workgroup_variables > 0 &&
928 num_workgroup_variables_with_block > 0) {
929 if (num_workgroup_variables != num_workgroup_variables_with_block) {
930 return vstate.diag(SPV_ERROR_INVALID_BINARY, vstate.FindDef(entry_point))
931 << "When declaring WorkgroupMemoryExplicitLayoutKHR, "
932 "either all or none of the Workgroup Storage Class variables "
933 "in the entry point interface must point to struct types "
934 "decorated with Block. Entry point id "
935 << entry_point << " does not meet this requirement.";
936 }
937 if (num_workgroup_variables_with_block > 1 &&
938 num_workgroup_variables_with_block !=
939 num_workgroup_variables_with_aliased) {
940 return vstate.diag(SPV_ERROR_INVALID_BINARY, vstate.FindDef(entry_point))
941 << "When declaring WorkgroupMemoryExplicitLayoutKHR, "
942 "if more than one Workgroup Storage Class variable in "
943 "the entry point interface point to a type decorated "
944 "with Block, all of them must be decorated with Aliased. "
945 "Entry point id "
946 << entry_point << " does not meet this requirement.";
947 }
948 } else if (!workgroup_blocks_allowed &&
949 num_workgroup_variables_with_block > 0) {
950 return vstate.diag(SPV_ERROR_INVALID_BINARY,
951 vstate.FindDef(entry_point))
952 << "Workgroup Storage Class variables can't be decorated with "
953 "Block unless declaring the WorkgroupMemoryExplicitLayoutKHR "
954 "capability.";
955 }
956 }
957 }
958 return SPV_SUCCESS;
959 }
960
961 // Load |constraints| with all the member constraints for structs contained
962 // within the given array type.
963 void ComputeMemberConstraintsForArray(MemberConstraints* constraints,
964 uint32_t array_id,
965 const LayoutConstraints& inherited,
966 ValidationState_t& vstate);
967
968 // Load |constraints| with all the member constraints for the given struct,
969 // and all its contained structs.
ComputeMemberConstraintsForStruct(MemberConstraints * constraints,uint32_t struct_id,const LayoutConstraints & inherited,ValidationState_t & vstate)970 void ComputeMemberConstraintsForStruct(MemberConstraints* constraints,
971 uint32_t struct_id,
972 const LayoutConstraints& inherited,
973 ValidationState_t& vstate) {
974 assert(constraints);
975 const auto& members = getStructMembers(struct_id, vstate);
976 for (uint32_t memberIdx = 0, numMembers = uint32_t(members.size());
977 memberIdx < numMembers; memberIdx++) {
978 LayoutConstraints& constraint =
979 (*constraints)[std::make_pair(struct_id, memberIdx)];
980 constraint = inherited;
981 auto member_decorations =
982 vstate.id_member_decorations(struct_id, memberIdx);
983 for (auto decoration = member_decorations.begin;
984 decoration != member_decorations.end; ++decoration) {
985 assert(decoration->struct_member_index() == (int)memberIdx);
986 switch (decoration->dec_type()) {
987 case spv::Decoration::RowMajor:
988 constraint.majorness = kRowMajor;
989 break;
990 case spv::Decoration::ColMajor:
991 constraint.majorness = kColumnMajor;
992 break;
993 case spv::Decoration::MatrixStride:
994 constraint.matrix_stride = decoration->params()[0];
995 break;
996 default:
997 break;
998 }
999 }
1000
1001 // Now recurse
1002 auto member_type_id = members[memberIdx];
1003 const auto member_type_inst = vstate.FindDef(member_type_id);
1004 const auto opcode = member_type_inst->opcode();
1005 switch (opcode) {
1006 case spv::Op::OpTypeArray:
1007 case spv::Op::OpTypeRuntimeArray:
1008 ComputeMemberConstraintsForArray(constraints, member_type_id, inherited,
1009 vstate);
1010 break;
1011 case spv::Op::OpTypeStruct:
1012 ComputeMemberConstraintsForStruct(constraints, member_type_id,
1013 inherited, vstate);
1014 break;
1015 default:
1016 break;
1017 }
1018 }
1019 }
1020
ComputeMemberConstraintsForArray(MemberConstraints * constraints,uint32_t array_id,const LayoutConstraints & inherited,ValidationState_t & vstate)1021 void ComputeMemberConstraintsForArray(MemberConstraints* constraints,
1022 uint32_t array_id,
1023 const LayoutConstraints& inherited,
1024 ValidationState_t& vstate) {
1025 assert(constraints);
1026 auto elem_type_id = vstate.FindDef(array_id)->words()[2];
1027 const auto elem_type_inst = vstate.FindDef(elem_type_id);
1028 const auto opcode = elem_type_inst->opcode();
1029 switch (opcode) {
1030 case spv::Op::OpTypeArray:
1031 case spv::Op::OpTypeRuntimeArray:
1032 ComputeMemberConstraintsForArray(constraints, elem_type_id, inherited,
1033 vstate);
1034 break;
1035 case spv::Op::OpTypeStruct:
1036 ComputeMemberConstraintsForStruct(constraints, elem_type_id, inherited,
1037 vstate);
1038 break;
1039 default:
1040 break;
1041 }
1042 }
1043
CheckDecorationsOfBuffers(ValidationState_t & vstate)1044 spv_result_t CheckDecorationsOfBuffers(ValidationState_t& vstate) {
1045 // Set of entry points that are known to use a push constant.
1046 std::unordered_set<uint32_t> uses_push_constant;
1047 for (const auto& inst : vstate.ordered_instructions()) {
1048 const auto& words = inst.words();
1049 auto type_id = inst.type_id();
1050 const Instruction* type_inst = vstate.FindDef(type_id);
1051 if (spv::Op::OpVariable == inst.opcode()) {
1052 const auto var_id = inst.id();
1053 // For storage class / decoration combinations, see Vulkan 14.5.4 "Offset
1054 // and Stride Assignment".
1055 const auto storageClass = inst.GetOperandAs<spv::StorageClass>(2);
1056 const bool uniform = storageClass == spv::StorageClass::Uniform;
1057 const bool uniform_constant =
1058 storageClass == spv::StorageClass::UniformConstant;
1059 const bool push_constant =
1060 storageClass == spv::StorageClass::PushConstant;
1061 const bool storage_buffer =
1062 storageClass == spv::StorageClass::StorageBuffer;
1063
1064 if (spvIsVulkanEnv(vstate.context()->target_env)) {
1065 // Vulkan: There must be no more than one PushConstant block per entry
1066 // point.
1067 if (push_constant) {
1068 auto entry_points = vstate.EntryPointReferences(var_id);
1069 for (auto ep_id : entry_points) {
1070 const bool already_used = !uses_push_constant.insert(ep_id).second;
1071 if (already_used) {
1072 return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(var_id))
1073 << vstate.VkErrorID(6674) << "Entry point id '" << ep_id
1074 << "' uses more than one PushConstant interface.\n"
1075 << "From Vulkan spec:\n"
1076 << "There must be no more than one push constant block "
1077 << "statically used per shader entry point.";
1078 }
1079 }
1080 }
1081 // Vulkan: Check DescriptorSet and Binding decoration for
1082 // UniformConstant which cannot be a struct.
1083 if (uniform_constant) {
1084 auto entry_points = vstate.EntryPointReferences(var_id);
1085 if (!entry_points.empty() &&
1086 !hasDecoration(var_id, spv::Decoration::DescriptorSet, vstate)) {
1087 return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(var_id))
1088 << vstate.VkErrorID(6677) << "UniformConstant id '" << var_id
1089 << "' is missing DescriptorSet decoration.\n"
1090 << "From Vulkan spec:\n"
1091 << "These variables must have DescriptorSet and Binding "
1092 "decorations specified";
1093 }
1094 if (!entry_points.empty() &&
1095 !hasDecoration(var_id, spv::Decoration::Binding, vstate)) {
1096 return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(var_id))
1097 << vstate.VkErrorID(6677) << "UniformConstant id '" << var_id
1098 << "' is missing Binding decoration.\n"
1099 << "From Vulkan spec:\n"
1100 << "These variables must have DescriptorSet and Binding "
1101 "decorations specified";
1102 }
1103 }
1104 }
1105
1106 if (spvIsOpenGLEnv(vstate.context()->target_env)) {
1107 bool has_block = hasDecoration(var_id, spv::Decoration::Block, vstate);
1108 bool has_buffer_block =
1109 hasDecoration(var_id, spv::Decoration::BufferBlock, vstate);
1110 if ((uniform && (has_block || has_buffer_block)) ||
1111 (storage_buffer && has_block)) {
1112 auto entry_points = vstate.EntryPointReferences(var_id);
1113 if (!entry_points.empty() &&
1114 !hasDecoration(var_id, spv::Decoration::Binding, vstate)) {
1115 return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(var_id))
1116 << (uniform ? "Uniform" : "Storage Buffer") << " id '"
1117 << var_id << "' is missing Binding decoration.\n"
1118 << "From ARB_gl_spirv extension:\n"
1119 << "Uniform and shader storage block variables must "
1120 << "also be decorated with a *Binding*.";
1121 }
1122 }
1123 }
1124
1125 const bool phys_storage_buffer =
1126 storageClass == spv::StorageClass::PhysicalStorageBuffer;
1127 const bool workgroup =
1128 storageClass == spv::StorageClass::Workgroup &&
1129 vstate.HasCapability(
1130 spv::Capability::WorkgroupMemoryExplicitLayoutKHR);
1131 if (uniform || push_constant || storage_buffer || phys_storage_buffer ||
1132 workgroup) {
1133 const auto ptrInst = vstate.FindDef(words[1]);
1134 assert(spv::Op::OpTypePointer == ptrInst->opcode());
1135 auto id = ptrInst->words()[3];
1136 auto id_inst = vstate.FindDef(id);
1137 // Jump through one level of arraying.
1138 if (!workgroup && (id_inst->opcode() == spv::Op::OpTypeArray ||
1139 id_inst->opcode() == spv::Op::OpTypeRuntimeArray)) {
1140 id = id_inst->GetOperandAs<uint32_t>(1u);
1141 id_inst = vstate.FindDef(id);
1142 }
1143 // Struct requirement is checked on variables so just move on here.
1144 if (spv::Op::OpTypeStruct != id_inst->opcode()) continue;
1145 MemberConstraints constraints;
1146 ComputeMemberConstraintsForStruct(&constraints, id, LayoutConstraints(),
1147 vstate);
1148 // Prepare for messages
1149 const char* sc_str =
1150 uniform ? "Uniform"
1151 : (push_constant ? "PushConstant"
1152 : (workgroup ? "Workgroup"
1153 : "StorageBuffer"));
1154
1155 if (spvIsVulkanEnv(vstate.context()->target_env)) {
1156 const bool block = hasDecoration(id, spv::Decoration::Block, vstate);
1157 const bool buffer_block =
1158 hasDecoration(id, spv::Decoration::BufferBlock, vstate);
1159 if (storage_buffer && buffer_block) {
1160 return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(var_id))
1161 << vstate.VkErrorID(6675) << "Storage buffer id '" << var_id
1162 << " In Vulkan, BufferBlock is disallowed on variables in "
1163 "the StorageBuffer storage class";
1164 }
1165 // Vulkan: Check Block decoration for PushConstant, Uniform
1166 // and StorageBuffer variables. Uniform can also use BufferBlock.
1167 if (push_constant && !block) {
1168 return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
1169 << vstate.VkErrorID(6675) << "PushConstant id '" << id
1170 << "' is missing Block decoration.\n"
1171 << "From Vulkan spec:\n"
1172 << "Such variables must be identified with a Block "
1173 "decoration";
1174 }
1175 if (storage_buffer && !block) {
1176 return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
1177 << vstate.VkErrorID(6675) << "StorageBuffer id '" << id
1178 << "' is missing Block decoration.\n"
1179 << "From Vulkan spec:\n"
1180 << "Such variables must be identified with a Block "
1181 "decoration";
1182 }
1183 if (uniform && !block && !buffer_block) {
1184 return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
1185 << vstate.VkErrorID(6676) << "Uniform id '" << id
1186 << "' is missing Block or BufferBlock decoration.\n"
1187 << "From Vulkan spec:\n"
1188 << "Such variables must be identified with a Block or "
1189 "BufferBlock decoration";
1190 }
1191 // Vulkan: Check DescriptorSet and Binding decoration for
1192 // Uniform and StorageBuffer variables.
1193 if (uniform || storage_buffer) {
1194 auto entry_points = vstate.EntryPointReferences(var_id);
1195 if (!entry_points.empty() &&
1196 !hasDecoration(var_id, spv::Decoration::DescriptorSet,
1197 vstate)) {
1198 return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(var_id))
1199 << vstate.VkErrorID(6677) << sc_str << " id '" << var_id
1200 << "' is missing DescriptorSet decoration.\n"
1201 << "From Vulkan spec:\n"
1202 << "These variables must have DescriptorSet and Binding "
1203 "decorations specified";
1204 }
1205 if (!entry_points.empty() &&
1206 !hasDecoration(var_id, spv::Decoration::Binding, vstate)) {
1207 return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(var_id))
1208 << vstate.VkErrorID(6677) << sc_str << " id '" << var_id
1209 << "' is missing Binding decoration.\n"
1210 << "From Vulkan spec:\n"
1211 << "These variables must have DescriptorSet and Binding "
1212 "decorations specified";
1213 }
1214 }
1215 }
1216
1217 for (const auto& dec : vstate.id_decorations(id)) {
1218 const bool blockDeco = spv::Decoration::Block == dec.dec_type();
1219 const bool bufferDeco =
1220 spv::Decoration::BufferBlock == dec.dec_type();
1221 const bool blockRules = uniform && blockDeco;
1222 const bool bufferRules =
1223 (uniform && bufferDeco) ||
1224 ((push_constant || storage_buffer ||
1225 phys_storage_buffer || workgroup) && blockDeco);
1226 if (uniform && blockDeco) {
1227 vstate.RegisterPointerToUniformBlock(ptrInst->id());
1228 vstate.RegisterStructForUniformBlock(id);
1229 }
1230 if ((uniform && bufferDeco) ||
1231 ((storage_buffer || phys_storage_buffer) && blockDeco)) {
1232 vstate.RegisterPointerToStorageBuffer(ptrInst->id());
1233 vstate.RegisterStructForStorageBuffer(id);
1234 }
1235
1236 if (blockRules || bufferRules) {
1237 const char* deco_str = blockDeco ? "Block" : "BufferBlock";
1238 spv_result_t recursive_status = SPV_SUCCESS;
1239 const bool scalar_block_layout = workgroup ?
1240 vstate.options()->workgroup_scalar_block_layout :
1241 vstate.options()->scalar_block_layout;
1242
1243 if (isMissingOffsetInStruct(id, vstate)) {
1244 return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
1245 << "Structure id " << id << " decorated as " << deco_str
1246 << " must be explicitly laid out with Offset "
1247 "decorations.";
1248 }
1249
1250 if (!checkForRequiredDecoration(
1251 id,
1252 [](spv::Decoration d) {
1253 return d == spv::Decoration::ArrayStride;
1254 },
1255 spv::Op::OpTypeArray, vstate)) {
1256 return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
1257 << "Structure id " << id << " decorated as " << deco_str
1258 << " must be explicitly laid out with ArrayStride "
1259 "decorations.";
1260 }
1261
1262 if (!checkForRequiredDecoration(
1263 id,
1264 [](spv::Decoration d) {
1265 return d == spv::Decoration::MatrixStride;
1266 },
1267 spv::Op::OpTypeMatrix, vstate)) {
1268 return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
1269 << "Structure id " << id << " decorated as " << deco_str
1270 << " must be explicitly laid out with MatrixStride "
1271 "decorations.";
1272 }
1273
1274 if (!checkForRequiredDecoration(
1275 id,
1276 [](spv::Decoration d) {
1277 return d == spv::Decoration::RowMajor ||
1278 d == spv::Decoration::ColMajor;
1279 },
1280 spv::Op::OpTypeMatrix, vstate)) {
1281 return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
1282 << "Structure id " << id << " decorated as " << deco_str
1283 << " must be explicitly laid out with RowMajor or "
1284 "ColMajor decorations.";
1285 }
1286
1287 if (spvIsVulkanEnv(vstate.context()->target_env)) {
1288 if (blockRules && (SPV_SUCCESS != (recursive_status = checkLayout(
1289 id, sc_str, deco_str, true,
1290 scalar_block_layout, 0,
1291 constraints, vstate)))) {
1292 return recursive_status;
1293 } else if (bufferRules &&
1294 (SPV_SUCCESS !=
1295 (recursive_status = checkLayout(
1296 id, sc_str, deco_str, false, scalar_block_layout,
1297 0, constraints, vstate)))) {
1298 return recursive_status;
1299 }
1300 }
1301 }
1302 }
1303 }
1304 } else if (type_inst && type_inst->opcode() == spv::Op::OpTypePointer &&
1305 type_inst->GetOperandAs<spv::StorageClass>(1u) ==
1306 spv::StorageClass::PhysicalStorageBuffer) {
1307 const bool scalar_block_layout = vstate.options()->scalar_block_layout;
1308 MemberConstraints constraints;
1309 const bool buffer = true;
1310 const auto data_type_id = type_inst->GetOperandAs<uint32_t>(2u);
1311 const auto* data_type_inst = vstate.FindDef(data_type_id);
1312 if (data_type_inst->opcode() == spv::Op::OpTypeStruct) {
1313 ComputeMemberConstraintsForStruct(&constraints, data_type_id,
1314 LayoutConstraints(), vstate);
1315 }
1316 if (auto res = checkLayout(data_type_id, "PhysicalStorageBuffer", "Block",
1317 !buffer, scalar_block_layout, 0, constraints,
1318 vstate)) {
1319 return res;
1320 }
1321 }
1322 }
1323 return SPV_SUCCESS;
1324 }
1325
1326 // Returns true if |decoration| cannot be applied to the same id more than once.
AtMostOncePerId(spv::Decoration decoration)1327 bool AtMostOncePerId(spv::Decoration decoration) {
1328 return decoration == spv::Decoration::ArrayStride;
1329 }
1330
1331 // Returns true if |decoration| cannot be applied to the same member more than
1332 // once.
AtMostOncePerMember(spv::Decoration decoration)1333 bool AtMostOncePerMember(spv::Decoration decoration) {
1334 switch (decoration) {
1335 case spv::Decoration::Offset:
1336 case spv::Decoration::MatrixStride:
1337 case spv::Decoration::RowMajor:
1338 case spv::Decoration::ColMajor:
1339 return true;
1340 default:
1341 return false;
1342 }
1343 }
1344
CheckDecorationsCompatibility(ValidationState_t & vstate)1345 spv_result_t CheckDecorationsCompatibility(ValidationState_t& vstate) {
1346 using PerIDKey = std::tuple<spv::Decoration, uint32_t>;
1347 using PerMemberKey = std::tuple<spv::Decoration, uint32_t, uint32_t>;
1348
1349 // An Array of pairs where the decorations in the pair cannot both be applied
1350 // to the same id.
1351 static const spv::Decoration mutually_exclusive_per_id[][2] = {
1352 {spv::Decoration::Block, spv::Decoration::BufferBlock},
1353 {spv::Decoration::Restrict, spv::Decoration::Aliased}};
1354 static const auto num_mutually_exclusive_per_id_pairs =
1355 sizeof(mutually_exclusive_per_id) / (2 * sizeof(spv::Decoration));
1356
1357 // An Array of pairs where the decorations in the pair cannot both be applied
1358 // to the same member.
1359 static const spv::Decoration mutually_exclusive_per_member[][2] = {
1360 {spv::Decoration::RowMajor, spv::Decoration::ColMajor}};
1361 static const auto num_mutually_exclusive_per_mem_pairs =
1362 sizeof(mutually_exclusive_per_member) / (2 * sizeof(spv::Decoration));
1363
1364 std::set<PerIDKey> seen_per_id;
1365 std::set<PerMemberKey> seen_per_member;
1366
1367 for (const auto& inst : vstate.ordered_instructions()) {
1368 const auto& words = inst.words();
1369 if (spv::Op::OpDecorate == inst.opcode()) {
1370 const auto id = words[1];
1371 const auto dec_type = static_cast<spv::Decoration>(words[2]);
1372 const auto k = PerIDKey(dec_type, id);
1373 const auto already_used = !seen_per_id.insert(k).second;
1374 if (already_used && AtMostOncePerId(dec_type)) {
1375 return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
1376 << "ID '" << id << "' decorated with "
1377 << vstate.SpvDecorationString(dec_type)
1378 << " multiple times is not allowed.";
1379 }
1380 // Verify certain mutually exclusive decorations are not both applied on
1381 // an ID.
1382 for (uint32_t pair_idx = 0;
1383 pair_idx < num_mutually_exclusive_per_id_pairs; ++pair_idx) {
1384 spv::Decoration excl_dec_type = spv::Decoration::Max;
1385 if (mutually_exclusive_per_id[pair_idx][0] == dec_type) {
1386 excl_dec_type = mutually_exclusive_per_id[pair_idx][1];
1387 } else if (mutually_exclusive_per_id[pair_idx][1] == dec_type) {
1388 excl_dec_type = mutually_exclusive_per_id[pair_idx][0];
1389 } else {
1390 continue;
1391 }
1392
1393 const auto excl_k = PerIDKey(excl_dec_type, id);
1394 if (seen_per_id.find(excl_k) != seen_per_id.end()) {
1395 return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
1396 << "ID '" << id << "' decorated with both "
1397 << vstate.SpvDecorationString(dec_type) << " and "
1398 << vstate.SpvDecorationString(excl_dec_type)
1399 << " is not allowed.";
1400 }
1401 }
1402 } else if (spv::Op::OpMemberDecorate == inst.opcode()) {
1403 const auto id = words[1];
1404 const auto member_id = words[2];
1405 const auto dec_type = static_cast<spv::Decoration>(words[3]);
1406 const auto k = PerMemberKey(dec_type, id, member_id);
1407 const auto already_used = !seen_per_member.insert(k).second;
1408 if (already_used && AtMostOncePerMember(dec_type)) {
1409 return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
1410 << "ID '" << id << "', member '" << member_id
1411 << "' decorated with " << vstate.SpvDecorationString(dec_type)
1412 << " multiple times is not allowed.";
1413 }
1414 // Verify certain mutually exclusive decorations are not both applied on
1415 // a (ID, member) tuple.
1416 for (uint32_t pair_idx = 0;
1417 pair_idx < num_mutually_exclusive_per_mem_pairs; ++pair_idx) {
1418 spv::Decoration excl_dec_type = spv::Decoration::Max;
1419 if (mutually_exclusive_per_member[pair_idx][0] == dec_type) {
1420 excl_dec_type = mutually_exclusive_per_member[pair_idx][1];
1421 } else if (mutually_exclusive_per_member[pair_idx][1] == dec_type) {
1422 excl_dec_type = mutually_exclusive_per_member[pair_idx][0];
1423 } else {
1424 continue;
1425 }
1426
1427 const auto excl_k = PerMemberKey(excl_dec_type, id, member_id);
1428 if (seen_per_member.find(excl_k) != seen_per_member.end()) {
1429 return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
1430 << "ID '" << id << "', member '" << member_id
1431 << "' decorated with both "
1432 << vstate.SpvDecorationString(dec_type) << " and "
1433 << vstate.SpvDecorationString(excl_dec_type)
1434 << " is not allowed.";
1435 }
1436 }
1437 }
1438 }
1439 return SPV_SUCCESS;
1440 }
1441
CheckVulkanMemoryModelDeprecatedDecorations(ValidationState_t & vstate)1442 spv_result_t CheckVulkanMemoryModelDeprecatedDecorations(
1443 ValidationState_t& vstate) {
1444 if (vstate.memory_model() != spv::MemoryModel::VulkanKHR) return SPV_SUCCESS;
1445
1446 std::string msg;
1447 std::ostringstream str(msg);
1448 for (const auto& def : vstate.all_definitions()) {
1449 const auto inst = def.second;
1450 const auto id = inst->id();
1451 for (const auto& dec : vstate.id_decorations(id)) {
1452 const auto member = dec.struct_member_index();
1453 if (dec.dec_type() == spv::Decoration::Coherent ||
1454 dec.dec_type() == spv::Decoration::Volatile) {
1455 str << (dec.dec_type() == spv::Decoration::Coherent ? "Coherent"
1456 : "Volatile");
1457 str << " decoration targeting " << vstate.getIdName(id);
1458 if (member != Decoration::kInvalidMember) {
1459 str << " (member index " << member << ")";
1460 }
1461 str << " is banned when using the Vulkan memory model.";
1462 return vstate.diag(SPV_ERROR_INVALID_ID, inst) << str.str();
1463 }
1464 }
1465 }
1466 return SPV_SUCCESS;
1467 }
1468
1469 // Returns SPV_SUCCESS if validation rules are satisfied for FPRoundingMode
1470 // decorations. Otherwise emits a diagnostic and returns something other than
1471 // SPV_SUCCESS.
CheckFPRoundingModeForShaders(ValidationState_t & vstate,const Instruction & inst,const Decoration & decoration)1472 spv_result_t CheckFPRoundingModeForShaders(ValidationState_t& vstate,
1473 const Instruction& inst,
1474 const Decoration& decoration) {
1475 // Validates width-only conversion instruction for floating-point object
1476 // i.e., OpFConvert
1477 if (inst.opcode() != spv::Op::OpFConvert) {
1478 return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
1479 << "FPRoundingMode decoration can be applied only to a "
1480 "width-only conversion instruction for floating-point "
1481 "object.";
1482 }
1483
1484 if (spvIsVulkanEnv(vstate.context()->target_env)) {
1485 const auto mode = spv::FPRoundingMode(decoration.params()[0]);
1486 if ((mode != spv::FPRoundingMode::RTE) &&
1487 (mode != spv::FPRoundingMode::RTZ)) {
1488 return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
1489 << vstate.VkErrorID(4675)
1490 << "In Vulkan, the FPRoundingMode mode must only by RTE or RTZ.";
1491 }
1492 }
1493
1494 // Validates Object operand of an OpStore
1495 for (const auto& use : inst.uses()) {
1496 const auto store = use.first;
1497 if (store->opcode() == spv::Op::OpFConvert) continue;
1498 if (spvOpcodeIsDebug(store->opcode())) continue;
1499 if (store->IsNonSemantic()) continue;
1500 if (spvOpcodeIsDecoration(store->opcode())) continue;
1501 if (store->opcode() != spv::Op::OpStore) {
1502 return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
1503 << "FPRoundingMode decoration can be applied only to the "
1504 "Object operand of an OpStore.";
1505 }
1506
1507 if (use.second != 2) {
1508 return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
1509 << "FPRoundingMode decoration can be applied only to the "
1510 "Object operand of an OpStore.";
1511 }
1512
1513 const auto ptr_inst = vstate.FindDef(store->GetOperandAs<uint32_t>(0));
1514 const auto ptr_type = vstate.FindDef(ptr_inst->GetOperandAs<uint32_t>(0));
1515
1516 const auto half_float_id = ptr_type->GetOperandAs<uint32_t>(2);
1517 if (!vstate.IsFloatScalarOrVectorType(half_float_id) ||
1518 vstate.GetBitWidth(half_float_id) != 16) {
1519 return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
1520 << "FPRoundingMode decoration can be applied only to the "
1521 "Object operand of an OpStore storing through a pointer "
1522 "to "
1523 "a 16-bit floating-point scalar or vector object.";
1524 }
1525
1526 // Validates storage class of the pointer to the OpStore
1527 const auto storage = ptr_type->GetOperandAs<spv::StorageClass>(1);
1528 if (storage != spv::StorageClass::StorageBuffer &&
1529 storage != spv::StorageClass::Uniform &&
1530 storage != spv::StorageClass::PushConstant &&
1531 storage != spv::StorageClass::Input &&
1532 storage != spv::StorageClass::Output &&
1533 storage != spv::StorageClass::PhysicalStorageBuffer) {
1534 return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
1535 << "FPRoundingMode decoration can be applied only to the "
1536 "Object operand of an OpStore in the StorageBuffer, "
1537 "PhysicalStorageBuffer, Uniform, PushConstant, Input, or "
1538 "Output Storage Classes.";
1539 }
1540 }
1541 return SPV_SUCCESS;
1542 }
1543
1544 // Returns SPV_SUCCESS if validation rules are satisfied for the NonWritable
1545 // decoration. Otherwise emits a diagnostic and returns something other than
1546 // SPV_SUCCESS. The |inst| parameter is the object being decorated. This must
1547 // be called after TypePass and AnnotateCheckDecorationsOfBuffers are called.
CheckNonWritableDecoration(ValidationState_t & vstate,const Instruction & inst,const Decoration & decoration)1548 spv_result_t CheckNonWritableDecoration(ValidationState_t& vstate,
1549 const Instruction& inst,
1550 const Decoration& decoration) {
1551 assert(inst.id() && "Parser ensures the target of the decoration has an ID");
1552
1553 if (decoration.struct_member_index() == Decoration::kInvalidMember) {
1554 // The target must be a memory object declaration.
1555 // First, it must be a variable or function parameter.
1556 const auto opcode = inst.opcode();
1557 const auto type_id = inst.type_id();
1558 if (opcode != spv::Op::OpVariable &&
1559 opcode != spv::Op::OpFunctionParameter) {
1560 return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
1561 << "Target of NonWritable decoration must be a memory object "
1562 "declaration (a variable or a function parameter)";
1563 }
1564 const auto var_storage_class = opcode == spv::Op::OpVariable
1565 ? inst.GetOperandAs<spv::StorageClass>(2)
1566 : spv::StorageClass::Max;
1567 if ((var_storage_class == spv::StorageClass::Function ||
1568 var_storage_class == spv::StorageClass::Private) &&
1569 vstate.features().nonwritable_var_in_function_or_private) {
1570 // New permitted feature in SPIR-V 1.4.
1571 } else if (
1572 // It may point to a UBO, SSBO, or storage image.
1573 vstate.IsPointerToUniformBlock(type_id) ||
1574 vstate.IsPointerToStorageBuffer(type_id) ||
1575 vstate.IsPointerToStorageImage(type_id)) {
1576 } else {
1577 return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
1578 << "Target of NonWritable decoration is invalid: must point to a "
1579 "storage image, uniform block, "
1580 << (vstate.features().nonwritable_var_in_function_or_private
1581 ? "storage buffer, or variable in Private or Function "
1582 "storage class"
1583 : "or storage buffer");
1584 }
1585 }
1586
1587 return SPV_SUCCESS;
1588 }
1589
1590 // Returns SPV_SUCCESS if validation rules are satisfied for Uniform or
1591 // UniformId decorations. Otherwise emits a diagnostic and returns something
1592 // other than SPV_SUCCESS. Assumes each decoration on a group has been
1593 // propagated down to the group members. The |inst| parameter is the object
1594 // being decorated.
CheckUniformDecoration(ValidationState_t & vstate,const Instruction & inst,const Decoration & decoration)1595 spv_result_t CheckUniformDecoration(ValidationState_t& vstate,
1596 const Instruction& inst,
1597 const Decoration& decoration) {
1598 const char* const dec_name = decoration.dec_type() == spv::Decoration::Uniform
1599 ? "Uniform"
1600 : "UniformId";
1601
1602 // Uniform or UniformId must decorate an "object"
1603 // - has a result ID
1604 // - is an instantiation of a non-void type. So it has a type ID, and that
1605 // type is not void.
1606
1607 // We already know the result ID is non-zero.
1608
1609 if (inst.type_id() == 0) {
1610 return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
1611 << dec_name << " decoration applied to a non-object";
1612 }
1613 if (Instruction* type_inst = vstate.FindDef(inst.type_id())) {
1614 if (type_inst->opcode() == spv::Op::OpTypeVoid) {
1615 return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
1616 << dec_name << " decoration applied to a value with void type";
1617 }
1618 } else {
1619 // We might never get here because this would have been rejected earlier in
1620 // the flow.
1621 return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
1622 << dec_name << " decoration applied to an object with invalid type";
1623 }
1624
1625 // Use of Uniform with OpDecorate is checked elsewhere.
1626 // Use of UniformId with OpDecorateId is checked elsewhere.
1627
1628 if (decoration.dec_type() == spv::Decoration::UniformId) {
1629 assert(decoration.params().size() == 1 &&
1630 "Grammar ensures UniformId has one parameter");
1631
1632 // The scope id is an execution scope.
1633 if (auto error =
1634 ValidateExecutionScope(vstate, &inst, decoration.params()[0]))
1635 return error;
1636 }
1637
1638 return SPV_SUCCESS;
1639 }
1640
1641 // Returns SPV_SUCCESS if validation rules are satisfied for NoSignedWrap or
1642 // NoUnsignedWrap decorations. Otherwise emits a diagnostic and returns
1643 // something other than SPV_SUCCESS. Assumes each decoration on a group has been
1644 // propagated down to the group members.
CheckIntegerWrapDecoration(ValidationState_t & vstate,const Instruction & inst,const Decoration & decoration)1645 spv_result_t CheckIntegerWrapDecoration(ValidationState_t& vstate,
1646 const Instruction& inst,
1647 const Decoration& decoration) {
1648 switch (inst.opcode()) {
1649 case spv::Op::OpIAdd:
1650 case spv::Op::OpISub:
1651 case spv::Op::OpIMul:
1652 case spv::Op::OpShiftLeftLogical:
1653 case spv::Op::OpSNegate:
1654 return SPV_SUCCESS;
1655 case spv::Op::OpExtInst:
1656 // TODO(dneto): Only certain extended instructions allow these
1657 // decorations. For now allow anything.
1658 return SPV_SUCCESS;
1659 default:
1660 break;
1661 }
1662
1663 return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
1664 << (decoration.dec_type() == spv::Decoration::NoSignedWrap
1665 ? "NoSignedWrap"
1666 : "NoUnsignedWrap")
1667 << " decoration may not be applied to "
1668 << spvOpcodeString(inst.opcode());
1669 }
1670
1671 // Returns SPV_SUCCESS if validation rules are satisfied for the Component
1672 // decoration. Otherwise emits a diagnostic and returns something other than
1673 // SPV_SUCCESS.
CheckComponentDecoration(ValidationState_t & vstate,const Instruction & inst,const Decoration & decoration)1674 spv_result_t CheckComponentDecoration(ValidationState_t& vstate,
1675 const Instruction& inst,
1676 const Decoration& decoration) {
1677 assert(inst.id() && "Parser ensures the target of the decoration has an ID");
1678 assert(decoration.params().size() == 1 &&
1679 "Grammar ensures Component has one parameter");
1680
1681 uint32_t type_id;
1682 if (decoration.struct_member_index() == Decoration::kInvalidMember) {
1683 // The target must be a memory object declaration.
1684 const auto opcode = inst.opcode();
1685 if (opcode != spv::Op::OpVariable &&
1686 opcode != spv::Op::OpFunctionParameter) {
1687 return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
1688 << "Target of Component decoration must be a memory object "
1689 "declaration (a variable or a function parameter)";
1690 }
1691
1692 // Only valid for the Input and Output Storage Classes.
1693 const auto storage_class = opcode == spv::Op::OpVariable
1694 ? inst.GetOperandAs<spv::StorageClass>(2)
1695 : spv::StorageClass::Max;
1696 if (storage_class != spv::StorageClass::Input &&
1697 storage_class != spv::StorageClass::Output &&
1698 storage_class != spv::StorageClass::Max) {
1699 return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
1700 << "Target of Component decoration is invalid: must point to a "
1701 "Storage Class of Input(1) or Output(3). Found Storage "
1702 "Class "
1703 << uint32_t(storage_class);
1704 }
1705
1706 type_id = inst.type_id();
1707 if (vstate.IsPointerType(type_id)) {
1708 const auto pointer = vstate.FindDef(type_id);
1709 type_id = pointer->GetOperandAs<uint32_t>(2);
1710 }
1711 } else {
1712 if (inst.opcode() != spv::Op::OpTypeStruct) {
1713 return vstate.diag(SPV_ERROR_INVALID_DATA, &inst)
1714 << "Attempted to get underlying data type via member index for "
1715 "non-struct type.";
1716 }
1717 type_id = inst.word(decoration.struct_member_index() + 2);
1718 }
1719
1720 if (spvIsVulkanEnv(vstate.context()->target_env)) {
1721 // Strip the array, if present.
1722 if (vstate.GetIdOpcode(type_id) == spv::Op::OpTypeArray) {
1723 type_id = vstate.FindDef(type_id)->word(2u);
1724 }
1725
1726 if (!vstate.IsIntScalarOrVectorType(type_id) &&
1727 !vstate.IsFloatScalarOrVectorType(type_id)) {
1728 return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
1729 << vstate.VkErrorID(4924)
1730 << "Component decoration specified for type "
1731 << vstate.getIdName(type_id) << " that is not a scalar or vector";
1732 }
1733
1734 const auto component = decoration.params()[0];
1735 if (component > 3) {
1736 return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
1737 << vstate.VkErrorID(4920)
1738 << "Component decoration value must not be greater than 3";
1739 }
1740
1741 const auto dimension = vstate.GetDimension(type_id);
1742 const auto bit_width = vstate.GetBitWidth(type_id);
1743 if (bit_width == 16 || bit_width == 32) {
1744 const auto sum_component = component + dimension;
1745 if (sum_component > 4) {
1746 return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
1747 << vstate.VkErrorID(4921)
1748 << "Sequence of components starting with " << component
1749 << " and ending with " << (sum_component - 1)
1750 << " gets larger than 3";
1751 }
1752 } else if (bit_width == 64) {
1753 if (dimension > 2) {
1754 return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
1755 << vstate.VkErrorID(7703)
1756 << "Component decoration only allowed on 64-bit scalar and "
1757 "2-component vector";
1758 }
1759 if (component == 1 || component == 3) {
1760 return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
1761 << vstate.VkErrorID(4923)
1762 << "Component decoration value must not be 1 or 3 for 64-bit "
1763 "data types";
1764 }
1765 // 64-bit is double per component dimension
1766 const auto sum_component = component + (2 * dimension);
1767 if (sum_component > 4) {
1768 return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
1769 << vstate.VkErrorID(4922)
1770 << "Sequence of components starting with " << component
1771 << " and ending with " << (sum_component - 1)
1772 << " gets larger than 3";
1773 }
1774 }
1775 }
1776
1777 return SPV_SUCCESS;
1778 }
1779
1780 // Returns SPV_SUCCESS if validation rules are satisfied for the Block
1781 // decoration. Otherwise emits a diagnostic and returns something other than
1782 // SPV_SUCCESS.
CheckBlockDecoration(ValidationState_t & vstate,const Instruction & inst,const Decoration & decoration)1783 spv_result_t CheckBlockDecoration(ValidationState_t& vstate,
1784 const Instruction& inst,
1785 const Decoration& decoration) {
1786 assert(inst.id() && "Parser ensures the target of the decoration has an ID");
1787 if (inst.opcode() != spv::Op::OpTypeStruct) {
1788 const char* const dec_name = decoration.dec_type() == spv::Decoration::Block
1789 ? "Block"
1790 : "BufferBlock";
1791 return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
1792 << dec_name << " decoration on a non-struct type.";
1793 }
1794 return SPV_SUCCESS;
1795 }
1796
CheckLocationDecoration(ValidationState_t & vstate,const Instruction & inst,const Decoration & decoration)1797 spv_result_t CheckLocationDecoration(ValidationState_t& vstate,
1798 const Instruction& inst,
1799 const Decoration& decoration) {
1800 if (inst.opcode() == spv::Op::OpVariable) return SPV_SUCCESS;
1801
1802 if (decoration.struct_member_index() != Decoration::kInvalidMember &&
1803 inst.opcode() == spv::Op::OpTypeStruct) {
1804 return SPV_SUCCESS;
1805 }
1806
1807 return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
1808 << "Location decoration can only be applied to a variable or member "
1809 "of a structure type";
1810 }
1811
CheckRelaxPrecisionDecoration(ValidationState_t & vstate,const Instruction & inst,const Decoration & decoration)1812 spv_result_t CheckRelaxPrecisionDecoration(ValidationState_t& vstate,
1813 const Instruction& inst,
1814 const Decoration& decoration) {
1815 // This is not the most precise check, but the rules for RelaxPrecision are
1816 // very general, and it will be difficult to implement precisely. For now,
1817 // I will only check for the cases that cause problems for the optimizer.
1818 if (!spvOpcodeGeneratesType(inst.opcode())) {
1819 return SPV_SUCCESS;
1820 }
1821
1822 if (decoration.struct_member_index() != Decoration::kInvalidMember &&
1823 inst.opcode() == spv::Op::OpTypeStruct) {
1824 return SPV_SUCCESS;
1825 }
1826 return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
1827 << "RelaxPrecision decoration cannot be applied to a type";
1828 }
1829
1830 #define PASS_OR_BAIL_AT_LINE(X, LINE) \
1831 { \
1832 spv_result_t e##LINE = (X); \
1833 if (e##LINE != SPV_SUCCESS) return e##LINE; \
1834 } static_assert(true, "require extra semicolon")
1835 #define PASS_OR_BAIL(X) PASS_OR_BAIL_AT_LINE(X, __LINE__)
1836
1837 // Check rules for decorations where we start from the decoration rather
1838 // than the decorated object. Assumes each decoration on a group have been
1839 // propagated down to the group members.
CheckDecorationsFromDecoration(ValidationState_t & vstate)1840 spv_result_t CheckDecorationsFromDecoration(ValidationState_t& vstate) {
1841 // Some rules are only checked for shaders.
1842 const bool is_shader = vstate.HasCapability(spv::Capability::Shader);
1843
1844 for (const auto& kv : vstate.id_decorations()) {
1845 const uint32_t id = kv.first;
1846 const auto& decorations = kv.second;
1847 if (decorations.empty()) continue;
1848
1849 const Instruction* inst = vstate.FindDef(id);
1850 assert(inst);
1851
1852 // We assume the decorations applied to a decoration group have already
1853 // been propagated down to the group members.
1854 if (inst->opcode() == spv::Op::OpDecorationGroup) continue;
1855
1856 for (const auto& decoration : decorations) {
1857 switch (decoration.dec_type()) {
1858 case spv::Decoration::Component:
1859 PASS_OR_BAIL(CheckComponentDecoration(vstate, *inst, decoration));
1860 break;
1861 case spv::Decoration::FPRoundingMode:
1862 if (is_shader)
1863 PASS_OR_BAIL(
1864 CheckFPRoundingModeForShaders(vstate, *inst, decoration));
1865 break;
1866 case spv::Decoration::NonWritable:
1867 PASS_OR_BAIL(CheckNonWritableDecoration(vstate, *inst, decoration));
1868 break;
1869 case spv::Decoration::Uniform:
1870 case spv::Decoration::UniformId:
1871 PASS_OR_BAIL(CheckUniformDecoration(vstate, *inst, decoration));
1872 break;
1873 case spv::Decoration::NoSignedWrap:
1874 case spv::Decoration::NoUnsignedWrap:
1875 PASS_OR_BAIL(CheckIntegerWrapDecoration(vstate, *inst, decoration));
1876 break;
1877 case spv::Decoration::Block:
1878 case spv::Decoration::BufferBlock:
1879 PASS_OR_BAIL(CheckBlockDecoration(vstate, *inst, decoration));
1880 break;
1881 case spv::Decoration::Location:
1882 PASS_OR_BAIL(CheckLocationDecoration(vstate, *inst, decoration));
1883 break;
1884 case spv::Decoration::RelaxedPrecision:
1885 PASS_OR_BAIL(
1886 CheckRelaxPrecisionDecoration(vstate, *inst, decoration));
1887 break;
1888 default:
1889 break;
1890 }
1891 }
1892 }
1893 return SPV_SUCCESS;
1894 }
1895
1896 } // namespace
1897
ValidateDecorations(ValidationState_t & vstate)1898 spv_result_t ValidateDecorations(ValidationState_t& vstate) {
1899 if (auto error = CheckImportedVariableInitialization(vstate)) return error;
1900 if (auto error = CheckDecorationsOfEntryPoints(vstate)) return error;
1901 if (auto error = CheckDecorationsOfBuffers(vstate)) return error;
1902 if (auto error = CheckDecorationsCompatibility(vstate)) return error;
1903 if (auto error = CheckLinkageAttrOfFunctions(vstate)) return error;
1904 if (auto error = CheckVulkanMemoryModelDeprecatedDecorations(vstate))
1905 return error;
1906 if (auto error = CheckDecorationsFromDecoration(vstate)) return error;
1907 return SPV_SUCCESS;
1908 }
1909
1910 } // namespace val
1911 } // namespace spvtools
1912