Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions source/val/validate.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,14 @@ spv_result_t ValidateAdjacency(ValidationState_t& _);
/// @return SPV_SUCCESS if no errors are found.
spv_result_t ValidateInterfaces(ValidationState_t& _);

// Vulkan interface matching ignores one array level for some execution
// models (per-vertex inputs and outputs). See the Vulkan spec section on
// interface matching.
bool ExecutionModelStripsInterfaceArray(spv::ExecutionModel model,
bool is_output, bool has_patch,
bool has_per_task_nv,
bool has_per_vertex_khr);

/// @brief Validates entry point call tree requirements of
/// SPV_KHR_float_controls2
///
Expand Down
54 changes: 24 additions & 30 deletions source/val/validate_interfaces.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,27 @@

namespace spvtools {
namespace val {

bool ExecutionModelStripsInterfaceArray(spv::ExecutionModel model,
bool is_output, bool has_patch,
bool has_per_task_nv,
bool has_per_vertex_khr) {
switch (model) {
case spv::ExecutionModel::TessellationControl:
return !has_patch;
case spv::ExecutionModel::TessellationEvaluation:
return !is_output && !has_patch;
case spv::ExecutionModel::Geometry:
return !is_output;
case spv::ExecutionModel::Fragment:
return !is_output && has_per_vertex_khr;
case spv::ExecutionModel::MeshNV:
return is_output && !has_per_task_nv;
default:
return false;
}
}

namespace {

// Limit the number of checked locations to 4096. Multiplied by 4 to represent
Expand Down Expand Up @@ -338,36 +359,9 @@ spv_result_t GetLocationsForVariable(
// Vulkan 14.1.3: Tessellation control and mesh per-vertex outputs and
// tessellation control, evaluation and geometry per-vertex inputs have a
// layer of arraying that is not included in interface matching.
bool is_arrayed = false;
switch (entry_point->GetOperandAs<spv::ExecutionModel>(0)) {
case spv::ExecutionModel::TessellationControl:
if (!has_patch) {
is_arrayed = true;
}
break;
case spv::ExecutionModel::TessellationEvaluation:
if (!is_output && !has_patch) {
is_arrayed = true;
}
break;
case spv::ExecutionModel::Geometry:
if (!is_output) {
is_arrayed = true;
}
break;
case spv::ExecutionModel::Fragment:
if (!is_output && has_per_vertex_khr) {
is_arrayed = true;
}
break;
case spv::ExecutionModel::MeshNV:
if (is_output && !has_per_task_nv) {
is_arrayed = true;
}
break;
default:
break;
}
const bool is_arrayed = ExecutionModelStripsInterfaceArray(
entry_point->GetOperandAs<spv::ExecutionModel>(0), is_output, has_patch,
has_per_task_nv, has_per_vertex_khr);

// Unpack arrayness.
if (is_arrayed && (type->opcode() == spv::Op::OpTypeArray ||
Expand Down
99 changes: 98 additions & 1 deletion source/val/validate_memory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -806,6 +806,103 @@ spv_result_t ValidateVariableCoopMat(ValidationState_t& _,
return SPV_SUCCESS;
}

bool IsSpecConstantSizedArray(ValidationState_t& _,
const Instruction* type_inst) {
if (!type_inst || type_inst->opcode() != spv::Op::OpTypeArray) return false;
const Instruction* length = _.FindDef(type_inst->GetOperandAs<uint32_t>(2u));
return length && spvOpcodeIsSpecConstant(length->opcode());
}

// True when |type_id| is the extra array level Vulkan interface matching
// strips for |inst|. The variable must be listed on an OpEntryPoint whose
// execution model strips that array.
bool IsStrippableInterfaceArray(ValidationState_t& _, const Instruction* inst,
spv::StorageClass storage_class,
uint32_t type_id) {
if (storage_class != spv::StorageClass::Input &&
storage_class != spv::StorageClass::Output) {
return false;
}
const Instruction* type = _.FindDef(type_id);
if (!type || (type->opcode() != spv::Op::OpTypeArray &&
type->opcode() != spv::Op::OpTypeRuntimeArray)) {
return false;
}

const bool is_output = storage_class == spv::StorageClass::Output;
const bool has_patch = _.HasDecoration(inst->id(), spv::Decoration::Patch);
const bool has_per_task_nv =
_.HasDecoration(inst->id(), spv::Decoration::PerTaskNV);
const bool has_per_vertex_khr =
_.HasDecoration(inst->id(), spv::Decoration::PerVertexKHR);

for (const auto ep_func_id : _.entry_points()) {
bool listed = false;
for (const auto& desc : _.entry_point_descriptions(ep_func_id)) {
for (const auto iface_id : desc.interfaces) {
if (iface_id == inst->id()) {
listed = true;
break;
}
}
if (listed) break;
}
if (!listed) continue;

const auto* models = _.GetExecutionModels(ep_func_id);
if (!models) continue;
for (const auto model : *models) {
if (ExecutionModelStripsInterfaceArray(model, is_output, has_patch,
has_per_task_nv,
has_per_vertex_khr)) {
return true;
}
}
}
return false;
}

// VUID-StandaloneSpirv-Function-04681
spv_result_t ValidateVulkanSpecConstantSizedArray(
ValidationState_t& _, const Instruction* inst,
spv::StorageClass storage_class, uint32_t value_id) {
if (storage_class == spv::StorageClass::Function ||
storage_class == spv::StorageClass::Private) {
return SPV_SUCCESS;
}
if (storage_class == spv::StorageClass::Workgroup &&
!_.HasDecoration(value_id, spv::Decoration::Block)) {
return SPV_SUCCESS;
}

uint32_t check_id = value_id;
if (IsStrippableInterfaceArray(_, inst, storage_class, check_id)) {
const Instruction* type = _.FindDef(check_id);
if (type) check_id = type->GetOperandAs<uint32_t>(1u);
}

const Instruction* check_type = _.FindDef(check_id);
if (check_type && check_type->opcode() == spv::Op::OpTypeArray) {
check_id = check_type->GetOperandAs<uint32_t>(1u);
}

if (_.ContainsType(check_id, [&_](const Instruction* type_inst) {
return IsSpecConstantSizedArray(_, type_inst);
})) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< _.VkErrorID(4681)
<< "A type that is an array sized with a specialization constant "
"must neither be, nor be contained in, the type of variable "
"<id> "
<< _.getIdName(inst->id())
<< ", unless it is equal to the variable type, declared in the "
"Function or Private Storage Class, a non-Block variable in "
"the Workgroup Storage Class, or an interface variable with an "
"additional level of arrayness.";
}
return SPV_SUCCESS;
}

// Vulkan specific validation rules for OpTypeRuntimeArray
spv_result_t ValidateVariableVulkanArray(ValidationState_t& _,
const Instruction* inst,
Expand Down Expand Up @@ -874,7 +971,7 @@ spv_result_t ValidateVariableVulkanArray(ValidationState_t& _,
}
}

return SPV_SUCCESS;
return ValidateVulkanSpecConstantSizedArray(_, inst, storage_class, value_id);
}

// Vulkan-specific validation for long vectors
Expand Down
2 changes: 2 additions & 0 deletions source/val/validation_state.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3326,6 +3326,8 @@ std::string ValidationState_t::VkErrorID(uint32_t id,
return VUID_WRAP(VUID-StandaloneSpirv-VulkanMemoryModel-04679);
case 4680:
return VUID_WRAP(VUID-StandaloneSpirv-OpTypeRuntimeArray-04680);
case 4681:
return VUID_WRAP(VUID-StandaloneSpirv-Function-04681);
case 4682:
return VUID_WRAP(VUID-StandaloneSpirv-OpControlBarrier-04682);
case 4685:
Expand Down
Loading
Loading