Skip to content

Commit 90cfb3e

Browse files
spirv-val: Give hints when user is forgetting feature bit (KhronosGroup#6164)
1 parent ec1c9ca commit 90cfb3e

File tree

5 files changed

+120
-63
lines changed

5 files changed

+120
-63
lines changed

source/val/validate_decorations.cpp

Lines changed: 91 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -398,11 +398,29 @@ bool IsAlignedTo(uint32_t offset, uint32_t alignment) {
398398
return 0 == (offset % alignment);
399399
}
400400

401+
std::string getStorageClassString(spv::StorageClass sc) {
402+
switch (sc) {
403+
case spv::StorageClass::Uniform:
404+
return "Uniform";
405+
case spv::StorageClass::UniformConstant:
406+
return "UniformConstant";
407+
case spv::StorageClass::PushConstant:
408+
return "PushConstant";
409+
case spv::StorageClass::Workgroup:
410+
return "Workgroup";
411+
case spv::StorageClass::PhysicalStorageBuffer:
412+
return "PhysicalStorageBuffer";
413+
default:
414+
// Only other valid storage class in these checks
415+
return "StorageBuffer";
416+
}
417+
}
418+
401419
// Returns SPV_SUCCESS if the given struct satisfies standard layout rules for
402420
// Block or BufferBlocks in Vulkan. Otherwise emits a diagnostic and returns
403421
// something other than SPV_SUCCESS. Matrices inherit the specified column
404422
// or row major-ness.
405-
spv_result_t checkLayout(uint32_t struct_id, const char* storage_class_str,
423+
spv_result_t checkLayout(uint32_t struct_id, spv::StorageClass storage_class,
406424
const char* decoration_str, bool blockRules,
407425
bool scalar_block_layout, uint32_t incoming_offset,
408426
MemberConstraints& constraints,
@@ -418,22 +436,48 @@ spv_result_t checkLayout(uint32_t struct_id, const char* storage_class_str,
418436
// is more permissive than relaxed layout.
419437
const bool relaxed_block_layout = vstate.IsRelaxedBlockLayout();
420438

421-
auto fail = [&vstate, struct_id, storage_class_str, decoration_str,
422-
blockRules, relaxed_block_layout,
439+
auto fail = [&vstate, struct_id, storage_class, decoration_str, blockRules,
440+
relaxed_block_layout,
423441
scalar_block_layout](uint32_t member_idx) -> DiagnosticStream {
424-
DiagnosticStream ds =
425-
std::move(vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(struct_id))
426-
<< "Structure id " << struct_id << " decorated as "
427-
<< decoration_str << " for variable in " << storage_class_str
428-
<< " storage class must follow "
429-
<< (scalar_block_layout
430-
? "scalar "
431-
: (relaxed_block_layout ? "relaxed " : "standard "))
432-
<< (blockRules ? "uniform buffer" : "storage buffer")
433-
<< " layout rules: member " << member_idx << " ");
442+
DiagnosticStream ds = std::move(
443+
vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(struct_id))
444+
<< "Structure id " << struct_id << " decorated as " << decoration_str
445+
<< " for variable in " << getStorageClassString(storage_class)
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 << " ");
434452
return ds;
435453
};
436454

455+
// People often use spirv-val from Vulkan Validation Layers, it ends up
456+
// mapping the various block layout rules from the enabled feature. This
457+
// offers a hint to help the user understand possbily why things are not
458+
// working when the shader itself "seems" valid, but just was a lack of adding
459+
// a supported feature
460+
auto extra = [&vstate, scalar_block_layout, storage_class,
461+
relaxed_block_layout, blockRules]() {
462+
if (!scalar_block_layout) {
463+
if (storage_class == spv::StorageClass::Workgroup) {
464+
return vstate.MissingFeature(
465+
"workgroupMemoryExplicitLayoutScalarBlockLayout feature",
466+
"--workgroup-scalar-block-layout", true);
467+
} else if (!relaxed_block_layout) {
468+
return vstate.MissingFeature("VK_KHR_relaxed_block_layout extension",
469+
"--relax-block-layout", true);
470+
} else if (blockRules) {
471+
return vstate.MissingFeature("uniformBufferStandardLayout feature",
472+
"--uniform-buffer-standard-layout", true);
473+
} else {
474+
return vstate.MissingFeature("scalarBlockLayoutfeature feature",
475+
"--scalar-block-layout", true);
476+
}
477+
}
478+
return std::string("");
479+
};
480+
437481
// If we are checking the layout of untyped pointers or physical storage
438482
// buffer pointers, we may not actually have a struct here. Instead, pretend
439483
// we have a struct with a single member at offset 0.
@@ -507,7 +551,7 @@ spv_result_t checkLayout(uint32_t struct_id, const char* storage_class_str,
507551
const auto size = getSize(id, constraint, constraints, vstate);
508552
// Check offset.
509553
if (offset == 0xffffffff)
510-
return fail(memberIdx) << "is missing an Offset decoration";
554+
return fail(memberIdx) << "is missing an Offset decoration" << extra();
511555

512556
if (opcode == spv::Op::OpTypeRuntimeArray &&
513557
ordered_member_idx != member_offsets.size() - 1) {
@@ -524,42 +568,44 @@ spv_result_t checkLayout(uint32_t struct_id, const char* storage_class_str,
524568
const auto componentId = inst->words()[2];
525569
const auto scalar_alignment = getScalarAlignment(componentId, vstate);
526570
if (!IsAlignedTo(offset, scalar_alignment)) {
527-
return fail(memberIdx)
528-
<< "at offset " << offset
529-
<< " is not aligned to scalar element size " << scalar_alignment;
571+
return fail(memberIdx) << "at offset " << offset
572+
<< " is not aligned to scalar element size "
573+
<< scalar_alignment << extra();
530574
}
531575
} else {
532576
// Without relaxed block layout, the offset must be divisible by the
533577
// alignment requirement.
534578
if (!IsAlignedTo(offset, alignment)) {
535-
return fail(memberIdx)
536-
<< "at offset " << offset << " is not aligned to " << alignment;
579+
return fail(memberIdx) << "at offset " << offset
580+
<< " is not aligned to " << alignment << extra();
537581
}
538582
}
539583
if (offset < nextValidOffset)
540584
return fail(memberIdx) << "at offset " << offset
541585
<< " overlaps previous member ending at offset "
542-
<< nextValidOffset - 1;
586+
<< nextValidOffset - 1 << extra();
543587
if (!scalar_block_layout && relaxed_block_layout) {
544588
// Check improper straddle of vectors.
545589
if (spv::Op::OpTypeVector == opcode &&
546590
hasImproperStraddle(id, offset, constraint, constraints, vstate))
547591
return fail(memberIdx)
548-
<< "is an improperly straddling vector at offset " << offset;
592+
<< "is an improperly straddling vector at offset " << offset
593+
<< extra();
549594
}
550595
// Check struct members recursively.
551596
spv_result_t recursive_status = SPV_SUCCESS;
552597
if (spv::Op::OpTypeStruct == opcode &&
553598
SPV_SUCCESS != (recursive_status = checkLayout(
554-
id, storage_class_str, decoration_str, blockRules,
599+
id, storage_class, decoration_str, blockRules,
555600
scalar_block_layout, offset, constraints, vstate)))
556601
return recursive_status;
557602
// Check matrix stride.
558603
if (spv::Op::OpTypeMatrix == opcode) {
559604
const auto stride = constraint.matrix_stride;
560605
if (!IsAlignedTo(stride, alignment)) {
561-
return fail(memberIdx) << "is a matrix with stride " << stride
562-
<< " not satisfying alignment to " << alignment;
606+
return fail(memberIdx)
607+
<< "is a matrix with stride " << stride
608+
<< " not satisfying alignment to " << alignment << extra();
563609
}
564610
}
565611

@@ -576,12 +622,13 @@ spv_result_t checkLayout(uint32_t struct_id, const char* storage_class_str,
576622
if (spv::Decoration::ArrayStride == decoration.dec_type()) {
577623
array_stride = decoration.params()[0];
578624
if (array_stride == 0) {
579-
return fail(memberIdx) << "contains an array with stride 0";
625+
return fail(memberIdx)
626+
<< "contains an array with stride 0" << extra();
580627
}
581628
if (!IsAlignedTo(array_stride, array_alignment))
582629
return fail(memberIdx)
583630
<< "contains an array with stride " << decoration.params()[0]
584-
<< " not satisfying alignment to " << alignment;
631+
<< " not satisfying alignment to " << alignment << extra();
585632
}
586633
}
587634

@@ -608,7 +655,7 @@ spv_result_t checkLayout(uint32_t struct_id, const char* storage_class_str,
608655

609656
if (SPV_SUCCESS !=
610657
(recursive_status = checkLayout(
611-
typeId, storage_class_str, decoration_str, blockRules,
658+
typeId, storage_class, decoration_str, blockRules,
612659
scalar_block_layout, next_offset, constraints, vstate)))
613660
return recursive_status;
614661

@@ -620,7 +667,7 @@ spv_result_t checkLayout(uint32_t struct_id, const char* storage_class_str,
620667
if (!IsAlignedTo(stride, alignment)) {
621668
return fail(memberIdx)
622669
<< "is a matrix with stride " << stride
623-
<< " not satisfying alignment to " << alignment;
670+
<< " not satisfying alignment to " << alignment << extra();
624671
}
625672
}
626673

@@ -636,7 +683,7 @@ spv_result_t checkLayout(uint32_t struct_id, const char* storage_class_str,
636683
if (element_size > array_stride) {
637684
return fail(memberIdx)
638685
<< "contains an array with stride " << array_stride
639-
<< ", but with an element size of " << element_size;
686+
<< ", but with an element size of " << element_size << extra();
640687
}
641688
}
642689
nextValidOffset = offset + size;
@@ -1207,8 +1254,8 @@ spv_result_t CheckDecorationsOfBuffers(ValidationState_t& vstate) {
12071254
if (!entry_points.empty() &&
12081255
!hasDecoration(var_id, spv::Decoration::Binding, vstate)) {
12091256
return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(var_id))
1210-
<< (uniform ? "Uniform" : "Storage Buffer") << " id '"
1211-
<< var_id << "' is missing Binding decoration.\n"
1257+
<< getStorageClassString(storageClass) << " id '" << var_id
1258+
<< "' is missing Binding decoration.\n"
12121259
<< "From ARB_gl_spirv extension:\n"
12131260
<< "Uniform and shader storage block variables must "
12141261
<< "also be decorated with a *Binding*.";
@@ -1243,12 +1290,6 @@ spv_result_t CheckDecorationsOfBuffers(ValidationState_t& vstate) {
12431290
ComputeMemberConstraintsForStruct(&constraints, id,
12441291
LayoutConstraints(), vstate);
12451292
}
1246-
// Prepare for messages
1247-
const char* sc_str =
1248-
uniform
1249-
? "Uniform"
1250-
: (push_constant ? "PushConstant"
1251-
: (workgroup ? "Workgroup" : "StorageBuffer"));
12521293

12531294
if (spvIsVulkanEnv(vstate.context()->target_env)) {
12541295
const bool block = hasDecoration(id, spv::Decoration::Block, vstate);
@@ -1294,7 +1335,8 @@ spv_result_t CheckDecorationsOfBuffers(ValidationState_t& vstate) {
12941335
!hasDecoration(var_id, spv::Decoration::DescriptorSet,
12951336
vstate)) {
12961337
return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(var_id))
1297-
<< vstate.VkErrorID(6677) << sc_str << " id '" << var_id
1338+
<< vstate.VkErrorID(6677)
1339+
<< getStorageClassString(storageClass) << " id '" << var_id
12981340
<< "' is missing DescriptorSet decoration.\n"
12991341
<< "From Vulkan spec:\n"
13001342
<< "These variables must have DescriptorSet and Binding "
@@ -1303,7 +1345,8 @@ spv_result_t CheckDecorationsOfBuffers(ValidationState_t& vstate) {
13031345
if (!entry_points.empty() &&
13041346
!hasDecoration(var_id, spv::Decoration::Binding, vstate)) {
13051347
return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(var_id))
1306-
<< vstate.VkErrorID(6677) << sc_str << " id '" << var_id
1348+
<< vstate.VkErrorID(6677)
1349+
<< getStorageClassString(storageClass) << " id '" << var_id
13071350
<< "' is missing Binding decoration.\n"
13081351
<< "From Vulkan spec:\n"
13091352
<< "These variables must have DescriptorSet and Binding "
@@ -1386,14 +1429,14 @@ spv_result_t CheckDecorationsOfBuffers(ValidationState_t& vstate) {
13861429
if (spvIsVulkanEnv(vstate.context()->target_env)) {
13871430
if (blockRules &&
13881431
(SPV_SUCCESS !=
1389-
(recursive_status = checkLayout(id, sc_str, deco_str, true,
1390-
scalar_block_layout, 0,
1391-
constraints, vstate)))) {
1432+
(recursive_status = checkLayout(
1433+
id, storageClass, deco_str, true, scalar_block_layout,
1434+
0, constraints, vstate)))) {
13921435
return recursive_status;
13931436
} else if (bufferRules &&
13941437
(SPV_SUCCESS != (recursive_status = checkLayout(
1395-
id, sc_str, deco_str, false,
1396-
scalar_block_layout, 0,
1438+
id, storageClass, deco_str,
1439+
false, scalar_block_layout, 0,
13971440
constraints, vstate)))) {
13981441
return recursive_status;
13991442
}
@@ -1413,9 +1456,9 @@ spv_result_t CheckDecorationsOfBuffers(ValidationState_t& vstate) {
14131456
ComputeMemberConstraintsForStruct(&constraints, pointee_type_id,
14141457
LayoutConstraints(), vstate);
14151458
}
1416-
if (auto res = checkLayout(pointee_type_id, "PhysicalStorageBuffer",
1417-
"Block", !buffer, scalar_block_layout, 0,
1418-
constraints, vstate)) {
1459+
if (auto res = checkLayout(
1460+
pointee_type_id, spv::StorageClass::PhysicalStorageBuffer,
1461+
"Block", !buffer, scalar_block_layout, 0, constraints, vstate)) {
14191462
return res;
14201463
}
14211464
} else if (vstate.HasCapability(spv::Capability::UntypedPointersKHR) &&
@@ -1464,14 +1507,6 @@ spv_result_t CheckDecorationsOfBuffers(ValidationState_t& vstate) {
14641507
const auto sc =
14651508
vstate.FindDef(ptr_ty_id)->GetOperandAs<spv::StorageClass>(1);
14661509

1467-
const char* sc_str =
1468-
sc == spv::StorageClass::Uniform
1469-
? "Uniform"
1470-
: (sc == spv::StorageClass::PushConstant
1471-
? "PushConstant"
1472-
: (sc == spv::StorageClass::Workgroup ? "Workgroup"
1473-
: "StorageBuffer"));
1474-
14751510
auto data_type = vstate.FindDef(data_type_id);
14761511
scalar_block_layout =
14771512
sc == spv::StorageClass::Workgroup
@@ -1511,7 +1546,7 @@ spv_result_t CheckDecorationsOfBuffers(ValidationState_t& vstate) {
15111546
? (sc == spv::StorageClass::Uniform ? "BufferBlock" : "Block")
15121547
: "Block";
15131548
if (auto result =
1514-
checkLayout(data_type_id, sc_str, deco_str, !bufferRules,
1549+
checkLayout(data_type_id, sc, deco_str, !bufferRules,
15151550
scalar_block_layout, 0, constraints, vstate)) {
15161551
return result;
15171552
}

source/val/validate_image.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -464,7 +464,9 @@ spv_result_t ValidateImageOperands(ValidationState_t& _,
464464
return _.diag(SPV_ERROR_INVALID_DATA, inst)
465465
<< _.VkErrorID(10213)
466466
<< "Image Operand Offset can only be used with "
467-
"OpImage*Gather operations";
467+
"OpImage*Gather operations."
468+
<< _.MissingFeature("maintenance8 feature",
469+
"--allow-offset-texture-operand", false);
468470
}
469471
}
470472
}

source/val/validate_mode_setting.cpp

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -557,6 +557,7 @@ spv_result_t ValidateExecutionMode(ValidationState_t& _,
557557
"Operands that are not id operands.";
558558
}
559559

560+
const bool is_vulkan_env = (spvIsVulkanEnv(_.context()->target_env));
560561
const auto* models = _.GetExecutionModels(entry_point_id);
561562
switch (mode) {
562563
case spv::ExecutionMode::Invocations:
@@ -667,7 +668,7 @@ spv_result_t ValidateExecutionMode(ValidationState_t& _,
667668
"tessellation execution model.";
668669
}
669670
}
670-
if (spvIsVulkanEnv(_.context()->target_env)) {
671+
if (is_vulkan_env) {
671672
if (_.HasCapability(spv::Capability::MeshShadingEXT) &&
672673
inst->GetOperandAs<uint32_t>(2) == 0) {
673674
return _.diag(SPV_ERROR_INVALID_DATA, inst)
@@ -690,8 +691,7 @@ spv_result_t ValidateExecutionMode(ValidationState_t& _,
690691
"execution "
691692
"model.";
692693
}
693-
if (mode == spv::ExecutionMode::OutputPrimitivesEXT &&
694-
spvIsVulkanEnv(_.context()->target_env)) {
694+
if (mode == spv::ExecutionMode::OutputPrimitivesEXT && is_vulkan_env) {
695695
if (_.HasCapability(spv::Capability::MeshShadingEXT) &&
696696
inst->GetOperandAs<uint32_t>(2) == 0) {
697697
return _.diag(SPV_ERROR_INVALID_DATA, inst)
@@ -761,9 +761,15 @@ spv_result_t ValidateExecutionMode(ValidationState_t& _,
761761
break;
762762
case spv::ExecutionMode::LocalSize:
763763
case spv::ExecutionMode::LocalSizeId:
764-
if (mode == spv::ExecutionMode::LocalSizeId && !_.IsLocalSizeIdAllowed())
764+
if (mode == spv::ExecutionMode::LocalSizeId &&
765+
!_.IsLocalSizeIdAllowed()) {
765766
return _.diag(SPV_ERROR_INVALID_DATA, inst)
766-
<< "LocalSizeId mode is not allowed by the current environment.";
767+
<< "LocalSizeId mode is not allowed by the current environment."
768+
<< (is_vulkan_env
769+
? _.MissingFeature("maintenance4 feature",
770+
"--allow-localsizeid", false)
771+
: "");
772+
}
767773

768774
if (!std::all_of(
769775
models->begin(), models->end(),
@@ -812,7 +818,7 @@ spv_result_t ValidateExecutionMode(ValidationState_t& _,
812818
}
813819
}
814820

815-
if (spvIsVulkanEnv(_.context()->target_env)) {
821+
if (is_vulkan_env) {
816822
if (mode == spv::ExecutionMode::OriginLowerLeft) {
817823
return _.diag(SPV_ERROR_INVALID_DATA, inst)
818824
<< _.VkErrorID(4653)

source/val/validation_state.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1949,6 +1949,14 @@ bool ValidationState_t::IsValidStorageClass(
19491949
return true;
19501950
}
19511951

1952+
std::string ValidationState_t::MissingFeature(const std::string& feature,
1953+
const std::string& cmdline,
1954+
bool hint) const {
1955+
return "\nThis is " + (hint ? std::string("may be ") : "") +
1956+
"allowed if you enable the " + feature + " (or use the " + cmdline +
1957+
" command line flag)";
1958+
}
1959+
19521960
#define VUID_WRAP(vuid) "[" #vuid "] "
19531961

19541962
// Currently no 2 VUID share the same id, so no need for |reference|

source/val/validation_state.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -844,6 +844,12 @@ class ValidationState_t {
844844
// Validates the storage class for the target environment.
845845
bool IsValidStorageClass(spv::StorageClass storage_class) const;
846846

847+
// Helps formulate a mesesage to user that setting one of the validator
848+
// options might make their SPIR-V actually valid The |hint| option is because
849+
// some checks are intertwined with each other, so hard to give confirmation
850+
std::string MissingFeature(const std::string& feature,
851+
const std::string& cmdline, bool hint) const;
852+
847853
// Takes a Vulkan Valid Usage ID (VUID) as |id| and optional |reference| and
848854
// will return a non-empty string only if ID is known and targeting Vulkan.
849855
// VUIDs are found in the Vulkan-Docs repo in the form "[[VUID-ref-ref-id]]"

0 commit comments

Comments
 (0)