Skip to content

Commit 7fa421b

Browse files
authored
spirv-val: Add remaining VUID check and unit tests for VK_QCOM_tile_s… (KhronosGroup#6660)
# VUID check - `VUID-StandaloneSpirv-TileImageEXT-08723` - `VUID-StandaloneSpirv-TileAttachmentQCOM-10689` - `VUID-TileApronSizeQCOM-TileApronSizeQCOM-10632` - `VUID-TileApronSizeQCOM-TileApronSizeQCOM-10633` - `VUID-TileApronSizeQCOM-TileApronSizeQCOM-10634` - `VUID-TileOffsetQCOM-TileOffsetQCOM-10626` - `VUID-TileOffsetQCOM-TileOffsetQCOM-10627` - `VUID-TileOffsetQCOM-TileOffsetQCOM-10628` - `VUID-TileDimensionQCOM-TileDimensionQCOM-10629` - `VUID-TileDimensionQCOM-TileDimensionQCOM-10630` - `VUID-TileDimensionQCOM-TileDimensionQCOM-10631` - `VUID-WorkgroupSize-TileShadingRateQCOM-10635` # Unit tests `VUID-08723` `VUID-10633` `VUID-10634` `VUID-10627` `VUID-10628` `VUID-10630` `VUID-10631` # VUID check failed to reach, blocked by other VUID check `VUID-10689` `VUID-10632` `VUID-10626` `VUID-10629` `VUID-10635`
1 parent ffc5ea4 commit 7fa421b

11 files changed

Lines changed: 742 additions & 4 deletions

source/val/validate_builtins.cpp

Lines changed: 149 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) 2018 Google LLC.
22
// Modifications Copyright (C) 2020 Advanced Micro Devices, Inc. All rights
33
// reserved.
4+
// Copyright (C) 2026 Qualcomm Technologies, Inc.
45
//
56
// Licensed under the Apache License, Version 2.0 (the "License");
67
// you may not use this file except in compliance with the License.
@@ -123,7 +124,7 @@ typedef enum VUIDError_ {
123124
VUIDErrorMax,
124125
} VUIDError;
125126

126-
const static uint32_t NumVUIDBuiltins = 42;
127+
const static uint32_t NumVUIDBuiltins = 45;
127128

128129
typedef struct {
129130
spv::BuiltIn builtIn;
@@ -176,7 +177,9 @@ std::array<BuiltinVUIDMapping, NumVUIDBuiltins> builtinVUIDInfo = {{
176177
{spv::BuiltIn::PrimitiveTriangleIndicesEXT, {7053, 7055, 7056}},
177178
{spv::BuiltIn::CullPrimitiveEXT, {7034, 7035, 7036}},
178179
{spv::BuiltIn::HitTriangleVertexPositionsKHR, {8747, 8748, 8749}},
179-
180+
{spv::BuiltIn::TileOffsetQCOM, {10626, 10627, 10628}},
181+
{spv::BuiltIn::TileDimensionQCOM, {10629, 10630, 10631}},
182+
{spv::BuiltIn::TileApronSizeQCOM, {10632, 10633, 10634}},
180183
// clang-format on
181184
}};
182185

@@ -380,6 +383,9 @@ class BuiltInsValidator {
380383
spv_result_t ValidateMeshShadingEXTBuiltinsAtDefinition(
381384
const Decoration& decoration, const Instruction& inst);
382385

386+
spv_result_t ValidateTileQCOMBuiltinAtDefinition(const Decoration& decoration,
387+
const Instruction& inst);
388+
383389
// Used as a common method for validating MeshEXT builtins
384390
spv_result_t ValidateMeshBuiltinInterfaceRules(
385391
const Decoration& decoration, const Instruction& inst,
@@ -587,6 +593,11 @@ class BuiltInsValidator {
587593
const Instruction& referenced_inst,
588594
const Instruction& referenced_from_inst);
589595

596+
spv_result_t ValidateTileQCOMBuiltinAtReference(
597+
const Decoration& decoration, const Instruction& built_in_inst,
598+
const Instruction& referenced_inst,
599+
const Instruction& referenced_from_inst);
600+
590601
// Validates that |built_in_inst| is not (even indirectly) referenced from
591602
// within a function which can be called with |execution_model|.
592603
//
@@ -623,6 +634,10 @@ class BuiltInsValidator {
623634
const Decoration& decoration, const Instruction& inst,
624635
uint32_t num_components,
625636
const std::function<spv_result_t(const std::string& message)>& diag);
637+
spv_result_t ValidateU32Vec(
638+
const Decoration& decoration, const Instruction& inst,
639+
uint32_t num_components,
640+
const std::function<spv_result_t(const std::string& message)>& diag);
626641
spv_result_t ValidateI32Arr(
627642
const Decoration& decoration, const Instruction& inst,
628643
const std::function<spv_result_t(const std::string& message)>& diag);
@@ -789,6 +804,9 @@ class BuiltInsValidator {
789804
// Execution models with which the current function can be called.
790805
std::set<spv::ExecutionModel> execution_models_;
791806

807+
// Execution modes with which the current function can be called.
808+
std::set<spv::ExecutionMode> execution_modes_;
809+
792810
// For Builtin that can only be declared once in an entry point, keep track if
793811
// the entry point has it already
794812
std::set<uint32_t> cull_primitive_entry_points_;
@@ -801,13 +819,17 @@ void BuiltInsValidator::Update(const Instruction& inst) {
801819
assert(function_id_ == 0);
802820
function_id_ = inst.id();
803821
execution_models_.clear();
822+
execution_modes_.clear();
804823
entry_points_ = &_.FunctionEntryPoints(function_id_);
805824
// Collect execution models from all entry points from which the current
806825
// function can be called.
807826
for (const uint32_t entry_point : *entry_points_) {
808827
if (const auto* models = _.GetExecutionModels(entry_point)) {
809828
execution_models_.insert(models->begin(), models->end());
810829
}
830+
if (const auto* modes = _.GetExecutionModes(entry_point)) {
831+
execution_modes_.insert(modes->begin(), modes->end());
832+
}
811833
}
812834
}
813835

@@ -817,6 +839,7 @@ void BuiltInsValidator::Update(const Instruction& inst) {
817839
function_id_ = 0;
818840
entry_points_ = &no_entry_points;
819841
execution_models_.clear();
842+
execution_modes_.clear();
820843
}
821844
}
822845

@@ -1103,6 +1126,40 @@ spv_result_t BuiltInsValidator::ValidateI32Vec(
11031126
return SPV_SUCCESS;
11041127
}
11051128

1129+
spv_result_t BuiltInsValidator::ValidateU32Vec(
1130+
const Decoration& decoration, const Instruction& inst,
1131+
uint32_t num_components,
1132+
const std::function<spv_result_t(const std::string& message)>& diag) {
1133+
uint32_t underlying_type = 0;
1134+
if (spv_result_t error =
1135+
GetUnderlyingType(_, decoration, inst, &underlying_type)) {
1136+
return error;
1137+
}
1138+
1139+
if (!_.IsUnsignedIntVectorType(underlying_type)) {
1140+
return diag(GetDefinitionDesc(decoration, inst) +
1141+
" is not an unsigned int vector.");
1142+
}
1143+
1144+
const uint32_t actual = _.GetDimension(underlying_type);
1145+
if (actual != num_components) {
1146+
std::ostringstream ss;
1147+
ss << GetDefinitionDesc(decoration, inst) << " has " << actual
1148+
<< " components.";
1149+
return diag(ss.str());
1150+
}
1151+
1152+
const uint32_t bit_width = _.GetBitWidth(underlying_type);
1153+
if (bit_width != 32) {
1154+
std::ostringstream ss;
1155+
ss << GetDefinitionDesc(decoration, inst)
1156+
<< " has components with bit width " << bit_width << ".";
1157+
return diag(ss.str());
1158+
}
1159+
1160+
return SPV_SUCCESS;
1161+
}
1162+
11061163
spv_result_t BuiltInsValidator::ValidateArrayedI32Vec(
11071164
const Decoration& decoration, const Instruction& inst,
11081165
uint32_t num_components,
@@ -3792,6 +3849,8 @@ spv_result_t BuiltInsValidator::ValidateWorkgroupSizeAtReference(
37923849
const Instruction& referenced_inst,
37933850
const Instruction& referenced_from_inst) {
37943851
if (spvIsVulkanEnv(_.context()->target_env)) {
3852+
const spv::StorageClass storage_class =
3853+
GetStorageClass(referenced_from_inst);
37953854
for (const spv::ExecutionModel execution_model : execution_models_) {
37963855
if (execution_model != spv::ExecutionModel::GLCompute &&
37973856
execution_model != spv::ExecutionModel::TaskNV &&
@@ -3810,6 +3869,19 @@ spv_result_t BuiltInsValidator::ValidateWorkgroupSizeAtReference(
38103869
referenced_from_inst, execution_model);
38113870
}
38123871
}
3872+
if (execution_modes_.count(spv::ExecutionMode::TileShadingRateQCOM) &&
3873+
storage_class != spv::StorageClass::Max &&
3874+
storage_class != spv::StorageClass::Input) {
3875+
return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
3876+
<< _.VkErrorID(10635)
3877+
<< spvLogStringForEnv(_.context()->target_env)
3878+
<< " spec allows BuiltIn WorkgroupSize to be only used for "
3879+
"variables with Input storage class when "
3880+
"TileShadingRateQCOM Execution Mode is used. "
3881+
<< GetReferenceDesc(decoration, built_in_inst, referenced_inst,
3882+
referenced_from_inst)
3883+
<< " " << GetStorageClassDesc(referenced_from_inst);
3884+
}
38133885
}
38143886

38153887
if (function_id_ == 0) {
@@ -4905,6 +4977,76 @@ spv_result_t BuiltInsValidator::ValidateMeshShadingEXTBuiltinsAtReference(
49054977
return SPV_SUCCESS;
49064978
}
49074979

4980+
spv_result_t BuiltInsValidator::ValidateTileQCOMBuiltinAtDefinition(
4981+
const Decoration& decoration, const Instruction& inst) {
4982+
const spv::BuiltIn builtin = decoration.builtin();
4983+
const uint32_t num_components =
4984+
(builtin == spv::BuiltIn::TileDimensionQCOM) ? 3 : 2;
4985+
if (spv_result_t error = ValidateU32Vec(
4986+
decoration, inst, num_components,
4987+
[this, &inst, builtin,
4988+
num_components](const std::string& msg) -> spv_result_t {
4989+
uint32_t vuid = GetVUIDForBuiltin(builtin, VUIDErrorType);
4990+
return _.diag(SPV_ERROR_INVALID_DATA, &inst)
4991+
<< _.VkErrorID(vuid)
4992+
<< "According to the Vulkan spec BuiltIn "
4993+
<< _.grammar().lookupOperandName(
4994+
SPV_OPERAND_TYPE_BUILT_IN,
4995+
static_cast<uint32_t>(builtin))
4996+
<< " variable must be a " << num_components
4997+
<< "-component 32-bit unsigned int vector. " << msg;
4998+
})) {
4999+
return error;
5000+
}
5001+
5002+
return ValidateTileQCOMBuiltinAtReference(decoration, inst, inst, inst);
5003+
}
5004+
5005+
spv_result_t BuiltInsValidator::ValidateTileQCOMBuiltinAtReference(
5006+
const Decoration& decoration, const Instruction& built_in_inst,
5007+
const Instruction& referenced_inst,
5008+
const Instruction& referenced_from_inst) {
5009+
if (spvIsVulkanEnv(_.context()->target_env)) {
5010+
const spv::BuiltIn builtin = decoration.builtin();
5011+
const spv::StorageClass sc = GetStorageClass(referenced_from_inst);
5012+
if (sc != spv::StorageClass::Max && sc != spv::StorageClass::Input) {
5013+
uint32_t vuid = GetVUIDForBuiltin(builtin, VUIDErrorStorageClass);
5014+
return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
5015+
<< _.VkErrorID(vuid) << "Vulkan spec allows BuiltIn "
5016+
<< _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
5017+
static_cast<uint32_t>(builtin))
5018+
<< " to be only used for variables with Input storage class. "
5019+
<< GetReferenceDesc(decoration, built_in_inst, referenced_inst,
5020+
referenced_from_inst)
5021+
<< " " << GetStorageClassDesc(referenced_from_inst);
5022+
}
5023+
5024+
for (const spv::ExecutionModel model : execution_models_) {
5025+
if (model != spv::ExecutionModel::Fragment &&
5026+
model != spv::ExecutionModel::GLCompute) {
5027+
uint32_t vuid = GetVUIDForBuiltin(builtin, VUIDErrorExecutionModel);
5028+
return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
5029+
<< _.VkErrorID(vuid) << "Vulkan spec allows BuiltIn "
5030+
<< _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
5031+
static_cast<uint32_t>(builtin))
5032+
<< " to be used only with Fragment or GLCompute execution "
5033+
"model. "
5034+
<< GetReferenceDesc(decoration, built_in_inst, referenced_inst,
5035+
referenced_from_inst, model);
5036+
}
5037+
}
5038+
}
5039+
5040+
if (function_id_ == 0) {
5041+
id_to_at_reference_checks_[referenced_from_inst.id()].push_back(
5042+
std::bind(&BuiltInsValidator::ValidateTileQCOMBuiltinAtReference, this,
5043+
decoration, built_in_inst, referenced_from_inst,
5044+
std::placeholders::_1));
5045+
}
5046+
5047+
return SPV_SUCCESS;
5048+
}
5049+
49085050
spv_result_t BuiltInsValidator::ValidateSingleBuiltInAtDefinition(
49095051
const Decoration& decoration, const Instruction& inst) {
49105052
const spv::BuiltIn label = decoration.builtin();
@@ -5095,6 +5237,11 @@ spv_result_t BuiltInsValidator::ValidateSingleBuiltInAtDefinitionVulkan(
50955237
case spv::BuiltIn::ResourceHeapEXT: {
50965238
return ValidateDescriptorHeapAtDefinition(decoration, inst);
50975239
}
5240+
case spv::BuiltIn::TileOffsetQCOM:
5241+
case spv::BuiltIn::TileDimensionQCOM:
5242+
case spv::BuiltIn::TileApronSizeQCOM: {
5243+
return ValidateTileQCOMBuiltinAtDefinition(decoration, inst);
5244+
}
50985245
default:
50995246
// No validation rules (for the moment).
51005247
break;

source/val/validate_decorations.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1251,6 +1251,14 @@ spv_result_t CheckDecorationsOfVariables(ValidationState_t& vstate) {
12511251
"decorations specified";
12521252
}
12531253
}
1254+
if (storageClass == spv::StorageClass::TileImageEXT) {
1255+
if (!hasDecoration(var_id, spv::Decoration::Location, vstate)) {
1256+
return vstate.diag(SPV_ERROR_INVALID_DATA, vstate.FindDef(var_id))
1257+
<< vstate.VkErrorID(8723)
1258+
<< "Variable with TileImageEXT Storage Class must be "
1259+
"decorated with Location.";
1260+
}
1261+
}
12541262
}
12551263
}
12561264
return SPV_SUCCESS;

source/val/validate_interfaces.cpp

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// Copyright (c) 2018 Google LLC.
2+
// Copyright (C) 2026 Qualcomm Technologies, Inc.
23
//
34
// Licensed under the Apache License, Version 2.0 (the "License");
45
// you may not use this file except in compliance with the License.
@@ -572,6 +573,8 @@ spv_result_t ValidateLocations(ValidationState_t& _,
572573
output_locations_per_stream;
573574
std::unordered_map<uint32_t, std::unordered_set<uint32_t>>
574575
output_index1_locations_per_stream;
576+
// For SPIR-V >= 1.4, TileImageEXT variables are always visible.
577+
std::unordered_map<uint32_t, uint32_t> tile_image_locations;
575578
std::unordered_set<uint32_t> seen;
576579
for (uint32_t i = 3; i < entry_point->operands().size(); ++i) {
577580
auto interface_id = entry_point->GetOperandAs<uint32_t>(i);
@@ -580,7 +583,8 @@ spv_result_t ValidateLocations(ValidationState_t& _,
580583
auto storage_class =
581584
interface_var->GetOperandAs<spv::StorageClass>(sc_index);
582585
if (storage_class != spv::StorageClass::Input &&
583-
storage_class != spv::StorageClass::Output) {
586+
storage_class != spv::StorageClass::Output &&
587+
storage_class != spv::StorageClass::TileImageEXT) {
584588
continue;
585589
}
586590
if (!seen.insert(interface_id).second) {
@@ -589,6 +593,23 @@ spv_result_t ValidateLocations(ValidationState_t& _,
589593
continue;
590594
}
591595

596+
if (storage_class == spv::StorageClass::TileImageEXT) {
597+
for (auto& dec : _.id_decorations(interface_var->id())) {
598+
if (dec.dec_type() == spv::Decoration::Location) {
599+
const auto result = tile_image_locations.emplace(dec.params()[0],
600+
interface_var->id());
601+
if (!result.second) {
602+
return _.diag(SPV_ERROR_INVALID_DATA, interface_var)
603+
<< _.VkErrorID(8723)
604+
<< "Variables with TileImageEXT Storage Class must not have "
605+
"conflicting Locations.";
606+
}
607+
break;
608+
}
609+
}
610+
continue;
611+
}
612+
592613
// The two Tessellation stages have a "Patch" variable that interface with
593614
// the Location mechanism, but are not suppose to be tied to the "normal"
594615
// input/output Location.

source/val/validate_memory.cpp

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) 2018 Google LLC.
22
// Modifications Copyright (C) 2020-2024 Advanced Micro Devices, Inc. All
33
// rights reserved.
4+
// Copyright (C) 2026 Qualcomm Technologies, Inc.
45
//
56
// Licensed under the Apache License, Version 2.0 (the "License");
67
// you may not use this file except in compliance with the License.
@@ -613,6 +614,15 @@ spv_result_t ValidateVariableStorageClass(ValidationState_t& _,
613614
return _.diag(SPV_ERROR_INVALID_ID, inst)
614615
<< "PhysicalStorageBuffer must not be used with OpVariable.";
615616
}
617+
618+
if (storage_class == spv::StorageClass::TileAttachmentQCOM &&
619+
!_.HasCapability(spv::Capability::TileShadingQCOM)) {
620+
return _.diag(SPV_ERROR_INVALID_CAPABILITY, inst)
621+
<< _.VkErrorID(10689)
622+
<< "the TileAttachmentQCOM storage class variable requires "
623+
"TileShadingQCOM capability enabled.";
624+
}
625+
616626
return SPV_SUCCESS;
617627
}
618628

@@ -1076,14 +1086,16 @@ spv_result_t ValidateVariableTileShadingQCOM(ValidationState_t& _,
10761086
spv::Dim dim = static_cast<spv::Dim>(pointee_type->word(3));
10771087
if (dim != spv::Dim::Dim2D) {
10781088
return _.diag(SPV_ERROR_INVALID_DATA, inst)
1089+
<< _.VkErrorID(10693)
10791090
<< "Any OpTypeImage variable in the TileAttachmentQCOM "
10801091
"Storage Class must "
10811092
"have 2D as its dimension";
10821093
}
10831094
unsigned sampled = pointee_type->word(7);
10841095
if (sampled != 1 && sampled != 2) {
10851096
return _.diag(SPV_ERROR_INVALID_DATA, inst)
1086-
<< "Any OpyTpeImage variable in the TileAttachmentQCOM "
1097+
<< _.VkErrorID(10694)
1098+
<< "Any OpTypeImage variable in the TileAttachmentQCOM "
10871099
"Storage Class must "
10881100
"have 1 or 2 as Image 'Sampled' parameter";
10891101
}
@@ -1101,6 +1113,7 @@ spv_result_t ValidateVariableTileShadingQCOM(ValidationState_t& _,
11011113
case spv::Op::OpImageQueryLevels:
11021114
case spv::Op::OpImageQuerySamples:
11031115
return _.diag(SPV_ERROR_INVALID_DATA, inst)
1116+
<< _.VkErrorID(10697)
11041117
<< "Any variable in the TileAttachmentQCOM Storage "
11051118
"Class must "
11061119
"not be consumed by an OpImageQuery* instruction";
@@ -1116,11 +1129,13 @@ spv_result_t ValidateVariableTileShadingQCOM(ValidationState_t& _,
11161129
if (!(_.HasDecoration(inst->id(), spv::Decoration::DescriptorSet) &&
11171130
_.HasDecoration(inst->id(), spv::Decoration::Binding))) {
11181131
return _.diag(SPV_ERROR_INVALID_ID, inst)
1132+
<< _.VkErrorID(10695)
11191133
<< "Any variable in the TileAttachmentQCOM Storage Class must "
11201134
"be decorated with DescriptorSet and Binding";
11211135
}
11221136
if (_.HasDecoration(inst->id(), spv::Decoration::Component)) {
11231137
return _.diag(SPV_ERROR_INVALID_ID, inst)
1138+
<< _.VkErrorID(10696)
11241139
<< "Any variable in the TileAttachmentQCOM Storage Class must "
11251140
"not be decorated with Component decoration";
11261141
}

0 commit comments

Comments
 (0)