Support SPV_KHR_untyped_pointers (#5736)

* Support SPV_KHR_untyped_pointers

Covers:
- assembler
- disassembler
- validator

fix copyright

Validate OpTypeUntypedPointerKHR

* Disallow an untyped pointer in a typed pointer
* Validate capability requirements for untyped pointer
* Allow duplicate untyped pointer declarations

Add round trip tests

Validate OpUntypedVariableKHR

Validate untyped access chains

* Add a test for opcodes that generate untyped pointers
* simplify some checks for operands needing types
* validate OpUnypedAccessChainKHR, OpUntypedInBoundsAccessChainKHR,
  OpUntypedPtrAccessChainKHR, OpUntypedInBoundsPtrAccessChainKHR

Unify variable validation

Validate OpCopyMemorySized

* Fix some opcode tests to accound for untyped pointers
* Add validation for OpCopyMemorySized for shaders and untyped pointers
* fix up tests

Validate pointer comparisons and bitcast

* Update more helpers
* Fix entry validation to allow OpUntypedVariableKHR
* Validate OpPtrEqual, OpPtrNotEqual and OpPtrDiff
* Validate OpBitcast

Validate atomics and untyped pointers

Make interface variable validation aware of untyped pointers

* Check OpUntypedVariableKHR in interface validation

More untyped pointer validation

* Validate interfaces more thoroughly
* Validate layouts for untyped pointer uses
* Improve capability checks for vulkan with OpTypeUntypedPointerKHR
* workgroup member explicit layout validation updates

More validation

* validate function arguments and parameters
* handle untyped pointer and variable in more places

Add a friendly assembly name for untyped pointers

Update OpCopyMemory validation and tests

Fix test for token update

Fixes for validation

* Allow typed pointers to contain untyped pointers
* Fix decoration validation
* add untyped pointer as a case for size and alignments

Fix interface validation

* Grabbed the wrong storage class operand for untyped variables
* Add ability to specify assembler options in validation tests

Add passthrough validation for OpUntypedArrayLengthKHR

More validation of untyped pointers

* Validate OpUntypedArrayLengthKHR
* Validate layout for OpLoad, OpStore, and OpUntypedArrayLengthKHR

Validation support for cooperative matrix and untyped pointers

* Allow untyped pointers for cooperative matrix KHR load and store

Updates to match spec

* Remove extra capability references
* Swap untyped variable data type and storage class operands
* update validation of variables

* update deps

---------

Co-authored-by: David Neto <dneto@google.com>
This commit is contained in:
alan-baker 2024-07-17 14:51:37 -04:00 committed by GitHub
parent 6248fda376
commit 3ab0d22608
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
34 changed files with 3657 additions and 329 deletions

2
DEPS
View File

@ -14,7 +14,7 @@ vars = {
're2_revision': '6dcd83d60f7944926bfd308cc13979fc53dd69ca',
'spirv_headers_revision': '41a8eb27f1a7554dadfcdd45819954eaa94935e6',
'spirv_headers_revision': 'db5a00f8cebe81146cafabf89019674a3c4bf03d',
}
deps = {

View File

@ -256,6 +256,11 @@ spv_result_t FriendlyNameMapper::ParseInstruction(
inst.words[2]) +
"_" + NameForId(inst.words[3]));
break;
case spv::Op::OpTypeUntypedPointerKHR:
SaveName(result_id, std::string("_ptr_") +
NameForEnumOperand(SPV_OPERAND_TYPE_STORAGE_CLASS,
inst.words[2]));
break;
case spv::Op::OpTypePipe:
SaveName(result_id,
std::string("Pipe") +

View File

@ -287,8 +287,11 @@ int32_t spvOpcodeIsComposite(const spv::Op opcode) {
bool spvOpcodeReturnsLogicalVariablePointer(const spv::Op opcode) {
switch (opcode) {
case spv::Op::OpVariable:
case spv::Op::OpUntypedVariableKHR:
case spv::Op::OpAccessChain:
case spv::Op::OpInBoundsAccessChain:
case spv::Op::OpUntypedAccessChainKHR:
case spv::Op::OpUntypedInBoundsAccessChainKHR:
case spv::Op::OpFunctionParameter:
case spv::Op::OpImageTexelPointer:
case spv::Op::OpCopyObject:
@ -296,6 +299,7 @@ bool spvOpcodeReturnsLogicalVariablePointer(const spv::Op opcode) {
case spv::Op::OpPhi:
case spv::Op::OpFunctionCall:
case spv::Op::OpPtrAccessChain:
case spv::Op::OpUntypedPtrAccessChainKHR:
case spv::Op::OpLoad:
case spv::Op::OpConstantNull:
case spv::Op::OpRawAccessChainNV:
@ -308,8 +312,11 @@ bool spvOpcodeReturnsLogicalVariablePointer(const spv::Op opcode) {
int32_t spvOpcodeReturnsLogicalPointer(const spv::Op opcode) {
switch (opcode) {
case spv::Op::OpVariable:
case spv::Op::OpUntypedVariableKHR:
case spv::Op::OpAccessChain:
case spv::Op::OpInBoundsAccessChain:
case spv::Op::OpUntypedAccessChainKHR:
case spv::Op::OpUntypedInBoundsAccessChainKHR:
case spv::Op::OpFunctionParameter:
case spv::Op::OpImageTexelPointer:
case spv::Op::OpCopyObject:
@ -351,6 +358,7 @@ int32_t spvOpcodeGeneratesType(spv::Op op) {
// spv::Op::OpTypeAccelerationStructureNV
case spv::Op::OpTypeRayQueryKHR:
case spv::Op::OpTypeHitObjectNV:
case spv::Op::OpTypeUntypedPointerKHR:
return true;
default:
// In particular, OpTypeForwardPointer does not generate a type,
@ -792,3 +800,16 @@ bool spvOpcodeIsBit(spv::Op opcode) {
return false;
}
}
bool spvOpcodeGeneratesUntypedPointer(spv::Op opcode) {
switch (opcode) {
case spv::Op::OpUntypedVariableKHR:
case spv::Op::OpUntypedAccessChainKHR:
case spv::Op::OpUntypedInBoundsAccessChainKHR:
case spv::Op::OpUntypedPtrAccessChainKHR:
case spv::Op::OpUntypedInBoundsPtrAccessChainKHR:
return true;
default:
return false;
}
}

View File

@ -162,4 +162,7 @@ bool spvOpcodeIsBit(spv::Op opcode);
// Gets the name of an instruction, without the "Op" prefix.
const char* spvOpcodeString(const spv::Op opcode);
// Returns true for opcodes that generate an untyped pointer result.
bool spvOpcodeGeneratesUntypedPointer(spv::Op opcode);
#endif // SOURCE_OPCODE_H_

View File

@ -117,6 +117,15 @@ spv_result_t ValidateAdjacency(ValidationState_t& _) {
"first instructions in the first block.";
}
break;
case spv::Op::OpUntypedVariableKHR:
if (inst.GetOperandAs<spv::StorageClass>(2) ==
spv::StorageClass::Function &&
adjacency_status != IN_ENTRY_BLOCK) {
return _.diag(SPV_ERROR_INVALID_DATA, &inst)
<< "All OpUntypedVariableKHR instructions in a function must "
"be the first instructions in the first block.";
}
break;
default:
adjacency_status = PHI_AND_VAR_INVALID;
break;

View File

@ -129,6 +129,7 @@ spv_result_t ValidateDecorationTarget(ValidationState_t& _, spv::Decoration dec,
break;
case spv::Decoration::BuiltIn:
if (target->opcode() != spv::Op::OpVariable &&
target->opcode() != spv::Op::OpUntypedVariableKHR &&
!spvOpcodeIsConstant(target->opcode())) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "BuiltIns can only target variables, structure members or "
@ -139,7 +140,8 @@ spv_result_t ValidateDecorationTarget(ValidationState_t& _, spv::Decoration dec,
if (!spvOpcodeIsConstant(target->opcode())) {
return fail(0) << "must be a constant for WorkgroupSize";
}
} else if (target->opcode() != spv::Op::OpVariable) {
} else if (target->opcode() != spv::Op::OpVariable &&
target->opcode() != spv::Op::OpUntypedVariableKHR) {
return fail(0) << "must be a variable";
}
break;
@ -161,11 +163,12 @@ spv_result_t ValidateDecorationTarget(ValidationState_t& _, spv::Decoration dec,
case spv::Decoration::RestrictPointer:
case spv::Decoration::AliasedPointer:
if (target->opcode() != spv::Op::OpVariable &&
target->opcode() != spv::Op::OpUntypedVariableKHR &&
target->opcode() != spv::Op::OpFunctionParameter &&
target->opcode() != spv::Op::OpRawAccessChainNV) {
return fail(0) << "must be a memory object declaration";
}
if (_.GetIdOpcode(target->type_id()) != spv::Op::OpTypePointer) {
if (!_.IsPointerType(target->type_id())) {
return fail(0) << "must be a pointer type";
}
break;
@ -176,7 +179,8 @@ spv_result_t ValidateDecorationTarget(ValidationState_t& _, spv::Decoration dec,
case spv::Decoration::Binding:
case spv::Decoration::DescriptorSet:
case spv::Decoration::InputAttachmentIndex:
if (target->opcode() != spv::Op::OpVariable) {
if (target->opcode() != spv::Op::OpVariable &&
target->opcode() != spv::Op::OpUntypedVariableKHR) {
return fail(0) << "must be a variable";
}
break;

View File

@ -183,7 +183,44 @@ spv_result_t AtomicsPass(ValidationState_t& _, const Instruction* inst) {
if (!_.GetPointerTypeInfo(pointer_type, &data_type, &storage_class)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< spvOpcodeString(opcode)
<< ": expected Pointer to be of type OpTypePointer";
<< ": expected Pointer to be a pointer type";
}
// If the pointer is an untyped pointer, get the data type elsewhere.
if (data_type == 0) {
switch (opcode) {
case spv::Op::OpAtomicLoad:
case spv::Op::OpAtomicExchange:
case spv::Op::OpAtomicFAddEXT:
case spv::Op::OpAtomicCompareExchange:
case spv::Op::OpAtomicCompareExchangeWeak:
case spv::Op::OpAtomicIIncrement:
case spv::Op::OpAtomicIDecrement:
case spv::Op::OpAtomicIAdd:
case spv::Op::OpAtomicISub:
case spv::Op::OpAtomicSMin:
case spv::Op::OpAtomicUMin:
case spv::Op::OpAtomicFMinEXT:
case spv::Op::OpAtomicSMax:
case spv::Op::OpAtomicUMax:
case spv::Op::OpAtomicFMaxEXT:
case spv::Op::OpAtomicAnd:
case spv::Op::OpAtomicOr:
case spv::Op::OpAtomicXor:
data_type = inst->type_id();
break;
case spv::Op::OpAtomicFlagTestAndSet:
case spv::Op::OpAtomicFlagClear:
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Untyped pointers are not supported by atomic flag "
"instructions";
break;
case spv::Op::OpAtomicStore:
data_type = _.FindDef(inst->GetOperandAs<uint32_t>(3))->type_id();
break;
default:
break;
}
}
// Can't use result_type because OpAtomicStore doesn't have a result

View File

@ -97,12 +97,16 @@ spv_result_t GetUnderlyingType(ValidationState_t& _,
spv::StorageClass GetStorageClass(const Instruction& inst) {
switch (inst.opcode()) {
case spv::Op::OpTypePointer:
case spv::Op::OpTypeUntypedPointerKHR:
case spv::Op::OpTypeForwardPointer: {
return spv::StorageClass(inst.word(2));
}
case spv::Op::OpVariable: {
return spv::StorageClass(inst.word(3));
}
case spv::Op::OpUntypedVariableKHR: {
return spv::StorageClass(inst.word(4));
}
case spv::Op::OpGenericCastToPtrExplicit: {
return spv::StorageClass(inst.word(4));
}

View File

@ -250,7 +250,8 @@ spv_result_t ValidateReturnValue(ValidationState_t& _,
}
if (_.addressing_model() == spv::AddressingModel::Logical &&
spv::Op::OpTypePointer == value_type->opcode() &&
(spv::Op::OpTypePointer == value_type->opcode() ||
spv::Op::OpTypeUntypedPointerKHR == value_type->opcode()) &&
!_.features().variable_pointers && !_.options()->relax_logical_pointer) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "OpReturnValue value's type <id> "

View File

@ -324,6 +324,7 @@ bool IsTypeNullable(const std::vector<uint32_t>& instruction,
}
return true;
}
case spv::Op::OpTypeUntypedPointerKHR:
case spv::Op::OpTypePointer:
if (spv::StorageClass(instruction[2]) ==
spv::StorageClass::PhysicalStorageBuffer) {

View File

@ -224,6 +224,7 @@ uint32_t getBaseAlignment(uint32_t member_id, bool roundUp,
break;
}
case spv::Op::OpTypePointer:
case spv::Op::OpTypeUntypedPointerKHR:
baseAlignment = vstate.pointer_size_and_alignment();
break;
default:
@ -270,6 +271,7 @@ uint32_t getScalarAlignment(uint32_t type_id, ValidationState_t& vstate) {
return max_member_alignment;
} break;
case spv::Op::OpTypePointer:
case spv::Op::OpTypeUntypedPointerKHR:
return vstate.pointer_size_and_alignment();
default:
assert(0);
@ -359,6 +361,7 @@ uint32_t getSize(uint32_t member_id, const LayoutConstraints& inherited,
return offset + getSize(lastMember, constraint, constraints, vstate);
}
case spv::Op::OpTypePointer:
case spv::Op::OpTypeUntypedPointerKHR:
return vstate.pointer_size_and_alignment();
default:
assert(0);
@ -432,9 +435,9 @@ spv_result_t checkLayout(uint32_t struct_id, const char* storage_class_str,
return ds;
};
// If we are checking physical storage buffer pointers, we may not actually
// have a struct here. Instead, pretend we have a struct with a single member
// at offset 0.
// If we are checking the layout of untyped pointers or physical storage
// buffer pointers, we may not actually have a struct here. Instead, pretend
// we have a struct with a single member at offset 0.
const auto& struct_type = vstate.FindDef(struct_id);
std::vector<uint32_t> members;
if (struct_type->opcode() == spv::Op::OpTypeStruct) {
@ -451,8 +454,8 @@ spv_result_t checkLayout(uint32_t struct_id, const char* storage_class_str,
};
std::vector<MemberOffsetPair> member_offsets;
// With physical storage buffers, we might be checking layouts that do not
// originate from a structure.
// With untyped pointers or physical storage buffers, we might be checking
// layouts that do not originate from a structure.
if (struct_type->opcode() == spv::Op::OpTypeStruct) {
member_offsets.reserve(members.size());
for (uint32_t memberIdx = 0, numMembers = uint32_t(members.size());
@ -770,14 +773,19 @@ spv_result_t CheckDecorationsOfEntryPoints(ValidationState_t& vstate) {
std::unordered_set<spv::BuiltIn> output_var_builtin;
for (auto interface : desc.interfaces) {
Instruction* var_instr = vstate.FindDef(interface);
if (!var_instr || spv::Op::OpVariable != var_instr->opcode()) {
if (!var_instr ||
(spv::Op::OpVariable != var_instr->opcode() &&
spv::Op::OpUntypedVariableKHR != var_instr->opcode())) {
return vstate.diag(SPV_ERROR_INVALID_ID, var_instr)
<< "Interfaces passed to OpEntryPoint must be of type "
"OpTypeVariable. Found Op"
<< "Interfaces passed to OpEntryPoint must be variables. "
"Found Op"
<< spvOpcodeString(var_instr->opcode()) << ".";
}
const bool untyped_pointers =
var_instr->opcode() == spv::Op::OpUntypedVariableKHR;
const auto sc_index = 2u;
const spv::StorageClass storage_class =
var_instr->GetOperandAs<spv::StorageClass>(2);
var_instr->GetOperandAs<spv::StorageClass>(sc_index);
if (vstate.version() >= SPV_SPIRV_VERSION_WORD(1, 4)) {
// Starting in 1.4, OpEntryPoint must list all global variables
// it statically uses and those interfaces must be unique.
@ -804,12 +812,13 @@ spv_result_t CheckDecorationsOfEntryPoints(ValidationState_t& vstate) {
}
}
const uint32_t ptr_id = var_instr->word(1);
Instruction* ptr_instr = vstate.FindDef(ptr_id);
// It is guaranteed (by validator ID checks) that ptr_instr is
// OpTypePointer. Word 3 of this instruction is the type being pointed
// to.
const uint32_t type_id = ptr_instr->word(3);
// to. For untyped variables, the pointee type comes from the data type
// operand.
const uint32_t type_id =
untyped_pointers ? var_instr->word(4)
: vstate.FindDef(var_instr->word(1))->word(3);
Instruction* type_instr = vstate.FindDef(type_id);
const bool is_struct =
type_instr && spv::Op::OpTypeStruct == type_instr->opcode();
@ -874,12 +883,25 @@ spv_result_t CheckDecorationsOfEntryPoints(ValidationState_t& vstate) {
if (storage_class == spv::StorageClass::Workgroup) {
++num_workgroup_variables;
if (is_struct) {
if (hasDecoration(type_id, spv::Decoration::Block, vstate))
++num_workgroup_variables_with_block;
if (hasDecoration(var_instr->id(), spv::Decoration::Aliased,
vstate))
++num_workgroup_variables_with_aliased;
if (type_instr) {
if (spv::Op::OpTypeStruct == type_instr->opcode()) {
if (hasDecoration(type_id, spv::Decoration::Block, vstate)) {
++num_workgroup_variables_with_block;
} else if (untyped_pointers &&
vstate.HasCapability(spv::Capability::Shader)) {
return vstate.diag(SPV_ERROR_INVALID_ID, var_instr)
<< "Untyped workgroup variables in shaders must be "
"block decorated";
}
if (hasDecoration(var_instr->id(), spv::Decoration::Aliased,
vstate))
++num_workgroup_variables_with_aliased;
} else if (untyped_pointers &&
vstate.HasCapability(spv::Capability::Shader)) {
return vstate.diag(SPV_ERROR_INVALID_ID, var_instr)
<< "Untyped workgroup variables in shaders must be block "
"decorated structs";
}
}
}
@ -960,25 +982,33 @@ spv_result_t CheckDecorationsOfEntryPoints(ValidationState_t& vstate) {
const bool workgroup_blocks_allowed = vstate.HasCapability(
spv::Capability::WorkgroupMemoryExplicitLayoutKHR);
if (workgroup_blocks_allowed && num_workgroup_variables > 0 &&
if (workgroup_blocks_allowed &&
!vstate.HasCapability(spv::Capability::UntypedPointersKHR) &&
num_workgroup_variables > 0 &&
num_workgroup_variables_with_block > 0) {
if (num_workgroup_variables != num_workgroup_variables_with_block) {
return vstate.diag(SPV_ERROR_INVALID_BINARY, vstate.FindDef(entry_point))
return vstate.diag(SPV_ERROR_INVALID_BINARY,
vstate.FindDef(entry_point))
<< "When declaring WorkgroupMemoryExplicitLayoutKHR, "
"either all or none of the Workgroup Storage Class variables "
"either all or none of the Workgroup Storage Class "
"variables "
"in the entry point interface must point to struct types "
"decorated with Block. Entry point id "
"decorated with Block (unless the "
"UntypedPointersKHR capability is declared). "
"Entry point id "
<< entry_point << " does not meet this requirement.";
}
if (num_workgroup_variables_with_block > 1 &&
num_workgroup_variables_with_block !=
num_workgroup_variables_with_aliased) {
return vstate.diag(SPV_ERROR_INVALID_BINARY, vstate.FindDef(entry_point))
return vstate.diag(SPV_ERROR_INVALID_BINARY,
vstate.FindDef(entry_point))
<< "When declaring WorkgroupMemoryExplicitLayoutKHR, "
"if more than one Workgroup Storage Class variable in "
"the entry point interface point to a type decorated "
"with Block, all of them must be decorated with Aliased. "
"Entry point id "
"with Block, all of them must be decorated with Aliased "
"(unless the UntypedPointerWorkgroupKHR capability is "
"declared). Entry point id "
<< entry_point << " does not meet this requirement.";
}
} else if (!workgroup_blocks_allowed &&
@ -1084,11 +1114,17 @@ spv_result_t CheckDecorationsOfBuffers(ValidationState_t& vstate) {
const auto& words = inst.words();
auto type_id = inst.type_id();
const Instruction* type_inst = vstate.FindDef(type_id);
if (spv::Op::OpVariable == inst.opcode()) {
bool scalar_block_layout = false;
MemberConstraints constraints;
if (spv::Op::OpVariable == inst.opcode() ||
spv::Op::OpUntypedVariableKHR == inst.opcode()) {
const bool untyped_pointer =
inst.opcode() == spv::Op::OpUntypedVariableKHR;
const auto var_id = inst.id();
// For storage class / decoration combinations, see Vulkan 14.5.4 "Offset
// and Stride Assignment".
const auto storageClass = inst.GetOperandAs<spv::StorageClass>(2);
const auto storageClassVal = words[3];
const auto storageClass = spv::StorageClass(storageClassVal);
const bool uniform = storageClass == spv::StorageClass::Uniform;
const bool uniform_constant =
storageClass == spv::StorageClass::UniformConstant;
@ -1167,20 +1203,24 @@ spv_result_t CheckDecorationsOfBuffers(ValidationState_t& vstate) {
if (uniform || push_constant || storage_buffer || phys_storage_buffer ||
workgroup) {
const auto ptrInst = vstate.FindDef(words[1]);
assert(spv::Op::OpTypePointer == ptrInst->opcode());
auto id = ptrInst->words()[3];
auto id_inst = vstate.FindDef(id);
// Jump through one level of arraying.
if (!workgroup && (id_inst->opcode() == spv::Op::OpTypeArray ||
id_inst->opcode() == spv::Op::OpTypeRuntimeArray)) {
id = id_inst->GetOperandAs<uint32_t>(1u);
id_inst = vstate.FindDef(id);
assert(spv::Op::OpTypePointer == ptrInst->opcode() ||
spv::Op::OpTypeUntypedPointerKHR == ptrInst->opcode());
auto id = untyped_pointer ? (words.size() > 4 ? words[4] : 0)
: ptrInst->words()[3];
if (id != 0) {
auto id_inst = vstate.FindDef(id);
// Jump through one level of arraying.
if (!workgroup &&
(id_inst->opcode() == spv::Op::OpTypeArray ||
id_inst->opcode() == spv::Op::OpTypeRuntimeArray)) {
id = id_inst->GetOperandAs<uint32_t>(1u);
id_inst = vstate.FindDef(id);
}
// Struct requirement is checked on variables so just move on here.
if (spv::Op::OpTypeStruct != id_inst->opcode()) continue;
ComputeMemberConstraintsForStruct(&constraints, id,
LayoutConstraints(), vstate);
}
// Struct requirement is checked on variables so just move on here.
if (spv::Op::OpTypeStruct != id_inst->opcode()) continue;
MemberConstraints constraints;
ComputeMemberConstraintsForStruct(&constraints, id, LayoutConstraints(),
vstate);
// Prepare for messages
const char* sc_str =
uniform ? "Uniform"
@ -1250,88 +1290,91 @@ spv_result_t CheckDecorationsOfBuffers(ValidationState_t& vstate) {
}
}
for (const auto& dec : vstate.id_decorations(id)) {
const bool blockDeco = spv::Decoration::Block == dec.dec_type();
const bool bufferDeco =
spv::Decoration::BufferBlock == dec.dec_type();
const bool blockRules = uniform && blockDeco;
const bool bufferRules =
(uniform && bufferDeco) ||
((push_constant || storage_buffer ||
phys_storage_buffer || workgroup) && blockDeco);
if (uniform && blockDeco) {
vstate.RegisterPointerToUniformBlock(ptrInst->id());
vstate.RegisterStructForUniformBlock(id);
}
if ((uniform && bufferDeco) ||
((storage_buffer || phys_storage_buffer) && blockDeco)) {
vstate.RegisterPointerToStorageBuffer(ptrInst->id());
vstate.RegisterStructForStorageBuffer(id);
}
if (blockRules || bufferRules) {
const char* deco_str = blockDeco ? "Block" : "BufferBlock";
spv_result_t recursive_status = SPV_SUCCESS;
const bool scalar_block_layout = workgroup ?
vstate.options()->workgroup_scalar_block_layout :
vstate.options()->scalar_block_layout;
if (isMissingOffsetInStruct(id, vstate)) {
return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
<< "Structure id " << id << " decorated as " << deco_str
<< " must be explicitly laid out with Offset "
"decorations.";
if (id != 0) {
for (const auto& dec : vstate.id_decorations(id)) {
const bool blockDeco = spv::Decoration::Block == dec.dec_type();
const bool bufferDeco =
spv::Decoration::BufferBlock == dec.dec_type();
const bool blockRules = uniform && blockDeco;
const bool bufferRules = (uniform && bufferDeco) ||
((push_constant || storage_buffer ||
phys_storage_buffer || workgroup) &&
blockDeco);
if (uniform && blockDeco) {
vstate.RegisterPointerToUniformBlock(ptrInst->id());
vstate.RegisterStructForUniformBlock(id);
}
if ((uniform && bufferDeco) ||
((storage_buffer || phys_storage_buffer) && blockDeco)) {
vstate.RegisterPointerToStorageBuffer(ptrInst->id());
vstate.RegisterStructForStorageBuffer(id);
}
if (!checkForRequiredDecoration(
id,
[](spv::Decoration d) {
return d == spv::Decoration::ArrayStride;
},
spv::Op::OpTypeArray, vstate)) {
return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
<< "Structure id " << id << " decorated as " << deco_str
<< " must be explicitly laid out with ArrayStride "
"decorations.";
}
if (blockRules || bufferRules) {
const char* deco_str = blockDeco ? "Block" : "BufferBlock";
spv_result_t recursive_status = SPV_SUCCESS;
scalar_block_layout =
workgroup ? vstate.options()->workgroup_scalar_block_layout
: vstate.options()->scalar_block_layout;
if (!checkForRequiredDecoration(
id,
[](spv::Decoration d) {
return d == spv::Decoration::MatrixStride;
},
spv::Op::OpTypeMatrix, vstate)) {
return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
<< "Structure id " << id << " decorated as " << deco_str
<< " must be explicitly laid out with MatrixStride "
"decorations.";
}
if (isMissingOffsetInStruct(id, vstate)) {
return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
<< "Structure id " << id << " decorated as " << deco_str
<< " must be explicitly laid out with Offset "
"decorations.";
}
if (!checkForRequiredDecoration(
id,
[](spv::Decoration d) {
return d == spv::Decoration::RowMajor ||
d == spv::Decoration::ColMajor;
},
spv::Op::OpTypeMatrix, vstate)) {
return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
<< "Structure id " << id << " decorated as " << deco_str
<< " must be explicitly laid out with RowMajor or "
"ColMajor decorations.";
}
if (!checkForRequiredDecoration(
id,
[](spv::Decoration d) {
return d == spv::Decoration::ArrayStride;
},
spv::Op::OpTypeArray, vstate)) {
return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
<< "Structure id " << id << " decorated as " << deco_str
<< " must be explicitly laid out with ArrayStride "
"decorations.";
}
if (spvIsVulkanEnv(vstate.context()->target_env)) {
if (blockRules && (SPV_SUCCESS != (recursive_status = checkLayout(
id, sc_str, deco_str, true,
if (!checkForRequiredDecoration(
id,
[](spv::Decoration d) {
return d == spv::Decoration::MatrixStride;
},
spv::Op::OpTypeMatrix, vstate)) {
return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
<< "Structure id " << id << " decorated as " << deco_str
<< " must be explicitly laid out with MatrixStride "
"decorations.";
}
if (!checkForRequiredDecoration(
id,
[](spv::Decoration d) {
return d == spv::Decoration::RowMajor ||
d == spv::Decoration::ColMajor;
},
spv::Op::OpTypeMatrix, vstate)) {
return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
<< "Structure id " << id << " decorated as " << deco_str
<< " must be explicitly laid out with RowMajor or "
"ColMajor decorations.";
}
if (spvIsVulkanEnv(vstate.context()->target_env)) {
if (blockRules &&
(SPV_SUCCESS !=
(recursive_status = checkLayout(id, sc_str, deco_str, true,
scalar_block_layout, 0,
constraints, vstate)))) {
return recursive_status;
} else if (bufferRules &&
(SPV_SUCCESS !=
(recursive_status = checkLayout(
id, sc_str, deco_str, false, scalar_block_layout,
0, constraints, vstate)))) {
return recursive_status;
return recursive_status;
} else if (bufferRules &&
(SPV_SUCCESS != (recursive_status = checkLayout(
id, sc_str, deco_str, false,
scalar_block_layout, 0,
constraints, vstate)))) {
return recursive_status;
}
}
}
}
@ -1340,19 +1383,97 @@ spv_result_t CheckDecorationsOfBuffers(ValidationState_t& vstate) {
} else if (type_inst && type_inst->opcode() == spv::Op::OpTypePointer &&
type_inst->GetOperandAs<spv::StorageClass>(1u) ==
spv::StorageClass::PhysicalStorageBuffer) {
const bool scalar_block_layout = vstate.options()->scalar_block_layout;
MemberConstraints constraints;
const bool buffer = true;
const auto data_type_id = type_inst->GetOperandAs<uint32_t>(2u);
const auto* data_type_inst = vstate.FindDef(data_type_id);
const auto pointee_type_id = type_inst->GetOperandAs<uint32_t>(2u);
const auto* data_type_inst = vstate.FindDef(pointee_type_id);
scalar_block_layout = vstate.options()->scalar_block_layout;
if (data_type_inst->opcode() == spv::Op::OpTypeStruct) {
ComputeMemberConstraintsForStruct(&constraints, pointee_type_id,
LayoutConstraints(), vstate);
}
if (auto res = checkLayout(pointee_type_id, "PhysicalStorageBuffer",
"Block", !buffer, scalar_block_layout, 0,
constraints, vstate)) {
return res;
}
} else if (vstate.HasCapability(spv::Capability::UntypedPointersKHR) &&
spvIsVulkanEnv(vstate.context()->target_env)) {
// Untyped variables are checked above. Here we check that instructions
// using an untyped pointer have a valid layout.
uint32_t ptr_ty_id = 0;
uint32_t data_type_id = 0;
switch (inst.opcode()) {
case spv::Op::OpUntypedAccessChainKHR:
case spv::Op::OpUntypedInBoundsAccessChainKHR:
case spv::Op::OpUntypedPtrAccessChainKHR:
case spv::Op::OpUntypedInBoundsPtrAccessChainKHR:
ptr_ty_id = inst.type_id();
data_type_id = inst.GetOperandAs<uint32_t>(2);
break;
case spv::Op::OpLoad:
if (vstate.GetIdOpcode(vstate.GetOperandTypeId(&inst, 2)) ==
spv::Op::OpTypeUntypedPointerKHR) {
const auto ptr_id = inst.GetOperandAs<uint32_t>(2);
ptr_ty_id = vstate.FindDef(ptr_id)->type_id();
data_type_id = inst.type_id();
}
break;
case spv::Op::OpStore:
if (vstate.GetIdOpcode(vstate.GetOperandTypeId(&inst, 0)) ==
spv::Op::OpTypeUntypedPointerKHR) {
const auto ptr_id = inst.GetOperandAs<uint32_t>(0);
ptr_ty_id = vstate.FindDef(ptr_id)->type_id();
data_type_id = vstate.GetOperandTypeId(&inst, 1);
}
break;
case spv::Op::OpUntypedArrayLengthKHR:
ptr_ty_id = vstate.FindDef(inst.GetOperandAs<uint32_t>(3))->type_id();
data_type_id = inst.GetOperandAs<uint32_t>(2);
break;
default:
break;
}
if (ptr_ty_id == 0 || data_type_id == 0) {
// Not an untyped pointer.
continue;
}
const auto sc =
vstate.FindDef(ptr_ty_id)->GetOperandAs<spv::StorageClass>(1);
const char* sc_str =
sc == spv::StorageClass::Uniform
? "Uniform"
: (sc == spv::StorageClass::PushConstant
? "PushConstant"
: (sc == spv::StorageClass::Workgroup ? "Workgroup"
: "StorageBuffer"));
const auto data_type = vstate.FindDef(data_type_id);
scalar_block_layout =
sc == spv::StorageClass::Workgroup
? vstate.options()->workgroup_scalar_block_layout
: vstate.options()->scalar_block_layout;
// Assume uniform storage class uses block rules unless we see a
// BufferBlock decorated struct in the data type.
bool bufferRules = sc == spv::StorageClass::Uniform ? false : true;
if (data_type->opcode() == spv::Op::OpTypeStruct) {
if (sc == spv::StorageClass::Uniform) {
bufferRules =
vstate.HasDecoration(data_type_id, spv::Decoration::BufferBlock);
}
ComputeMemberConstraintsForStruct(&constraints, data_type_id,
LayoutConstraints(), vstate);
}
if (auto res = checkLayout(data_type_id, "PhysicalStorageBuffer", "Block",
!buffer, scalar_block_layout, 0, constraints,
vstate)) {
return res;
const char* deco_str =
bufferRules
? (sc == spv::StorageClass::Uniform ? "BufferBlock" : "Block")
: "Block";
if (auto result =
checkLayout(data_type_id, sc_str, deco_str, !bufferRules,
scalar_block_layout, 0, constraints, vstate)) {
return result;
}
}
}
@ -1585,15 +1706,19 @@ spv_result_t CheckNonWritableDecoration(ValidationState_t& vstate,
const auto opcode = inst.opcode();
const auto type_id = inst.type_id();
if (opcode != spv::Op::OpVariable &&
opcode != spv::Op::OpUntypedVariableKHR &&
opcode != spv::Op::OpFunctionParameter &&
opcode != spv::Op::OpRawAccessChainNV) {
return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
<< "Target of NonWritable decoration must be a memory object "
"declaration (a variable or a function parameter)";
}
const auto var_storage_class = opcode == spv::Op::OpVariable
? inst.GetOperandAs<spv::StorageClass>(2)
: spv::StorageClass::Max;
const auto var_storage_class =
opcode == spv::Op::OpVariable
? inst.GetOperandAs<spv::StorageClass>(2)
: opcode == spv::Op::OpUntypedVariableKHR
? inst.GetOperandAs<spv::StorageClass>(3)
: spv::StorageClass::Max;
if ((var_storage_class == spv::StorageClass::Function ||
var_storage_class == spv::StorageClass::Private) &&
vstate.features().nonwritable_var_in_function_or_private) {

View File

@ -156,7 +156,9 @@ spv_result_t ValidateFunctionParameter(ValidationState_t& _,
param_nonarray_type_id =
_.FindDef(param_nonarray_type_id)->GetOperandAs<uint32_t>(1u);
}
if (_.GetIdOpcode(param_nonarray_type_id) == spv::Op::OpTypePointer) {
if (_.GetIdOpcode(param_nonarray_type_id) == spv::Op::OpTypePointer ||
_.GetIdOpcode(param_nonarray_type_id) ==
spv::Op::OpTypeUntypedPointerKHR) {
auto param_nonarray_type = _.FindDef(param_nonarray_type_id);
if (param_nonarray_type->GetOperandAs<spv::StorageClass>(1u) ==
spv::StorageClass::PhysicalStorageBuffer) {
@ -185,7 +187,7 @@ spv_result_t ValidateFunctionParameter(ValidationState_t& _,
<< ": can't specify both Aliased and Restrict for "
"PhysicalStorageBuffer pointer.";
}
} else {
} else if (param_nonarray_type->opcode() == spv::Op::OpTypePointer) {
const auto pointee_type_id =
param_nonarray_type->GetOperandAs<uint32_t>(2);
const auto pointee_type = _.FindDef(pointee_type_id);
@ -288,7 +290,8 @@ spv_result_t ValidateFunctionCall(ValidationState_t& _,
}
if (_.addressing_model() == spv::AddressingModel::Logical) {
if (parameter_type->opcode() == spv::Op::OpTypePointer &&
if ((parameter_type->opcode() == spv::Op::OpTypePointer ||
parameter_type->opcode() == spv::Op::OpTypeUntypedPointerKHR) &&
!_.options()->relax_logical_pointer) {
spv::StorageClass sc =
parameter_type->GetOperandAs<spv::StorageClass>(1u);
@ -317,9 +320,11 @@ spv_result_t ValidateFunctionCall(ValidationState_t& _,
// Validate memory object declaration requirements.
if (argument->opcode() != spv::Op::OpVariable &&
argument->opcode() != spv::Op::OpUntypedVariableKHR &&
argument->opcode() != spv::Op::OpFunctionParameter) {
const bool ssbo_vptr = _.features().variable_pointers &&
sc == spv::StorageClass::StorageBuffer;
const bool ssbo_vptr =
_.HasCapability(spv::Capability::VariablePointersStorageBuffer) &&
sc == spv::StorageClass::StorageBuffer;
const bool wg_vptr =
_.HasCapability(spv::Capability::VariablePointers) &&
sc == spv::StorageClass::Workgroup;

View File

@ -165,6 +165,8 @@ spv_result_t IdPass(ValidationState_t& _, Instruction* inst) {
!spvOpcodeIsDecoration(opcode) && opcode != spv::Op::OpFunction &&
opcode != spv::Op::OpCooperativeMatrixLengthNV &&
opcode != spv::Op::OpCooperativeMatrixLengthKHR &&
!spvOpcodeGeneratesUntypedPointer(opcode) &&
opcode != spv::Op::OpUntypedArrayLengthKHR &&
!(opcode == spv::Op::OpSpecConstantOp &&
(spv::Op(inst->word(3)) ==
spv::Op::OpCooperativeMatrixLengthNV ||
@ -185,6 +187,8 @@ spv_result_t IdPass(ValidationState_t& _, Instruction* inst) {
opcode != spv::Op::OpFunction &&
opcode != spv::Op::OpCooperativeMatrixLengthNV &&
opcode != spv::Op::OpCooperativeMatrixLengthKHR &&
!spvOpcodeGeneratesUntypedPointer(opcode) &&
opcode != spv::Op::OpUntypedArrayLengthKHR &&
!(opcode == spv::Op::OpSpecConstantOp &&
(spv::Op(inst->word(3)) ==
spv::Op::OpCooperativeMatrixLengthNV ||

View File

@ -1121,7 +1121,8 @@ spv_result_t ValidateSampledImage(ValidationState_t& _,
spv_result_t ValidateImageTexelPointer(ValidationState_t& _,
const Instruction* inst) {
const auto result_type = _.FindDef(inst->type_id());
if (result_type->opcode() != spv::Op::OpTypePointer) {
if (result_type->opcode() != spv::Op::OpTypePointer &&
result_type->opcode() == spv::Op::OpTypeUntypedPointerKHR) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected Result Type to be OpTypePointer";
}
@ -1133,16 +1134,20 @@ spv_result_t ValidateImageTexelPointer(ValidationState_t& _,
"operand is Image";
}
const auto ptr_type = result_type->GetOperandAs<uint32_t>(2);
const auto ptr_opcode = _.GetIdOpcode(ptr_type);
if (ptr_opcode != spv::Op::OpTypeInt && ptr_opcode != spv::Op::OpTypeFloat &&
ptr_opcode != spv::Op::OpTypeVoid &&
!(ptr_opcode == spv::Op::OpTypeVector &&
_.HasCapability(spv::Capability::AtomicFloat16VectorNV) &&
_.IsFloat16Vector2Or4Type(ptr_type))) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected Result Type to be OpTypePointer whose Type operand "
"must be a scalar numerical type or OpTypeVoid";
uint32_t ptr_type = 0;
if (result_type->opcode() == spv::Op::OpTypePointer) {
ptr_type = result_type->GetOperandAs<uint32_t>(2);
const auto ptr_opcode = _.GetIdOpcode(ptr_type);
if (ptr_opcode != spv::Op::OpTypeInt &&
ptr_opcode != spv::Op::OpTypeFloat &&
ptr_opcode != spv::Op::OpTypeVoid &&
!(ptr_opcode == spv::Op::OpTypeVector &&
_.HasCapability(spv::Capability::AtomicFloat16VectorNV) &&
_.IsFloat16Vector2Or4Type(ptr_type))) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected Result Type to be OpTypePointer whose Type operand "
"must be a scalar numerical type or OpTypeVoid";
}
}
const auto image_ptr = _.FindDef(_.GetOperandTypeId(inst, 2));
@ -1163,7 +1168,8 @@ spv_result_t ValidateImageTexelPointer(ValidationState_t& _,
<< "Corrupt image type definition";
}
if (info.sampled_type != ptr_type &&
if (result_type->opcode() == spv::Op::OpTypePointer &&
info.sampled_type != ptr_type &&
!(_.HasCapability(spv::Capability::AtomicFloat16VectorNV) &&
_.IsFloat16Vector2Or4Type(ptr_type) &&
_.GetIdOpcode(info.sampled_type) == spv::Op::OpTypeFloat &&

View File

@ -34,11 +34,13 @@ const uint32_t kMaxLocations = 4096 * 4;
bool is_interface_variable(const Instruction* inst, bool is_spv_1_4) {
if (is_spv_1_4) {
// Starting in SPIR-V 1.4, all global variables are interface variables.
return inst->opcode() == spv::Op::OpVariable &&
return (inst->opcode() == spv::Op::OpVariable ||
inst->opcode() == spv::Op::OpUntypedVariableKHR) &&
inst->GetOperandAs<spv::StorageClass>(2u) !=
spv::StorageClass::Function;
} else {
return inst->opcode() == spv::Op::OpVariable &&
return (inst->opcode() == spv::Op::OpVariable ||
inst->opcode() == spv::Op::OpUntypedVariableKHR) &&
(inst->GetOperandAs<spv::StorageClass>(2u) ==
spv::StorageClass::Input ||
inst->GetOperandAs<spv::StorageClass>(2u) ==
@ -242,8 +244,9 @@ spv_result_t GetLocationsForVariable(
std::unordered_set<uint32_t>* output_index1_locations) {
const bool is_fragment = entry_point->GetOperandAs<spv::ExecutionModel>(0) ==
spv::ExecutionModel::Fragment;
const bool is_output =
variable->GetOperandAs<spv::StorageClass>(2) == spv::StorageClass::Output;
const auto sc_index = 2u;
const bool is_output = variable->GetOperandAs<spv::StorageClass>(sc_index) ==
spv::StorageClass::Output;
auto ptr_type_id = variable->GetOperandAs<uint32_t>(0);
auto ptr_type = _.FindDef(ptr_type_id);
auto type_id = ptr_type->GetOperandAs<uint32_t>(2);
@ -525,7 +528,9 @@ spv_result_t ValidateLocations(ValidationState_t& _,
for (uint32_t i = 3; i < entry_point->operands().size(); ++i) {
auto interface_id = entry_point->GetOperandAs<uint32_t>(i);
auto interface_var = _.FindDef(interface_id);
auto storage_class = interface_var->GetOperandAs<spv::StorageClass>(2);
const auto sc_index = 2u;
auto storage_class =
interface_var->GetOperandAs<spv::StorageClass>(sc_index);
if (storage_class != spv::StorageClass::Input &&
storage_class != spv::StorageClass::Output) {
continue;

View File

@ -159,9 +159,11 @@ spv_result_t LogicalsPass(ValidationState_t& _, const Instruction* inst) {
const spv::Op type_opcode = type_inst->opcode();
switch (type_opcode) {
case spv::Op::OpTypeUntypedPointerKHR:
case spv::Op::OpTypePointer: {
if (_.addressing_model() == spv::AddressingModel::Logical &&
!_.features().variable_pointers)
!_.HasCapability(
spv::Capability::VariablePointersStorageBuffer))
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Using pointers with OpSelect requires capability "
<< "VariablePointers or VariablePointersStorageBuffer";

View File

@ -407,19 +407,58 @@ spv_result_t CheckMemoryAccess(ValidationState_t& _, const Instruction* inst,
}
spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) {
const bool untyped_pointer = inst->opcode() == spv::Op::OpUntypedVariableKHR;
auto result_type = _.FindDef(inst->type_id());
if (!result_type || result_type->opcode() != spv::Op::OpTypePointer) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "OpVariable Result Type <id> " << _.getIdName(inst->type_id())
<< " is not a pointer type.";
if (untyped_pointer) {
if (!result_type ||
result_type->opcode() != spv::Op::OpTypeUntypedPointerKHR)
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Result type must be an untyped pointer";
} else {
if (!result_type || result_type->opcode() != spv::Op::OpTypePointer) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "OpVariable Result Type <id> " << _.getIdName(inst->type_id())
<< " is not a pointer type.";
}
}
const auto type_index = 2;
const auto value_id = result_type->GetOperandAs<uint32_t>(type_index);
auto value_type = _.FindDef(value_id);
const auto storage_class_index = 2u;
auto storage_class =
inst->GetOperandAs<spv::StorageClass>(storage_class_index);
uint32_t value_id = 0;
if (untyped_pointer) {
const auto has_data_type = 3u < inst->operands().size();
if (has_data_type) {
value_id = inst->GetOperandAs<uint32_t>(3u);
auto data_type = _.FindDef(value_id);
if (!data_type || !spvOpcodeGeneratesType(data_type->opcode())) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Data type must be a type instruction";
}
} else {
if (storage_class == spv::StorageClass::Function ||
storage_class == spv::StorageClass::Private ||
storage_class == spv::StorageClass::Workgroup) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Data type must be specified for Function, Private, and "
"Workgroup storage classes";
}
if (spvIsVulkanEnv(_.context()->target_env)) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Vulkan requires that data type be specified";
}
}
}
const auto initializer_index = 3;
const auto storage_class_index = 2;
// For OpVariable the data type comes from pointee type of the result type,
// while for OpUntypedVariableKHR the data type comes from the operand.
if (!untyped_pointer) {
value_id = result_type->GetOperandAs<uint32_t>(2);
}
auto value_type = value_id == 0 ? nullptr : _.FindDef(value_id);
const auto initializer_index = untyped_pointer ? 4u : 3u;
if (initializer_index < inst->operands().size()) {
const auto initializer_id = inst->GetOperandAs<uint32_t>(initializer_index);
const auto initializer = _.FindDef(initializer_id);
@ -431,18 +470,15 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) {
initializer && spvOpcodeIsConstant(initializer->opcode());
if (!initializer || !(is_constant || is_module_scope_var)) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "OpVariable Initializer <id> " << _.getIdName(initializer_id)
<< "Variable Initializer <id> " << _.getIdName(initializer_id)
<< " is not a constant or module-scope variable.";
}
if (initializer->type_id() != value_id) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Initializer type must match the type pointed to by the Result "
"Type";
<< "Initializer type must match the data type";
}
}
auto storage_class =
inst->GetOperandAs<spv::StorageClass>(storage_class_index);
if (storage_class != spv::StorageClass::Workgroup &&
storage_class != spv::StorageClass::CrossWorkgroup &&
storage_class != spv::StorageClass::Private &&
@ -466,7 +502,7 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) {
}
}
}
if (!builtin &&
if (!builtin && value_type &&
ContainsInvalidBool(_, value_type, storage_input_or_output)) {
if (storage_input_or_output) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
@ -495,7 +531,7 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) {
if (storage_class == spv::StorageClass::Generic) {
return _.diag(SPV_ERROR_INVALID_BINARY, inst)
<< "OpVariable storage class cannot be Generic";
<< "Variable storage class cannot be Generic";
}
if (inst->function() && storage_class != spv::StorageClass::Function) {
@ -517,17 +553,17 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) {
result_type->GetOperandAs<spv::StorageClass>(result_storage_class_index);
if (storage_class != result_storage_class) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "From SPIR-V spec, section 3.32.8 on OpVariable:\n"
<< "Its Storage Class operand must be the same as the Storage Class "
<< "operand of the result type.";
<< "Storage class must match result type storage class";
}
// Variable pointer related restrictions.
const auto pointee = _.FindDef(result_type->word(3));
const auto pointee = untyped_pointer
? value_id == 0 ? nullptr : _.FindDef(value_id)
: _.FindDef(result_type->word(3));
if (_.addressing_model() == spv::AddressingModel::Logical &&
!_.options()->relax_logical_pointer) {
// VariablePointersStorageBuffer is implied by VariablePointers.
if (pointee->opcode() == spv::Op::OpTypePointer) {
if (pointee && pointee->opcode() == spv::Op::OpTypePointer) {
if (!_.HasCapability(spv::Capability::VariablePointersStorageBuffer)) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "In Logical addressing, variables may not allocate a pointer "
@ -546,7 +582,7 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) {
// Vulkan Push Constant Interface section: Check type of PushConstant
// variables.
if (storage_class == spv::StorageClass::PushConstant) {
if (pointee->opcode() != spv::Op::OpTypeStruct) {
if (pointee && pointee->opcode() != spv::Op::OpTypeStruct) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< _.VkErrorID(6808) << "PushConstant OpVariable <id> "
<< _.getIdName(inst->id()) << " has illegal type.\n"
@ -558,11 +594,11 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) {
// Vulkan Descriptor Set Interface: Check type of UniformConstant and
// Uniform variables.
if (storage_class == spv::StorageClass::UniformConstant) {
if (!IsAllowedTypeOrArrayOfSame(
_, pointee,
{spv::Op::OpTypeImage, spv::Op::OpTypeSampler,
spv::Op::OpTypeSampledImage,
spv::Op::OpTypeAccelerationStructureKHR})) {
if (pointee && !IsAllowedTypeOrArrayOfSame(
_, pointee,
{spv::Op::OpTypeImage, spv::Op::OpTypeSampler,
spv::Op::OpTypeSampledImage,
spv::Op::OpTypeAccelerationStructureKHR})) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< _.VkErrorID(4655) << "UniformConstant OpVariable <id> "
<< _.getIdName(inst->id()) << " has illegal type.\n"
@ -575,7 +611,8 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) {
}
if (storage_class == spv::StorageClass::Uniform) {
if (!IsAllowedTypeOrArrayOfSame(_, pointee, {spv::Op::OpTypeStruct})) {
if (pointee &&
!IsAllowedTypeOrArrayOfSame(_, pointee, {spv::Op::OpTypeStruct})) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< _.VkErrorID(6807) << "Uniform OpVariable <id> "
<< _.getIdName(inst->id()) << " has illegal type.\n"
@ -588,7 +625,8 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) {
}
if (storage_class == spv::StorageClass::StorageBuffer) {
if (!IsAllowedTypeOrArrayOfSame(_, pointee, {spv::Op::OpTypeStruct})) {
if (pointee &&
!IsAllowedTypeOrArrayOfSame(_, pointee, {spv::Op::OpTypeStruct})) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< _.VkErrorID(6807) << "StorageBuffer OpVariable <id> "
<< _.getIdName(inst->id()) << " has illegal type.\n"
@ -621,11 +659,17 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) {
}
}
}
}
// Initializers in Vulkan are only allowed in some storage clases
if (inst->operands().size() > 3) {
// Vulkan Appendix A: Check that if contains initializer, then
// storage class is Output, Private, or Function.
if (inst->operands().size() > initializer_index &&
storage_class != spv::StorageClass::Output &&
storage_class != spv::StorageClass::Private &&
storage_class != spv::StorageClass::Function) {
if (spvIsVulkanEnv(_.context()->target_env)) {
if (storage_class == spv::StorageClass::Workgroup) {
auto init_id = inst->GetOperandAs<uint32_t>(3);
auto init_id = inst->GetOperandAs<uint32_t>(initializer_index);
auto init = _.FindDef(init_id);
if (init->opcode() != spv::Op::OpConstantNull) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
@ -652,7 +696,7 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) {
}
}
if (inst->operands().size() > 3) {
if (initializer_index < inst->operands().size()) {
if (storage_class == spv::StorageClass::TaskPayloadWorkgroupEXT) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "OpVariable, <id> " << _.getIdName(inst->id())
@ -676,10 +720,10 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) {
}
auto pointee_base = pointee;
while (pointee_base->opcode() == spv::Op::OpTypeArray) {
while (pointee_base && pointee_base->opcode() == spv::Op::OpTypeArray) {
pointee_base = _.FindDef(pointee_base->GetOperandAs<uint32_t>(1u));
}
if (pointee_base->opcode() == spv::Op::OpTypePointer) {
if (pointee_base && pointee_base->opcode() == spv::Op::OpTypePointer) {
if (pointee_base->GetOperandAs<spv::StorageClass>(1u) ==
spv::StorageClass::PhysicalStorageBuffer) {
// check for AliasedPointer/RestrictPointer
@ -769,7 +813,7 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) {
// Cooperative matrix types can only be allocated in Function or Private
if ((storage_class != spv::StorageClass::Function &&
storage_class != spv::StorageClass::Private) &&
ContainsCooperativeMatrix(_, pointee)) {
pointee && ContainsCooperativeMatrix(_, pointee)) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Cooperative matrix types (or types containing them) can only be "
"allocated "
@ -785,7 +829,8 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) {
(!_.HasCapability(spv::Capability::Float16) &&
_.ContainsSizedIntOrFloatType(value_id, spv::Op::OpTypeFloat, 16))) {
auto underlying_type = value_type;
while (underlying_type->opcode() == spv::Op::OpTypePointer) {
while (underlying_type &&
underlying_type->opcode() == spv::Op::OpTypePointer) {
storage_class = underlying_type->GetOperandAs<spv::StorageClass>(1u);
underlying_type =
_.FindDef(underlying_type->GetOperandAs<uint32_t>(2u));
@ -801,7 +846,8 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) {
}
break;
case spv::StorageClass::Uniform:
if (!_.HasCapability(
if (underlying_type &&
!_.HasCapability(
spv::Capability::UniformAndStorageBuffer16BitAccess)) {
if (underlying_type->opcode() == spv::Op::OpTypeArray ||
underlying_type->opcode() == spv::Op::OpTypeRuntimeArray) {
@ -849,7 +895,8 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) {
if (!_.HasCapability(spv::Capability::Int8) &&
_.ContainsSizedIntOrFloatType(value_id, spv::Op::OpTypeInt, 8)) {
auto underlying_type = value_type;
while (underlying_type->opcode() == spv::Op::OpTypePointer) {
while (underlying_type &&
underlying_type->opcode() == spv::Op::OpTypePointer) {
storage_class = underlying_type->GetOperandAs<spv::StorageClass>(1u);
underlying_type =
_.FindDef(underlying_type->GetOperandAs<uint32_t>(2u));
@ -865,7 +912,8 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) {
}
break;
case spv::StorageClass::Uniform:
if (!_.HasCapability(
if (underlying_type &&
!_.HasCapability(
spv::Capability::UniformAndStorageBuffer8BitAccess)) {
if (underlying_type->opcode() == spv::Op::OpTypeArray ||
underlying_type->opcode() == spv::Op::OpTypeRuntimeArray) {
@ -930,21 +978,23 @@ spv_result_t ValidateLoad(ValidationState_t& _, const Instruction* inst) {
}
const auto pointer_type = _.FindDef(pointer->type_id());
if (!pointer_type || pointer_type->opcode() != spv::Op::OpTypePointer) {
if (!pointer_type ||
(pointer_type->opcode() != spv::Op::OpTypePointer &&
pointer_type->opcode() != spv::Op::OpTypeUntypedPointerKHR)) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "OpLoad type for pointer <id> " << _.getIdName(pointer_id)
<< " is not a pointer type.";
}
uint32_t pointee_data_type;
spv::StorageClass storage_class;
if (!_.GetPointerTypeInfo(pointer_type->id(), &pointee_data_type,
&storage_class) ||
result_type->id() != pointee_data_type) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "OpLoad Result Type <id> " << _.getIdName(inst->type_id())
<< " does not match Pointer <id> " << _.getIdName(pointer->id())
<< "s type.";
if (pointer_type->opcode() == spv::Op::OpTypePointer) {
const auto pointee_type =
_.FindDef(pointer_type->GetOperandAs<uint32_t>(2));
if (!pointee_type || result_type->id() != pointee_type->id()) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "OpLoad Result Type <id> " << _.getIdName(inst->type_id())
<< " does not match Pointer <id> " << _.getIdName(pointer->id())
<< "s type.";
}
}
if (!_.options()->before_hlsl_legalization &&
@ -987,17 +1037,23 @@ spv_result_t ValidateStore(ValidationState_t& _, const Instruction* inst) {
<< " is not a logical pointer.";
}
const auto pointer_type = _.FindDef(pointer->type_id());
if (!pointer_type || pointer_type->opcode() != spv::Op::OpTypePointer) {
if (!pointer_type ||
(pointer_type->opcode() != spv::Op::OpTypePointer &&
pointer_type->opcode() != spv::Op::OpTypeUntypedPointerKHR)) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "OpStore type for pointer <id> " << _.getIdName(pointer_id)
<< " is not a pointer type.";
}
const auto type_id = pointer_type->GetOperandAs<uint32_t>(2);
const auto type = _.FindDef(type_id);
if (!type || spv::Op::OpTypeVoid == type->opcode()) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "OpStore Pointer <id> " << _.getIdName(pointer_id)
<< "s type is void.";
Instruction* type = nullptr;
if (pointer_type->opcode() == spv::Op::OpTypePointer) {
const auto type_id = pointer_type->GetOperandAs<uint32_t>(2);
type = _.FindDef(type_id);
if (!type || spv::Op::OpTypeVoid == type->opcode()) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "OpStore Pointer <id> " << _.getIdName(pointer_id)
<< "s type is void.";
}
}
// validate storage class
@ -1074,7 +1130,7 @@ spv_result_t ValidateStore(ValidationState_t& _, const Instruction* inst) {
<< "s type is void.";
}
if (type->id() != object_type->id()) {
if (type && (type->id() != object_type->id())) {
if (!_.options()->relax_struct_store ||
type->opcode() != spv::Op::OpTypeStruct ||
object_type->opcode() != spv::Op::OpTypeStruct) {
@ -1179,7 +1235,8 @@ spv_result_t ValidateCopyMemory(ValidationState_t& _, const Instruction* inst) {
const auto target_pointer_type = _.FindDef(target->type_id());
if (!target_pointer_type ||
target_pointer_type->opcode() != spv::Op::OpTypePointer) {
(target_pointer_type->opcode() != spv::Op::OpTypePointer &&
target_pointer_type->opcode() != spv::Op::OpTypeUntypedPointerKHR)) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Target operand <id> " << _.getIdName(target_id)
<< " is not a pointer.";
@ -1187,35 +1244,52 @@ spv_result_t ValidateCopyMemory(ValidationState_t& _, const Instruction* inst) {
const auto source_pointer_type = _.FindDef(source->type_id());
if (!source_pointer_type ||
source_pointer_type->opcode() != spv::Op::OpTypePointer) {
(source_pointer_type->opcode() != spv::Op::OpTypePointer &&
source_pointer_type->opcode() != spv::Op::OpTypeUntypedPointerKHR)) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Source operand <id> " << _.getIdName(source_id)
<< " is not a pointer.";
}
if (inst->opcode() == spv::Op::OpCopyMemory) {
const auto target_type =
_.FindDef(target_pointer_type->GetOperandAs<uint32_t>(2));
if (!target_type || target_type->opcode() == spv::Op::OpTypeVoid) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Target operand <id> " << _.getIdName(target_id)
<< " cannot be a void pointer.";
const bool target_typed =
target_pointer_type->opcode() == spv::Op::OpTypePointer;
const bool source_typed =
source_pointer_type->opcode() == spv::Op::OpTypePointer;
Instruction* target_type = nullptr;
Instruction* source_type = nullptr;
if (target_typed) {
target_type = _.FindDef(target_pointer_type->GetOperandAs<uint32_t>(2));
if (!target_type || target_type->opcode() == spv::Op::OpTypeVoid) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Target operand <id> " << _.getIdName(target_id)
<< " cannot be a void pointer.";
}
}
const auto source_type =
_.FindDef(source_pointer_type->GetOperandAs<uint32_t>(2));
if (!source_type || source_type->opcode() == spv::Op::OpTypeVoid) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Source operand <id> " << _.getIdName(source_id)
<< " cannot be a void pointer.";
if (source_typed) {
source_type = _.FindDef(source_pointer_type->GetOperandAs<uint32_t>(2));
if (!source_type || source_type->opcode() == spv::Op::OpTypeVoid) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Source operand <id> " << _.getIdName(source_id)
<< " cannot be a void pointer.";
}
}
if (target_type->id() != source_type->id()) {
if (target_type && source_type && target_type->id() != source_type->id()) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Target <id> " << _.getIdName(source_id)
<< "s type does not match Source <id> "
<< _.getIdName(source_type->id()) << "s type.";
}
if (!target_type && !source_type) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "One of Source or Target must be a typed pointer";
}
if (auto error = CheckMemoryAccess(_, inst, 2)) return error;
} else {
const auto size_id = inst->GetOperandAs<uint32_t>(2);
const auto size = _.FindDef(size_id);
@ -1231,7 +1305,6 @@ spv_result_t ValidateCopyMemory(ValidationState_t& _, const Instruction* inst) {
<< "Size operand <id> " << _.getIdName(size_id)
<< " must be a scalar integer type.";
}
bool is_zero = true;
switch (size->opcode()) {
case spv::Op::OpConstantNull:
@ -1258,18 +1331,125 @@ spv_result_t ValidateCopyMemory(ValidationState_t& _, const Instruction* inst) {
// Cannot infer any other opcodes.
break;
}
if (_.HasCapability(spv::Capability::Shader)) {
bool is_int = false;
bool is_const = false;
uint32_t value = 0;
std::tie(is_int, is_const, value) = _.EvalInt32IfConst(size_id);
if (is_const) {
if (value % 4 != 0) {
const auto source_sc =
source_pointer_type->GetOperandAs<spv::StorageClass>(1);
const auto target_sc =
target_pointer_type->GetOperandAs<spv::StorageClass>(1);
const bool int8 = _.HasCapability(spv::Capability::Int8);
const bool ubo_int8 = _.HasCapability(
spv::Capability::UniformAndStorageBuffer8BitAccess);
const bool ssbo_int8 =
_.HasCapability(spv::Capability::StorageBuffer8BitAccess) ||
ubo_int8;
const bool pc_int8 =
_.HasCapability(spv::Capability::StoragePushConstant8);
const bool wg_int8 = _.HasCapability(
spv::Capability::WorkgroupMemoryExplicitLayout8BitAccessKHR);
const bool int16 = _.HasCapability(spv::Capability::Int16) || int8;
const bool ubo_int16 =
_.HasCapability(
spv::Capability::UniformAndStorageBuffer16BitAccess) ||
ubo_int8;
const bool ssbo_int16 =
_.HasCapability(spv::Capability::StorageBuffer16BitAccess) ||
ubo_int16 || ssbo_int8;
const bool pc_int16 =
_.HasCapability(spv::Capability::StoragePushConstant16) ||
pc_int8;
const bool io_int16 =
_.HasCapability(spv::Capability::StorageInputOutput16);
const bool wg_int16 = _.HasCapability(
spv::Capability::WorkgroupMemoryExplicitLayout16BitAccessKHR);
bool source_int16_match = false;
bool target_int16_match = false;
bool source_int8_match = false;
bool target_int8_match = false;
switch (source_sc) {
case spv::StorageClass::StorageBuffer:
source_int16_match = ssbo_int16;
source_int8_match = ssbo_int8;
break;
case spv::StorageClass::Uniform:
source_int16_match = ubo_int16;
source_int8_match = ubo_int8;
break;
case spv::StorageClass::PushConstant:
source_int16_match = pc_int16;
source_int8_match = pc_int8;
break;
case spv::StorageClass::Input:
case spv::StorageClass::Output:
source_int16_match = io_int16;
break;
case spv::StorageClass::Workgroup:
source_int16_match = wg_int16;
source_int8_match = wg_int8;
break;
default:
break;
}
switch (target_sc) {
case spv::StorageClass::StorageBuffer:
target_int16_match = ssbo_int16;
target_int8_match = ssbo_int8;
break;
case spv::StorageClass::Uniform:
target_int16_match = ubo_int16;
target_int8_match = ubo_int8;
break;
case spv::StorageClass::PushConstant:
target_int16_match = pc_int16;
target_int8_match = pc_int8;
break;
// Input is read-only so it cannot be the target pointer.
case spv::StorageClass::Output:
target_int16_match = io_int16;
break;
case spv::StorageClass::Workgroup:
target_int16_match = wg_int16;
target_int8_match = wg_int8;
break;
default:
break;
}
if (!int8 && !int16 && !(source_int16_match && target_int16_match)) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Size must be a multiple of 4";
}
if (value % 2 != 0) {
if (!int8 && !(source_int8_match && target_int8_match)) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Size must be a multiple of 2";
}
}
}
}
}
if (auto error = CheckMemoryAccess(_, inst, 3)) return error;
}
if (auto error = ValidateCopyMemoryMemoryAccess(_, inst)) return error;
// Get past the pointers to avoid checking a pointer copy.
auto sub_type = _.FindDef(target_pointer_type->GetOperandAs<uint32_t>(2));
while (sub_type->opcode() == spv::Op::OpTypePointer) {
sub_type = _.FindDef(sub_type->GetOperandAs<uint32_t>(2));
}
if (_.HasCapability(spv::Capability::Shader) &&
_.ContainsLimitedUseIntOrFloatType(sub_type->id())) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Cannot copy memory of objects containing 8- or 16-bit types";
if (target_pointer_type->opcode() == spv::Op::OpTypePointer) {
auto sub_type = _.FindDef(target_pointer_type->GetOperandAs<uint32_t>(2));
while (sub_type->opcode() == spv::Op::OpTypePointer) {
sub_type = _.FindDef(sub_type->GetOperandAs<uint32_t>(2));
}
if (_.HasCapability(spv::Capability::Shader) &&
_.ContainsLimitedUseIntOrFloatType(sub_type->id())) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Cannot copy memory of objects containing 8- or 16-bit types";
}
}
return SPV_SUCCESS;
@ -1280,27 +1460,50 @@ spv_result_t ValidateAccessChain(ValidationState_t& _,
std::string instr_name =
"Op" + std::string(spvOpcodeString(static_cast<spv::Op>(inst->opcode())));
// The result type must be OpTypePointer.
const bool untyped_pointer = spvOpcodeGeneratesUntypedPointer(inst->opcode());
// The result type must be OpTypePointer for regular access chains and an
// OpTypeUntypedPointerKHR for untyped access chains.
auto result_type = _.FindDef(inst->type_id());
if (spv::Op::OpTypePointer != result_type->opcode()) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "The Result Type of " << instr_name << " <id> "
<< _.getIdName(inst->id()) << " must be OpTypePointer. Found Op"
<< spvOpcodeString(static_cast<spv::Op>(result_type->opcode()))
<< ".";
if (untyped_pointer) {
if (!result_type ||
spv::Op::OpTypeUntypedPointerKHR != result_type->opcode()) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "The Result Type of " << instr_name << " <id> "
<< _.getIdName(inst->id())
<< " must be OpTypeUntypedPointerKHR. Found Op"
<< spvOpcodeString(static_cast<spv::Op>(result_type->opcode()))
<< ".";
}
} else {
if (!result_type || spv::Op::OpTypePointer != result_type->opcode()) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "The Result Type of " << instr_name << " <id> "
<< _.getIdName(inst->id()) << " must be OpTypePointer. Found Op"
<< spvOpcodeString(static_cast<spv::Op>(result_type->opcode()))
<< ".";
}
}
// Result type is a pointer. Find out what it's pointing to.
// This will be used to make sure the indexing results in the same type.
// OpTypePointer word 3 is the type being pointed to.
const auto result_type_pointee = _.FindDef(result_type->word(3));
if (untyped_pointer) {
// Base type must be a non-pointer type.
const auto base_type = _.FindDef(inst->GetOperandAs<uint32_t>(2));
if (!base_type || !spvOpcodeGeneratesType(base_type->opcode()) ||
base_type->opcode() == spv::Op::OpTypePointer ||
base_type->opcode() == spv::Op::OpTypeUntypedPointerKHR) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Base type must be a non-pointer type";
}
}
// Base must be a pointer, pointing to the base of a composite object.
const auto base_index = 2;
const auto base_index = untyped_pointer ? 3 : 2;
const auto base_id = inst->GetOperandAs<uint32_t>(base_index);
const auto base = _.FindDef(base_id);
const auto base_type = _.FindDef(base->type_id());
if (!base_type || spv::Op::OpTypePointer != base_type->opcode()) {
if (!base_type || !(spv::Op::OpTypePointer == base_type->opcode() ||
(untyped_pointer && spv::Op::OpTypeUntypedPointerKHR ==
base_type->opcode()))) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "The Base <id> " << _.getIdName(base_id) << " in " << instr_name
<< " instruction must be a pointer.";
@ -1318,14 +1521,18 @@ spv_result_t ValidateAccessChain(ValidationState_t& _,
}
// The type pointed to by OpTypePointer (word 3) must be a composite type.
auto type_pointee = _.FindDef(base_type->word(3));
auto type_pointee = untyped_pointer
? _.FindDef(inst->GetOperandAs<uint32_t>(2))
: _.FindDef(base_type->word(3));
// Check Universal Limit (SPIR-V Spec. Section 2.17).
// The number of indexes passed to OpAccessChain may not exceed 255
// The instruction includes 4 words + N words (for N indexes)
size_t num_indexes = inst->words().size() - 4;
if (inst->opcode() == spv::Op::OpPtrAccessChain ||
inst->opcode() == spv::Op::OpInBoundsPtrAccessChain) {
inst->opcode() == spv::Op::OpInBoundsPtrAccessChain ||
inst->opcode() == spv::Op::OpUntypedPtrAccessChainKHR ||
inst->opcode() == spv::Op::OpUntypedInBoundsPtrAccessChainKHR) {
// In pointer access chains, the element operand is required, but not
// counted as an index.
--num_indexes;
@ -1344,9 +1551,11 @@ spv_result_t ValidateAccessChain(ValidationState_t& _,
// instruction. The second index will apply similarly to that result, and so
// on. Once any non-composite type is reached, there must be no remaining
// (unused) indexes.
auto starting_index = 4;
auto starting_index = untyped_pointer ? 5 : 4;
if (inst->opcode() == spv::Op::OpPtrAccessChain ||
inst->opcode() == spv::Op::OpInBoundsPtrAccessChain) {
inst->opcode() == spv::Op::OpInBoundsPtrAccessChain ||
inst->opcode() == spv::Op::OpUntypedPtrAccessChainKHR ||
inst->opcode() == spv::Op::OpUntypedInBoundsPtrAccessChainKHR) {
++starting_index;
}
for (size_t i = starting_index; i < inst->words().size(); ++i) {
@ -1411,18 +1620,25 @@ spv_result_t ValidateAccessChain(ValidationState_t& _,
}
}
}
// At this point, we have fully walked down from the base using the indices.
// The type being pointed to should be the same as the result type.
if (type_pointee->id() != result_type_pointee->id()) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< instr_name << " result type (Op"
<< spvOpcodeString(
static_cast<spv::Op>(result_type_pointee->opcode()))
<< ") does not match the type that results from indexing into the "
"base "
"<id> (Op"
<< spvOpcodeString(static_cast<spv::Op>(type_pointee->opcode()))
<< ").";
if (!untyped_pointer) {
// Result type is a pointer. Find out what it's pointing to.
// This will be used to make sure the indexing results in the same type.
// OpTypePointer word 3 is the type being pointed to.
const auto result_type_pointee = _.FindDef(result_type->word(3));
// At this point, we have fully walked down from the base using the indeces.
// The type being pointed to should be the same as the result type.
if (type_pointee->id() != result_type_pointee->id()) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< instr_name << " result type (Op"
<< spvOpcodeString(
static_cast<spv::Op>(result_type_pointee->opcode()))
<< ") does not match the type that results from indexing into the "
"base "
"<id> (Op"
<< spvOpcodeString(static_cast<spv::Op>(type_pointee->opcode()))
<< ").";
}
}
return SPV_SUCCESS;
@ -1550,7 +1766,8 @@ spv_result_t ValidateRawAccessChain(ValidationState_t& _,
spv_result_t ValidatePtrAccessChain(ValidationState_t& _,
const Instruction* inst) {
if (_.addressing_model() == spv::AddressingModel::Logical) {
if (_.addressing_model() == spv::AddressingModel::Logical &&
inst->opcode() == spv::Op::OpPtrAccessChain) {
if (!_.features().variable_pointers) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Generating variable pointers requires capability "
@ -1561,9 +1778,13 @@ spv_result_t ValidatePtrAccessChain(ValidationState_t& _,
// Need to call first, will make sure Base is a valid ID
if (auto error = ValidateAccessChain(_, inst)) return error;
const bool untyped_pointer = spvOpcodeGeneratesUntypedPointer(inst->opcode());
const auto base_id = inst->GetOperandAs<uint32_t>(2);
const auto base = _.FindDef(base_id);
const auto base_type = _.FindDef(base->type_id());
const auto base_type = untyped_pointer
? _.FindDef(inst->GetOperandAs<uint32_t>(2))
: _.FindDef(base->type_id());
const auto base_type_storage_class =
base_type->GetOperandAs<spv::StorageClass>(1);
@ -1581,15 +1802,17 @@ spv_result_t ValidatePtrAccessChain(ValidationState_t& _,
}
if (spvIsVulkanEnv(_.context()->target_env)) {
const auto untyped_cap =
untyped_pointer && _.HasCapability(spv::Capability::UntypedPointersKHR);
if (base_type_storage_class == spv::StorageClass::Workgroup) {
if (!_.HasCapability(spv::Capability::VariablePointers)) {
if (!_.HasCapability(spv::Capability::VariablePointers) && !untyped_cap) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< _.VkErrorID(7651)
<< "OpPtrAccessChain Base operand pointing to Workgroup "
"storage class must use VariablePointers capability";
}
} else if (base_type_storage_class == spv::StorageClass::StorageBuffer) {
if (!_.features().variable_pointers) {
if (!_.features().variable_pointers && !untyped_cap) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< _.VkErrorID(7652)
<< "OpPtrAccessChain Base operand pointing to StorageBuffer "
@ -1597,7 +1820,8 @@ spv_result_t ValidatePtrAccessChain(ValidationState_t& _,
"VariablePointersStorageBuffer capability";
}
} else if (base_type_storage_class !=
spv::StorageClass::PhysicalStorageBuffer) {
spv::StorageClass::PhysicalStorageBuffer &&
!untyped_cap) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< _.VkErrorID(7650)
<< "OpPtrAccessChain Base operand must point to Workgroup, "
@ -1624,18 +1848,28 @@ spv_result_t ValidateArrayLength(ValidationState_t& state,
<< " must be OpTypeInt with width 32 and signedness 0.";
}
// The structure that is passed in must be an pointer to a structure, whose
// last element is a runtime array.
auto pointer = state.FindDef(inst->GetOperandAs<uint32_t>(2));
auto pointer_type = state.FindDef(pointer->type_id());
if (pointer_type->opcode() != spv::Op::OpTypePointer) {
const bool untyped = inst->opcode() == spv::Op::OpUntypedArrayLengthKHR;
auto pointer_ty_id = state.GetOperandTypeId(inst, (untyped ? 3 : 2));
auto pointer_ty = state.FindDef(pointer_ty_id);
if (untyped) {
if (pointer_ty->opcode() != spv::Op::OpTypeUntypedPointerKHR) {
return state.diag(SPV_ERROR_INVALID_ID, inst)
<< "Pointer must be an untyped pointer";
}
} else if (pointer_ty->opcode() != spv::Op::OpTypePointer) {
return state.diag(SPV_ERROR_INVALID_ID, inst)
<< "The Structure's type in " << instr_name << " <id> "
<< state.getIdName(inst->id())
<< " must be a pointer to an OpTypeStruct.";
}
auto structure_type = state.FindDef(pointer_type->GetOperandAs<uint32_t>(2));
Instruction* structure_type = nullptr;
if (untyped) {
structure_type = state.FindDef(inst->GetOperandAs<uint32_t>(2));
} else {
structure_type = state.FindDef(pointer_ty->GetOperandAs<uint32_t>(2));
}
if (structure_type->opcode() != spv::Op::OpTypeStruct) {
return state.diag(SPV_ERROR_INVALID_ID, inst)
<< "The Structure's type in " << instr_name << " <id> "
@ -1654,11 +1888,12 @@ spv_result_t ValidateArrayLength(ValidationState_t& state,
// The array member must the index of the last element (the run time
// array).
if (inst->GetOperandAs<uint32_t>(3) != num_of_members - 1) {
const auto index = untyped ? 4 : 3;
if (inst->GetOperandAs<uint32_t>(index) != num_of_members - 1) {
return state.diag(SPV_ERROR_INVALID_ID, inst)
<< "The array member in " << instr_name << " <id> "
<< state.getIdName(inst->id())
<< " must be an the last member of the struct.";
<< " must be the last member of the struct.";
}
return SPV_SUCCESS;
}
@ -1843,12 +2078,16 @@ spv_result_t ValidateCooperativeMatrixLoadStoreKHR(ValidationState_t& _,
const auto pointer_type_id = pointer->type_id();
const auto pointer_type = _.FindDef(pointer_type_id);
if (!pointer_type || pointer_type->opcode() != spv::Op::OpTypePointer) {
if (!pointer_type ||
!(pointer_type->opcode() == spv::Op::OpTypePointer ||
pointer_type->opcode() == spv::Op::OpTypeUntypedPointerKHR)) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< opname << " type for pointer <id> " << _.getIdName(pointer_id)
<< " is not a pointer type.";
}
const bool untyped =
pointer_type->opcode() == spv::Op::OpTypeUntypedPointerKHR;
const auto storage_class_index = 1u;
const auto storage_class =
pointer_type->GetOperandAs<spv::StorageClass>(storage_class_index);
@ -1863,13 +2102,15 @@ spv_result_t ValidateCooperativeMatrixLoadStoreKHR(ValidationState_t& _,
<< " is not Workgroup, StorageBuffer, or PhysicalStorageBuffer.";
}
const auto pointee_id = pointer_type->GetOperandAs<uint32_t>(2);
const auto pointee_type = _.FindDef(pointee_id);
if (!pointee_type || !(_.IsIntScalarOrVectorType(pointee_id) ||
_.IsFloatScalarOrVectorType(pointee_id))) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< opname << " Pointer <id> " << _.getIdName(pointer->id())
<< "s Type must be a scalar or vector type.";
if (!untyped) {
const auto pointee_id = pointer_type->GetOperandAs<uint32_t>(2);
const auto pointee_type = _.FindDef(pointee_id);
if (!pointee_type || !(_.IsIntScalarOrVectorType(pointee_id) ||
_.IsFloatScalarOrVectorType(pointee_id))) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< opname << " Pointer <id> " << _.getIdName(pointer->id())
<< "s Type must be a scalar or vector type.";
}
}
const auto layout_index =
@ -1935,7 +2176,8 @@ spv_result_t ValidatePtrComparison(ValidationState_t& _,
<< "The types of Operand 1 and Operand 2 must match";
}
const auto op1_type = _.FindDef(op1->type_id());
if (!op1_type || op1_type->opcode() != spv::Op::OpTypePointer) {
if (!op1_type || (op1_type->opcode() != spv::Op::OpTypePointer &&
op1_type->opcode() != spv::Op::OpTypeUntypedPointerKHR)) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Operand type must be a pointer";
}
@ -1967,6 +2209,7 @@ spv_result_t ValidatePtrComparison(ValidationState_t& _,
spv_result_t MemoryPass(ValidationState_t& _, const Instruction* inst) {
switch (inst->opcode()) {
case spv::Op::OpVariable:
case spv::Op::OpUntypedVariableKHR:
if (auto error = ValidateVariable(_, inst)) return error;
break;
case spv::Op::OpLoad:
@ -1980,17 +2223,22 @@ spv_result_t MemoryPass(ValidationState_t& _, const Instruction* inst) {
if (auto error = ValidateCopyMemory(_, inst)) return error;
break;
case spv::Op::OpPtrAccessChain:
case spv::Op::OpUntypedPtrAccessChainKHR:
case spv::Op::OpUntypedInBoundsPtrAccessChainKHR:
if (auto error = ValidatePtrAccessChain(_, inst)) return error;
break;
case spv::Op::OpAccessChain:
case spv::Op::OpInBoundsAccessChain:
case spv::Op::OpInBoundsPtrAccessChain:
case spv::Op::OpUntypedAccessChainKHR:
case spv::Op::OpUntypedInBoundsAccessChainKHR:
if (auto error = ValidateAccessChain(_, inst)) return error;
break;
case spv::Op::OpRawAccessChainNV:
if (auto error = ValidateRawAccessChain(_, inst)) return error;
break;
case spv::Op::OpArrayLength:
case spv::Op::OpUntypedArrayLengthKHR:
if (auto error = ValidateArrayLength(_, inst)) return error;
break;
case spv::Op::OpCooperativeMatrixLoadNV:

View File

@ -36,6 +36,7 @@ spv_result_t ValidateUniqueness(ValidationState_t& _, const Instruction* inst) {
const auto opcode = inst->opcode();
if (opcode != spv::Op::OpTypeArray && opcode != spv::Op::OpTypeRuntimeArray &&
opcode != spv::Op::OpTypeStruct && opcode != spv::Op::OpTypePointer &&
opcode != spv::Op::OpTypeUntypedPointerKHR &&
!_.RegisterUniqueTypeDeclaration(inst)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Duplicate non-aggregate type declarations are not allowed. "
@ -583,6 +584,33 @@ spv_result_t ValidateTypeCooperativeMatrix(ValidationState_t& _,
return SPV_SUCCESS;
}
spv_result_t ValidateTypeUntypedPointerKHR(ValidationState_t& _,
const Instruction* inst) {
if (spvIsVulkanEnv(_.context()->target_env)) {
const auto sc = inst->GetOperandAs<spv::StorageClass>(1);
switch (sc) {
case spv::StorageClass::Workgroup:
if (!_.HasCapability(
spv::Capability::WorkgroupMemoryExplicitLayoutKHR)) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Workgroup storage class untyped pointers in Vulkan "
"require WorkgroupMemoryExplicitLayoutKHR be declared";
}
break;
case spv::StorageClass::StorageBuffer:
case spv::StorageClass::PhysicalStorageBuffer:
case spv::StorageClass::Uniform:
case spv::StorageClass::PushConstant:
break;
default:
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "In Vulkan, untyped pointers can only be used in an "
"explicitly laid out storage class";
}
}
return SPV_SUCCESS;
}
} // namespace
spv_result_t TypePass(ValidationState_t& _, const Instruction* inst) {
@ -628,6 +656,9 @@ spv_result_t TypePass(ValidationState_t& _, const Instruction* inst) {
case spv::Op::OpTypeCooperativeMatrixKHR:
if (auto error = ValidateTypeCooperativeMatrix(_, inst)) return error;
break;
case spv::Op::OpTypeUntypedPointerKHR:
if (auto error = ValidateTypeUntypedPointerKHR(_, inst)) return error;
break;
default:
break;
}

View File

@ -73,6 +73,7 @@ ModuleLayoutSection InstructionLayoutSection(
case spv::Op::OpTypeForwardPointer:
return kLayoutTypes;
case spv::Op::OpVariable:
case spv::Op::OpUntypedVariableKHR:
if (current_section == kLayoutTypes) return kLayoutTypes;
return kLayoutFunctionDefinitions;
case spv::Op::OpExtInst:
@ -1185,7 +1186,9 @@ bool ValidationState_t::GetStructMemberTypes(
bool ValidationState_t::IsPointerType(uint32_t id) const {
const Instruction* inst = FindDef(id);
return inst && inst->opcode() == spv::Op::OpTypePointer;
assert(inst);
return inst->opcode() == spv::Op::OpTypePointer ||
inst->opcode() == spv::Op::OpTypeUntypedPointerKHR;
}
bool ValidationState_t::GetPointerTypeInfo(
@ -1195,6 +1198,12 @@ bool ValidationState_t::GetPointerTypeInfo(
const Instruction* inst = FindDef(id);
assert(inst);
if (inst->opcode() == spv::Op::OpTypeUntypedPointerKHR) {
*storage_class = spv::StorageClass(inst->word(2));
*data_type = 0;
return true;
}
if (inst->opcode() != spv::Op::OpTypePointer) return false;
*storage_class = spv::StorageClass(inst->word(2));
@ -1705,6 +1714,39 @@ bool ValidationState_t::ContainsRuntimeArray(uint32_t id) const {
return ContainsType(id, f, /* traverse_all_types = */ false);
}
bool ValidationState_t::ContainsUntypedPointer(uint32_t id) const {
const auto inst = FindDef(id);
if (!inst) return false;
if (!spvOpcodeGeneratesType(inst->opcode())) return false;
if (inst->opcode() == spv::Op::OpTypeUntypedPointerKHR) return true;
switch (inst->opcode()) {
case spv::Op::OpTypeArray:
case spv::Op::OpTypeRuntimeArray:
case spv::Op::OpTypeVector:
case spv::Op::OpTypeMatrix:
case spv::Op::OpTypeImage:
case spv::Op::OpTypeSampledImage:
case spv::Op::OpTypeCooperativeMatrixNV:
return ContainsUntypedPointer(inst->GetOperandAs<uint32_t>(1u));
case spv::Op::OpTypePointer:
if (IsForwardPointer(id)) return false;
return ContainsUntypedPointer(inst->GetOperandAs<uint32_t>(2u));
case spv::Op::OpTypeFunction:
case spv::Op::OpTypeStruct: {
for (uint32_t i = 1; i < inst->operands().size(); ++i) {
if (ContainsUntypedPointer(inst->GetOperandAs<uint32_t>(i)))
return true;
}
return false;
}
default:
return false;
}
return false;
}
bool ValidationState_t::IsValidStorageClass(
spv::StorageClass storage_class) const {
if (spvIsVulkanEnv(context()->target_env)) {

View File

@ -649,6 +649,9 @@ class ValidationState_t {
const std::function<bool(const Instruction*)>& f,
bool traverse_all_types = true) const;
// Returns true if |id| is type id that contains an untyped pointer.
bool ContainsUntypedPointer(uint32_t id) const;
// Returns type_id if id has type or zero otherwise.
uint32_t GetTypeId(uint32_t id) const;

View File

@ -61,8 +61,33 @@ INSTANTIATE_TEST_SUITE_P(
ExpectedOpCodeCapabilities{
spv::Op::OpImageSparseSampleImplicitLod,
CapabilitySet{spv::Capability::SparseResidency}},
ExpectedOpCodeCapabilities{spv::Op::OpCopyMemorySized,
CapabilitySet{spv::Capability::Addresses}},
ExpectedOpCodeCapabilities{
spv::Op::OpCopyMemorySized,
CapabilitySet{spv::Capability::Addresses,
spv::Capability::UntypedPointersKHR}},
ExpectedOpCodeCapabilities{spv::Op::OpArrayLength,
CapabilitySet{spv::Capability::Shader}},
ExpectedOpCodeCapabilities{spv::Op::OpFunction, CapabilitySet()},
ExpectedOpCodeCapabilities{spv::Op::OpConvertFToS, CapabilitySet()},
ExpectedOpCodeCapabilities{
spv::Op::OpEmitStreamVertex,
CapabilitySet{spv::Capability::GeometryStreams}},
ExpectedOpCodeCapabilities{
spv::Op::OpTypeNamedBarrier,
CapabilitySet{spv::Capability::NamedBarrier}},
ExpectedOpCodeCapabilities{
spv::Op::OpGetKernelMaxNumSubgroups,
CapabilitySet{spv::Capability::SubgroupDispatch}},
ExpectedOpCodeCapabilities{spv::Op::OpImageQuerySamples,
CapabilitySet{spv::Capability::Kernel,
spv::Capability::ImageQuery}},
ExpectedOpCodeCapabilities{
spv::Op::OpImageSparseSampleImplicitLod,
CapabilitySet{spv::Capability::SparseResidency}},
ExpectedOpCodeCapabilities{
spv::Op::OpCopyMemorySized,
CapabilitySet{spv::Capability::Addresses,
spv::Capability::UntypedPointersKHR}},
ExpectedOpCodeCapabilities{spv::Op::OpArrayLength,
CapabilitySet{spv::Capability::Shader}},
ExpectedOpCodeCapabilities{spv::Op::OpFunction, CapabilitySet()},

View File

@ -1327,5 +1327,54 @@ INSTANTIATE_TEST_SUITE_P(
{1, 2, 3})},
})));
// SPV_KHR_untyped_pointers
INSTANTIATE_TEST_SUITE_P(
SPV_KHR_untyped_pointers, ExtensionRoundTripTest,
Combine(
Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_3, SPV_ENV_VULKAN_1_0,
SPV_ENV_VULKAN_1_1, SPV_ENV_VULKAN_1_2),
ValuesIn(std::vector<AssemblyCase>{
{"OpExtension \"SPV_KHR_untyped_pointers\"\n",
MakeInstruction(spv::Op::OpExtension,
MakeVector("SPV_KHR_untyped_pointers"))},
{"OpCapability UntypedPointersKHR\n",
MakeInstruction(spv::Op::OpCapability,
{(int)spv::Capability::UntypedPointersKHR})},
{"OpCapability UntypedPointersKHR\n",
MakeInstruction(spv::Op::OpCapability, {4473})},
{"%1 = OpTypeUntypedPointerKHR Workgroup\n",
MakeInstruction(spv::Op::OpTypeUntypedPointerKHR,
{1, int(spv::StorageClass::Workgroup)})},
{"%2 = OpUntypedVariableKHR %1 Workgroup %3\n",
MakeInstruction(spv::Op::OpUntypedVariableKHR,
{1, 2, int(spv::StorageClass::Workgroup), 3})},
{"%2 = OpUntypedVariableKHR %1 Workgroup %3 %4\n",
MakeInstruction(spv::Op::OpUntypedVariableKHR,
{1, 2, int(spv::StorageClass::Workgroup), 3, 4})},
{"%2 = OpUntypedAccessChainKHR %1 %3 %4\n",
MakeInstruction(spv::Op::OpUntypedAccessChainKHR, {1, 2, 3, 4})},
{"%2 = OpUntypedAccessChainKHR %1 %3 %4 %5 %6 %7\n",
MakeInstruction(spv::Op::OpUntypedAccessChainKHR,
{1, 2, 3, 4, 5, 6, 7})},
{"%2 = OpUntypedInBoundsAccessChainKHR %1 %3 %4\n",
MakeInstruction(spv::Op::OpUntypedInBoundsAccessChainKHR,
{1, 2, 3, 4})},
{"%2 = OpUntypedInBoundsAccessChainKHR %1 %3 %4 %5 %6 %7\n",
MakeInstruction(spv::Op::OpUntypedInBoundsAccessChainKHR,
{1, 2, 3, 4, 5, 6, 7})},
{"%2 = OpUntypedPtrAccessChainKHR %1 %3 %4 %5\n",
MakeInstruction(spv::Op::OpUntypedPtrAccessChainKHR,
{1, 2, 3, 4, 5})},
{"%2 = OpUntypedPtrAccessChainKHR %1 %3 %4 %5 %6 %7\n",
MakeInstruction(spv::Op::OpUntypedPtrAccessChainKHR,
{1, 2, 3, 4, 5, 6, 7})},
{"%2 = OpUntypedInBoundsPtrAccessChainKHR %1 %3 %4 %5\n",
MakeInstruction(spv::Op::OpUntypedInBoundsPtrAccessChainKHR,
{1, 2, 3, 4, 5})},
{"%2 = OpUntypedInBoundsPtrAccessChainKHR %1 %3 %4 %5 %6 %7\n",
MakeInstruction(spv::Op::OpUntypedInBoundsPtrAccessChainKHR,
{1, 2, 3, 4, 5, 6, 7})},
})));
} // namespace
} // namespace spvtools

View File

@ -230,6 +230,33 @@ OpFunctionEnd
"FPFastMathMode and NoContraction cannot decorate the same target"));
}
TEST_F(DecorationTest, RestrictOnUntypedPointer) {
const std::string text = R"(
OpCapability Shader
OpCapability Linkage
OpCapability UntypedPointersKHR
OpCapability SampleRateShading
OpCapability TransformFeedback
OpCapability GeometryStreams
OpCapability Tessellation
OpExtension "SPV_KHR_untyped_pointers"
OpExtension "SPV_KHR_storage_buffer_storage_class"
OpMemoryModel Logical GLSL450
OpDecorate %param Restrict
%ptr = OpTypeUntypedPointerKHR StorageBuffer
%void = OpTypeVoid
%f_ty = OpTypeFunction %void %ptr
%f = OpFunction %void None %f_ty
%param = OpFunctionParameter %ptr
%entry = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(text);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
}
using MemberOnlyDecorations = spvtest::ValidateBase<std::string>;
TEST_P(MemberOnlyDecorations, MemberDecoration) {

View File

@ -1142,9 +1142,8 @@ OpAtomicStore %f32_1 %device %relaxed %f32_1
CompileSuccessfully(GenerateKernelCode(body));
ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
HasSubstr("AtomicStore: expected Pointer to be of type OpTypePointer"));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("AtomicStore: expected Pointer to be a pointer type"));
}
TEST_F(ValidateAtomics, AtomicStoreWrongPointerDataType) {
@ -1607,7 +1606,7 @@ TEST_F(ValidateAtomics, AtomicFlagTestAndSetNotPointer) {
ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("AtomicFlagTestAndSet: "
"expected Pointer to be of type OpTypePointer"));
"expected Pointer to be a pointer type"));
}
TEST_F(ValidateAtomics, AtomicFlagTestAndSetNotIntPointer) {
@ -1681,7 +1680,7 @@ OpAtomicFlagClear %u32_1 %device %relaxed
ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("AtomicFlagClear: "
"expected Pointer to be of type OpTypePointer"));
"expected Pointer to be a pointer type"));
}
TEST_F(ValidateAtomics, AtomicFlagClearNotIntPointer) {
@ -2847,6 +2846,125 @@ TEST_F(ValidateAtomics, AtomicFloat16Vector3ExchangeFail) {
"float scalar type"));
}
TEST_F(ValidateAtomics, AtomicLoadUntypedPointer) {
const std::string spirv = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpCapability WorkgroupMemoryExplicitLayoutKHR
OpExtension "SPV_KHR_workgroup_memory_explicit_layout"
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main" %var
OpDecorate %struct Block
OpMemberDecorate %struct 0 Offset 0
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_0 = OpConstant %int 0
%int_1 = OpConstant %int 1
%struct = OpTypeStruct %int
%ptr = OpTypeUntypedPointerKHR Workgroup
%var = OpUntypedVariableKHR %ptr Workgroup %struct
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
%load = OpAtomicLoad %int %var %int_1 %int_0
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
}
TEST_F(ValidateAtomics, AtomicStoreUntypedPointer) {
const std::string spirv = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpCapability WorkgroupMemoryExplicitLayoutKHR
OpExtension "SPV_KHR_workgroup_memory_explicit_layout"
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main" %var
OpDecorate %struct Block
OpMemberDecorate %struct 0 Offset 0
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_0 = OpConstant %int 0
%int_1 = OpConstant %int 1
%struct = OpTypeStruct %int
%ptr = OpTypeUntypedPointerKHR Workgroup
%var = OpUntypedVariableKHR %ptr Workgroup %struct
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpAtomicStore %var %int_1 %int_0 %int_0
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
}
TEST_F(ValidateAtomics, AtomicExchangeUntypedPointer) {
const std::string spirv = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpCapability WorkgroupMemoryExplicitLayoutKHR
OpExtension "SPV_KHR_workgroup_memory_explicit_layout"
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main" %var
OpDecorate %struct Block
OpMemberDecorate %struct 0 Offset 0
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_0 = OpConstant %int 0
%int_1 = OpConstant %int 1
%struct = OpTypeStruct %int
%ptr = OpTypeUntypedPointerKHR Workgroup
%var = OpUntypedVariableKHR %ptr Workgroup %struct
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
%ex = OpAtomicExchange %int %var %int_1 %int_0 %int_0
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
}
TEST_F(ValidateAtomics, AtomicFlagClearUntypedPointer) {
const std::string spirv = R"(
OpCapability Kernel
OpCapability Linkage
OpCapability UntypedPointersKHR
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical OpenCL
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_0 = OpConstant %int 0
%int_1 = OpConstant %int 1
%ptr = OpTypeUntypedPointerKHR Workgroup
%var = OpUntypedVariableKHR %ptr Workgroup %int
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpAtomicFlagClear %var %int_1 %int_0
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
HasSubstr(
"Untyped pointers are not supported by atomic flag instructions"));
}
} // namespace
} // namespace val
} // namespace spvtools

View File

@ -1940,6 +1940,64 @@ OpExtension "SPV_KHR_ray_query"
"uint vector as input"));
}
TEST_F(ValidateConversion, BitcastUntypedPointerInput) {
const std::string spirv = R"(
OpCapability Shader
OpCapability VariablePointers
OpCapability UntypedPointersKHR
OpCapability WorkgroupMemoryExplicitLayoutKHR
OpExtension "SPV_KHR_workgroup_memory_explicit_layout"
OpExtension "SPV_KHR_variable_pointers"
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main" %var
OpDecorate %struct Block
OpMemberDecorate %struct 0 Offset 0
%void = OpTypeVoid
%int = OpTypeInt 32 0
%struct = OpTypeStruct %int
%ptr = OpTypeUntypedPointerKHR Workgroup
%var = OpUntypedVariableKHR %ptr Workgroup %struct
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
%cast = OpBitcast %int %var
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
}
TEST_F(ValidateConversion, BitcastUntypedPointerOutput) {
const std::string spirv = R"(
OpCapability Shader
OpCapability VariablePointers
OpCapability UntypedPointersKHR
OpCapability WorkgroupMemoryExplicitLayoutKHR
OpExtension "SPV_KHR_workgroup_memory_explicit_layout"
OpExtension "SPV_KHR_variable_pointers"
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_0 = OpConstant %int 0
%ptr = OpTypeUntypedPointerKHR Workgroup
%var = OpUntypedVariableKHR %ptr Workgroup %int
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
%cast = OpBitcast %ptr %int_0
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
}
using ValidateSmallConversions = spvtest::ValidateBase<std::string>;
CodeGenerator GetSmallConversionsCodeGenerator() {

View File

@ -9362,6 +9362,37 @@ OpFunctionEnd
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_3));
}
TEST_F(ValidateDecorations, UntypedVariableDuplicateInterface) {
const std::string spirv = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpCapability WorkgroupMemoryExplicitLayoutKHR
OpExtension "SPV_KHR_workgroup_memory_explicit_layout"
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main" %var %var
OpName %var "var"
OpDecorate %struct Block
OpMemberDecorate %struct 0 Offset 0
%void = OpTypeVoid
%int = OpTypeInt 32 0
%struct = OpTypeStruct %int
%ptr = OpTypeUntypedPointerKHR Workgroup
%var = OpUntypedVariableKHR %ptr Workgroup %struct
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(
getDiagnosticString(),
HasSubstr("Non-unique OpEntryPoint interface '2[%var]' is disallowed"));
}
TEST_F(ValidateDecorations, PhysicalStorageBufferMissingOffset) {
const std::string spirv = R"(
OpCapability Shader
@ -9967,6 +9998,372 @@ TEST_F(ValidateDecorations, MultipleBuiltinsBlockMixed) {
AnyVUID("VUID-StandaloneSpirv-OpEntryPoint-09659"));
}
TEST_F(ValidateDecorations, UntypedVariableWorkgroupRequiresStruct) {
const std::string spirv = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpCapability WorkgroupMemoryExplicitLayoutKHR
OpExtension "SPV_KHR_untyped_pointers"
OpExtension "SPV_KHR_workgroup_memory_explicit_layout"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main" %var
%void = OpTypeVoid
%int = OpTypeInt 32 0
%ptr = OpTypeUntypedPointerKHR Workgroup
%var = OpUntypedVariableKHR %ptr Workgroup %int
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Untyped workgroup variables in shaders must be block "
"decorated structs"));
}
TEST_F(ValidateDecorations, UntypedVariableWorkgroupRequiresBlockStruct) {
const std::string spirv = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpCapability WorkgroupMemoryExplicitLayoutKHR
OpExtension "SPV_KHR_untyped_pointers"
OpExtension "SPV_KHR_workgroup_memory_explicit_layout"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main" %var
%void = OpTypeVoid
%int = OpTypeInt 32 0
%struct = OpTypeStruct %int
%ptr = OpTypeUntypedPointerKHR Workgroup
%var = OpUntypedVariableKHR %ptr Workgroup %struct
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Untyped workgroup variables in shaders must be block "
"decorated"));
}
TEST_F(ValidateDecorations, UntypedVariableStorageBufferMissingBlock) {
const std::string spirv = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpExtension "SPV_KHR_untyped_pointers"
OpExtension "SPV_KHR_storage_buffer_storage_class"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
OpName %struct "struct"
%void = OpTypeVoid
%int = OpTypeInt 32 0
%struct = OpTypeStruct %int
%ptr = OpTypeUntypedPointerKHR StorageBuffer
%var = OpUntypedVariableKHR %ptr StorageBuffer %struct
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("StorageBuffer id '2' is missing Block decoration"));
}
TEST_F(ValidateDecorations, UntypedVariableUniformMissingBlock) {
const std::string spirv = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
OpName %struct "struct"
%void = OpTypeVoid
%int = OpTypeInt 32 0
%struct = OpTypeStruct %int
%ptr = OpTypeUntypedPointerKHR Uniform
%var = OpUntypedVariableKHR %ptr Uniform %struct
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
EXPECT_THAT(
getDiagnosticString(),
HasSubstr("Uniform id '2' is missing Block or BufferBlock decoration"));
}
TEST_F(ValidateDecorations, UntypedVariablePushConstantMissingBlock) {
const std::string spirv = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
OpName %struct "struct"
%void = OpTypeVoid
%int = OpTypeInt 32 0
%struct = OpTypeStruct %int
%ptr = OpTypeUntypedPointerKHR PushConstant
%var = OpUntypedVariableKHR %ptr PushConstant %struct
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("PushConstant id '2' is missing Block decoration"));
}
using UntypedVariableSetAndBinding = spvtest::ValidateBase<std::string>;
TEST_P(UntypedVariableSetAndBinding, MissingSet) {
const auto sc = GetParam();
const std::string spirv = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpExtension "SPV_KHR_untyped_pointers"
OpExtension "SPV_KHR_storage_buffer_storage_class"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
OpName %var "var"
OpDecorate %struct Block
OpMemberDecorate %struct 0 Offset 0
OpDecorate %var Binding 0
%void = OpTypeVoid
%int = OpTypeInt 32 0
%struct = OpTypeStruct %int
%ptr = OpTypeUntypedPointerKHR )" +
sc + R"(
%var = OpUntypedVariableKHR %ptr )" + sc + R"( %struct
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
%load = OpLoad %struct %var
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
EXPECT_THAT(getDiagnosticString(),
HasSubstr(sc + " id '2' is missing DescriptorSet decoration"));
}
TEST_P(UntypedVariableSetAndBinding, MissingBinding) {
const auto sc = GetParam();
const std::string spirv = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpExtension "SPV_KHR_untyped_pointers"
OpExtension "SPV_KHR_storage_buffer_storage_class"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
OpName %var "var"
OpDecorate %struct Block
OpMemberDecorate %struct 0 Offset 0
OpDecorate %var DescriptorSet 0
%void = OpTypeVoid
%int = OpTypeInt 32 0
%struct = OpTypeStruct %int
%ptr = OpTypeUntypedPointerKHR )" +
sc + R"(
%var = OpUntypedVariableKHR %ptr )" + sc + R"( %struct
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
%load = OpLoad %struct %var
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
EXPECT_THAT(getDiagnosticString(),
HasSubstr(sc + " id '2' is missing Binding decoration"));
}
INSTANTIATE_TEST_SUITE_P(ValidateUntypedVariableSetAndBinding,
UntypedVariableSetAndBinding,
Values("StorageBuffer", "Uniform"));
using UntypedPointerLayout =
spvtest::ValidateBase<std::tuple<std::string, std::string>>;
TEST_P(UntypedPointerLayout, BadOffset) {
const auto sc = std::get<0>(GetParam());
const auto op = std::get<1>(GetParam());
const std::string set = (sc == "StorageBuffer" || sc == "Uniform"
? R"(OpDecorate %var DescriptorSet 0
OpDecorate %var Binding 0
)"
: R"()");
const std::string spirv = R"(
OpCapability Shader
OpCapability VariablePointers
OpCapability UntypedPointersKHR
OpCapability WorkgroupMemoryExplicitLayoutKHR
OpExtension "SPV_KHR_untyped_pointers"
OpExtension "SPV_KHR_variable_pointers"
OpExtension "SPV_KHR_workgroup_memory_explicit_layout"
OpExtension "SPV_KHR_storage_buffer_storage_class"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main" %var
OpExecutionMode %main LocalSize 1 1 1
OpName %var "var"
OpDecorate %struct Block
OpMemberDecorate %struct 0 Offset 0
OpMemberDecorate %struct 1 Offset 4
)" + set + R"(OpMemberDecorate %test_type 0 Offset 0
OpMemberDecorate %test_type 1 Offset 1
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_0 = OpConstant %int 0
%struct = OpTypeStruct %int %int
%test_type = OpTypeStruct %int %int
%test_val = OpConstantNull %test_type
%ptr = OpTypeUntypedPointerKHR )" +
sc + R"(
%var = OpUntypedVariableKHR %ptr )" + sc + R"( %struct
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
)" + op + R"(
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_2);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_2));
const bool read_only = sc == "Uniform" || sc == "PushConstant";
if (!read_only || op.find("OpStore") == std::string::npos) {
EXPECT_THAT(getDiagnosticString(),
HasSubstr("member 1 at offset 1 is not aligned to"));
}
}
TEST_P(UntypedPointerLayout, BadStride) {
const auto sc = std::get<0>(GetParam());
const auto op = std::get<1>(GetParam());
const std::string set = (sc == "StorageBuffer" || sc == "Uniform"
? R"(OpDecorate %var DescriptorSet 0
OpDecorate %var Binding 0
)"
: R"()");
const std::string spirv = R"(
OpCapability Shader
OpCapability VariablePointers
OpCapability UntypedPointersKHR
OpCapability WorkgroupMemoryExplicitLayoutKHR
OpExtension "SPV_KHR_untyped_pointers"
OpExtension "SPV_KHR_variable_pointers"
OpExtension "SPV_KHR_workgroup_memory_explicit_layout"
OpExtension "SPV_KHR_storage_buffer_storage_class"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main" %var
OpExecutionMode %main LocalSize 1 1 1
OpName %var "var"
OpDecorate %struct Block
OpMemberDecorate %struct 0 Offset 0
OpMemberDecorate %struct 1 Offset 4
)" + set + R"(OpDecorate %test_type ArrayStride 4
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_0 = OpConstant %int 0
%int_4 = OpConstant %int 4
%int4 = OpTypeVector %int 4
%test_type = OpTypeArray %int4 %int_4
%test_val = OpConstantNull %test_type
%struct = OpTypeStruct %int %int
%ptr = OpTypeUntypedPointerKHR )" +
sc + R"(
%var = OpUntypedVariableKHR %ptr )" + sc + R"( %struct
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
)" + op + R"(
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_2);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_2));
const bool read_only = sc == "Uniform" || sc == "PushConstant";
if (!read_only || op.find("OpStore") == std::string::npos) {
EXPECT_THAT(
getDiagnosticString(),
HasSubstr("array with stride 4 not satisfying alignment to 16"));
}
}
INSTANTIATE_TEST_SUITE_P(
ValidateUntypedPointerLayout, UntypedPointerLayout,
Combine(Values("StorageBuffer", "Uniform", "PushConstant", "Workgroup"),
Values("%gep = OpUntypedAccessChainKHR %ptr %test_type %var %int_0",
"%gep = OpUntypedInBoundsAccessChainKHR %ptr %test_type "
"%var %int_0",
"%gep = OpUntypedPtrAccessChainKHR %ptr %test_type %var "
"%int_0 %int_0",
"%ld = OpLoad %test_type %var", "OpStore %var %test_val")));
TEST_F(ValidateDecorations, UntypedArrayLengthMissingOffset) {
const std::string spirv = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
OpDecorate %struct Block
OpDecorate %block Block
OpMemberDecorate %block 0 Offset 0
OpDecorate %array ArrayStride 4
OpDecorate %var DescriptorSet 0
OpDecorate %var Binding 0
%void = OpTypeVoid
%int = OpTypeInt 32 0
%array = OpTypeRuntimeArray %int
%struct = OpTypeStruct %array
%block = OpTypeStruct %array
%ptr = OpTypeUntypedPointerKHR StorageBuffer
%var = OpUntypedVariableKHR %ptr StorageBuffer %block
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
%len = OpUntypedArrayLengthKHR %int %struct %var 0
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_2);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_2));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("member 0 is missing an Offset decoration"));
}
} // namespace
} // namespace val
} // namespace spvtools

View File

@ -0,0 +1,110 @@
// Copyright (c) 2021 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Tests for OpExtension validator rules.
#include <string>
#include <vector>
#include "gmock/gmock.h"
#include "source/enum_string_mapping.h"
#include "source/extensions.h"
#include "source/spirv_target_env.h"
#include "test/test_fixture.h"
#include "test/unit_spirv.h"
#include "test/val/val_fixtures.h"
namespace spvtools {
namespace val {
namespace {
using ::testing::HasSubstr;
using ::testing::Values;
using ::testing::ValuesIn;
using ValidateSpvKHRSubgroupUniformControlFlow = spvtest::ValidateBase<bool>;
TEST_F(ValidateSpvKHRSubgroupUniformControlFlow, Valid) {
const std::string str = R"(
OpCapability Shader
OpExtension "SPV_KHR_subgroup_uniform_control_flow"
OpMemoryModel Logical Simple
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
OpExecutionMode %main SubgroupUniformControlFlowKHR
%void = OpTypeVoid
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(str.c_str());
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
}
TEST_F(ValidateSpvKHRSubgroupUniformControlFlow, RequiresExtension) {
const std::string str = R"(
OpCapability Shader
OpMemoryModel Logical Simple
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
OpExecutionMode %main SubgroupUniformControlFlowKHR
%void = OpTypeVoid
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(str.c_str());
EXPECT_NE(SPV_SUCCESS, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("2nd operand of ExecutionMode: operand "
"SubgroupUniformControlFlowKHR(4421) "
"requires one of these extensions: "
"SPV_KHR_subgroup_uniform_control_flow"));
}
TEST_F(ValidateSpvKHRSubgroupUniformControlFlow, RequiresShaderCapability) {
const std::string str = R"(
OpCapability Kernel
OpCapability Addresses
OpExtension "SPV_KHR_subgroup_uniform_control_flow"
OpMemoryModel Physical32 OpenCL
OpEntryPoint Kernel %main "main"
OpExecutionMode %main SubgroupUniformControlFlowKHR
%void = OpTypeVoid
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(str.c_str());
EXPECT_NE(SPV_SUCCESS, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Operand 2 of ExecutionMode requires one of these "
"capabilities: Shader"));
}
} // namespace
} // namespace val
} // namespace spvtools

View File

@ -76,6 +76,8 @@ class ValidateBase : public ::testing::Test,
diagnostic_ = nullptr;
}
void SetAssembleOptions(uint32_t options) { assemble_options_ = options; }
std::string getDiagnosticString();
spv_position_t getErrorPosition();
spv_validator_options getValidatorOptions();
@ -84,6 +86,7 @@ class ValidateBase : public ::testing::Test,
spv_diagnostic diagnostic_;
spv_validator_options options_;
std::unique_ptr<spvtools::val::ValidationState_t> vstate_;
uint32_t assemble_options_ = SPV_TEXT_TO_BINARY_OPTION_NONE;
};
template <typename T>
@ -132,8 +135,9 @@ void ValidateBase<T>::CompileSuccessfully(std::string code,
DestroyBinary();
spv_diagnostic diagnostic = nullptr;
ScopedContext context(env);
auto status = spvTextToBinary(context.context, code.c_str(), code.size(),
&binary_, &diagnostic);
auto status =
spvTextToBinaryWithOptions(context.context, code.c_str(), code.size(),
assemble_options_, &binary_, &diagnostic);
EXPECT_EQ(SPV_SUCCESS, status)
<< "ERROR: " << diagnostic->error
<< "\nSPIR-V could not be compiled into binary:\n"

View File

@ -836,6 +836,113 @@ TEST_F(ValidateFunctionCall, LogicallyMismatchedPointersArraySize) {
HasSubstr("type does not match Function <id>"));
}
TEST_F(ValidateFunctionCall, UntypedPointerParameterMismatch) {
const std::string spirv = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpExtension "SPV_KHR_untyped_pointers"
OpExtension "SPV_KHR_storage_buffer_storage_class"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpName %var "var"
OpName %ptr2 "ptr2"
%void = OpTypeVoid
%int = OpTypeInt 32 0
%ptr = OpTypeUntypedPointerKHR Private
%ptr2 = OpTypeUntypedPointerKHR Private
%var = OpUntypedVariableKHR %ptr Private %int
%void_fn = OpTypeFunction %void
%ptr_fn = OpTypeFunction %void %ptr2
%foo = OpFunction %void None %ptr_fn
%param = OpFunctionParameter %ptr2
%first = OpLabel
OpReturn
OpFunctionEnd
%main = OpFunction %void None %void_fn
%entry = OpLabel
%call = OpFunctionCall %void %foo %var
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("OpFunctionCall Argument <id> '2[%var]'s type does not "
"match Function <id> '3[%ptr2]'s parameter type"));
}
TEST_F(ValidateFunctionCall, UntypedPointerParameterGood) {
const std::string spirv = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpExtension "SPV_KHR_untyped_pointers"
OpExtension "SPV_KHR_storage_buffer_storage_class"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpName %var "var"
%void = OpTypeVoid
%int = OpTypeInt 32 0
%ptr = OpTypeUntypedPointerKHR Private
%var = OpUntypedVariableKHR %ptr Private %int
%void_fn = OpTypeFunction %void
%ptr_fn = OpTypeFunction %void %ptr
%foo = OpFunction %void None %ptr_fn
%param = OpFunctionParameter %ptr
%first = OpLabel
OpReturn
OpFunctionEnd
%main = OpFunction %void None %void_fn
%entry = OpLabel
%call = OpFunctionCall %void %foo %var
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
}
TEST_F(ValidateFunctionCall,
UntypedPointerParameterNotMemoryObjectDeclaration) {
const std::string spirv = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpExtension "SPV_KHR_untyped_pointers"
OpExtension "SPV_KHR_storage_buffer_storage_class"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpName %var "var"
OpName %gep "gep"
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_0 = OpConstant %int 0
%struct = OpTypeStruct %int
%ptr = OpTypeUntypedPointerKHR Private
%var = OpUntypedVariableKHR %ptr Private %int
%void_fn = OpTypeFunction %void
%ptr_fn = OpTypeFunction %void %ptr
%foo = OpFunction %void None %ptr_fn
%param = OpFunctionParameter %ptr
%first = OpLabel
OpReturn
OpFunctionEnd
%main = OpFunction %void None %void_fn
%entry = OpLabel
%gep = OpUntypedAccessChainKHR %ptr %struct %var %int_0
%call = OpFunctionCall %void %foo %gep
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
HasSubstr(
"Pointer operand '3[%gep]' must be a memory object declaration"));
}
INSTANTIATE_TEST_SUITE_P(StorageClass, ValidateFunctionCall,
Values("UniformConstant", "Input", "Uniform", "Output",
"Workgroup", "Private", "Function",

View File

@ -578,9 +578,8 @@ TEST_P(ValidateIdWithMessage, OpEntryPointInterfaceIsNotVariableTypeBad) {
CompileSuccessfully(spirv);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr(make_message(
"Interfaces passed to OpEntryPoint must be of type "
"OpTypeVariable. Found OpTypePointer.")));
HasSubstr("Interfaces passed to OpEntryPoint must be variables. "
"Found OpTypePointer."));
}
TEST_P(ValidateIdWithMessage, OpEntryPointInterfaceStorageClassBad) {
@ -1167,6 +1166,160 @@ TEST_P(ValidateIdWithMessage, OpTypePointerBad) {
"type.")));
}
TEST_P(ValidateIdWithMessage, OpTypePointerCanHaveUntypedPointer) {
const std::string spirv = R"(
OpCapability Kernel
OpCapability Linkage
OpCapability UntypedPointersKHR
OpCapability WorkgroupMemoryExplicitLayoutKHR
OpExtension "SPV_KHR_workgroup_memory_explicit_layout"
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical OpenCL
%ptr = OpTypeUntypedPointerKHR Workgroup
%ptr2 = OpTypePointer Private %ptr
)";
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
}
TEST_P(ValidateIdWithMessage, OpTypeUntypedPointerWorkgroupGood) {
const std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpCapability UntypedPointersKHR
OpCapability WorkgroupMemoryExplicitLayoutKHR
OpExtension "SPV_KHR_workgroup_memory_explicit_layout"
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
%ptr = OpTypeUntypedPointerKHR Workgroup
)";
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
}
TEST_P(ValidateIdWithMessage,
OpTypeUntypedPointerWorkgroupMissingExplicitLayout) {
const std::string spirv = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
%ptr = OpTypeUntypedPointerKHR Workgroup
%void = OpTypeVoid
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
EXPECT_THAT(
getDiagnosticString(),
HasSubstr("Workgroup storage class untyped pointers in Vulkan require "
"WorkgroupMemoryExplicitLayoutKHR be declared"));
}
TEST_P(ValidateIdWithMessage, OpTypeUntypedPointerWorkgroupGoodAll) {
const std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpCapability UntypedPointersKHR
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
%ptr = OpTypeUntypedPointerKHR Workgroup
)";
CompileSuccessfully(spirv);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
}
TEST_P(ValidateIdWithMessage, OpTypeUntypedPointerStorageBufferGood) {
const std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpCapability UntypedPointersKHR
OpExtension "SPV_KHR_storage_buffer_storage_class"
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
%ptr = OpTypeUntypedPointerKHR StorageBuffer
)";
CompileSuccessfully(spirv);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
}
TEST_P(ValidateIdWithMessage, OpTypeUntypedPointerUniformGood) {
const std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpCapability UntypedPointersKHR
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
%ptr = OpTypeUntypedPointerKHR Uniform
)";
CompileSuccessfully(spirv);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
}
TEST_P(ValidateIdWithMessage, OpTypeUntypedPointerPushConstantGood) {
const std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpCapability UntypedPointersKHR
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
%ptr = OpTypeUntypedPointerKHR PushConstant
)";
CompileSuccessfully(spirv);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
}
TEST_P(ValidateIdWithMessage, OpTypeUntypedPointerCrossWorkgroupGood) {
const std::string spirv = R"(
OpCapability Kernel
OpCapability Linkage
OpCapability UntypedPointersKHR
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical OpenCL
%ptr = OpTypeUntypedPointerKHR CrossWorkgroup
)";
CompileSuccessfully(spirv);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
}
TEST_P(ValidateIdWithMessage, OpTypeUntypedPointerVulkanInvalidStorageClass) {
const std::string spirv = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
%void = OpTypeVoid
%void_fn = OpTypeFunction %void
%ptr = OpTypeUntypedPointerKHR Private
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("In Vulkan, untyped pointers can only be used in an "
"explicitly laid out storage class"));
}
TEST_P(ValidateIdWithMessage, OpTypeFunctionGood) {
std::string spirv = kGLSL450MemoryModel + R"(
%1 = OpTypeVoid
@ -2270,9 +2423,8 @@ OpFunctionEnd
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr(make_message(
"OpVariable Initializer <id> '8[%8]' is not a constant "
"or module-scope variable")));
HasSubstr("Variable Initializer <id> '8[%8]' is not a constant "
"or module-scope variable"));
}
TEST_P(ValidateIdWithMessage, OpVariableInitializerIsModuleVarGood) {
@ -6404,9 +6556,10 @@ OpMemoryModel Logical VulkanKHR
%7 = OpConstant %2 2
%8 = OpConstant %2 5
%9 = OpTypeFunction %1
%12 = OpConstant %2 4
%10 = OpFunction %1 None %9
%11 = OpLabel
OpCopyMemorySized %4 %6 %7 NonPrivatePointerKHR|MakePointerAvailableKHR %7
OpCopyMemorySized %4 %6 %12 NonPrivatePointerKHR|MakePointerAvailableKHR %7
OpReturn
OpFunctionEnd
)";
@ -6431,10 +6584,11 @@ OpMemoryModel Logical VulkanKHR
%6 = OpVariable %5 Uniform
%7 = OpConstant %2 2
%8 = OpConstant %2 5
%12 = OpConstant %2 4
%9 = OpTypeFunction %1
%10 = OpFunction %1 None %9
%11 = OpLabel
OpCopyMemorySized %4 %6 %7 NonPrivatePointerKHR|MakePointerVisibleKHR %8
OpCopyMemorySized %4 %6 %12 NonPrivatePointerKHR|MakePointerVisibleKHR %8
OpReturn
OpFunctionEnd
)";
@ -6460,10 +6614,11 @@ OpMemoryModel Logical VulkanKHR
%6 = OpVariable %5 Uniform
%7 = OpConstant %2 2
%8 = OpConstant %2 5
%12 = OpConstant %2 4
%9 = OpTypeFunction %1
%10 = OpFunction %1 None %9
%11 = OpLabel
OpCopyMemorySized %4 %6 %7 NonPrivatePointerKHR|MakePointerAvailableKHR|MakePointerVisibleKHR %7 %8
OpCopyMemorySized %4 %6 %12 NonPrivatePointerKHR|MakePointerAvailableKHR|MakePointerVisibleKHR %7 %8
OpReturn
OpFunctionEnd
)";
@ -6489,10 +6644,11 @@ OpMemoryModel Logical VulkanKHR
%6 = OpVariable %5 Uniform
%7 = OpConstant %2 2
%8 = OpConstant %2 5
%12 = OpConstant %2 4
%9 = OpTypeFunction %1
%10 = OpFunction %1 None %9
%11 = OpLabel
OpCopyMemorySized %4 %6 %7 MakePointerAvailableKHR %7
OpCopyMemorySized %4 %6 %12 MakePointerAvailableKHR %7
OpReturn
OpFunctionEnd
)";
@ -6522,10 +6678,11 @@ OpMemoryModel Logical VulkanKHR
%6 = OpVariable %5 Uniform
%7 = OpConstant %2 2
%8 = OpConstant %2 5
%12 = OpConstant %2 4
%9 = OpTypeFunction %1
%10 = OpFunction %1 None %9
%11 = OpLabel
OpCopyMemorySized %4 %6 %7 MakePointerVisibleKHR %8
OpCopyMemorySized %4 %6 %12 MakePointerVisibleKHR %8
OpReturn
OpFunctionEnd
)";
@ -6555,10 +6712,11 @@ OpMemoryModel Logical VulkanKHR
%6 = OpVariable %5 Uniform
%7 = OpConstant %2 2
%8 = OpConstant %2 5
%12 = OpConstant %2 4
%9 = OpTypeFunction %1
%10 = OpFunction %1 None %9
%11 = OpLabel
OpCopyMemorySized %4 %6 %7 NonPrivatePointerKHR
OpCopyMemorySized %4 %6 %12 NonPrivatePointerKHR
OpReturn
OpFunctionEnd
)";
@ -6589,10 +6747,11 @@ OpMemoryModel Logical VulkanKHR
%6 = OpVariable %5 Input
%7 = OpConstant %2 2
%8 = OpConstant %2 5
%12 = OpConstant %2 4
%9 = OpTypeFunction %1
%10 = OpFunction %1 None %9
%11 = OpLabel
OpCopyMemorySized %4 %6 %7 NonPrivatePointerKHR
OpCopyMemorySized %4 %6 %12 NonPrivatePointerKHR
OpReturn
OpFunctionEnd
)";

View File

@ -1691,6 +1691,122 @@ OpFunctionEnd
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_3));
}
TEST_F(ValidateInterfacesTest, UntypedVariableInputMissing) {
const std::string text = R"(
OpCapability Kernel
OpCapability UntypedPointersKHR
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical OpenCL
OpEntryPoint Kernel %main "main"
OpExecutionMode %main LocalSize 1 1 1
OpName %var "var"
OpDecorate %var BuiltIn LocalInvocationId
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int3 = OpTypeVector %int 3
%ptr = OpTypeUntypedPointerKHR Input
%var = OpUntypedVariableKHR %ptr Input %int3
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
%load = OpLoad %int3 %var
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(text);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Interface variable id <2> is used by entry point "
"'main' id <1>, but is not listed as an interface"));
}
TEST_F(ValidateInterfacesTest, UntypedVariableWorkgroupMissingSpv1p4) {
const std::string text = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpCapability WorkgroupMemoryExplicitLayoutKHR
OpExtension "SPV_KHR_workgroup_memory_explicit_layout"
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpName %var "var"
%void = OpTypeVoid
%int = OpTypeInt 32 0
%ptr = OpTypeUntypedPointerKHR Workgroup
%var = OpUntypedVariableKHR %ptr Workgroup %int
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
%load = OpLoad %int %var
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Interface variable id <2> is used by entry point "
"'main' id <1>, but is not listed as an interface"));
}
TEST_F(ValidateInterfacesTest, UntypedIdMatchesInputVulkan1p3) {
const std::string text = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main" %var
OpExecutionMode %main OriginUpperLeft
OpDecorate %var DescriptorSet 0
OpDecorate %var Binding 0
OpDecorate %1 Block
OpMemberDecorate %1 0 Offset 0
%void = OpTypeVoid
%float = OpTypeFloat 32
%1 = OpTypeStruct %float ; this id matches Input storage class
%ptr = OpTypeUntypedPointerKHR Uniform
%var = OpUntypedVariableKHR %ptr Uniform %1
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpReturn
OpFunctionEnd
)";
SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
CompileSuccessfully(text, SPV_ENV_VULKAN_1_3);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_3));
}
TEST_F(ValidateInterfacesTest, UntypedIdMatchesPushConstantVulkan1p3) {
const std::string text = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main" %var
OpExecutionMode %main OriginUpperLeft
OpDecorate %var DescriptorSet 0
OpDecorate %var Binding 0
OpDecorate %9 Block
OpMemberDecorate %9 0 Offset 0
%void = OpTypeVoid
%float = OpTypeFloat 32
%9 = OpTypeStruct %float ; this id matches PushConstant storage class
%ptr = OpTypeUntypedPointerKHR Uniform
%var = OpUntypedVariableKHR %ptr Uniform %9
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpReturn
OpFunctionEnd
)";
SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
CompileSuccessfully(text, SPV_ENV_VULKAN_1_3);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_3));
}
} // namespace
} // namespace val
} // namespace spvtools

File diff suppressed because it is too large Load Diff

View File

@ -165,7 +165,7 @@ TEST_F(ValidateStorage, GenericVariableOutsideFunction) {
CompileSuccessfully(str);
ASSERT_EQ(SPV_ERROR_INVALID_BINARY, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("OpVariable storage class cannot be Generic"));
HasSubstr("Variable storage class cannot be Generic"));
}
TEST_F(ValidateStorage, GenericVariableInsideFunction) {
@ -187,7 +187,7 @@ TEST_F(ValidateStorage, GenericVariableInsideFunction) {
CompileSuccessfully(str);
EXPECT_EQ(SPV_ERROR_INVALID_BINARY, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("OpVariable storage class cannot be Generic"));
HasSubstr("Variable storage class cannot be Generic"));
}
TEST_F(ValidateStorage, RelaxedLogicalPointerFunctionParam) {

View File

@ -270,6 +270,24 @@ OpMemoryModel Logical GLSL450
Not(HasSubstr(GetErrorString(spv::Op::OpTypePointer))));
}
TEST_F(ValidateTypeUnique, DuplicateUntypedPointer) {
std::string str = R"(
OpCapability Shader
OpCapability Linkage
OpCapability UntypedPointersKHR
OpCapability WorkgroupMemoryExplicitLayoutKHR
OpExtension "SPV_KHR_workgroup_memory_explicit_layout"
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
%u32 = OpTypeInt 32 0
%ptr1 = OpTypeUntypedPointerKHR Workgroup
%ptr2 = OpTypeUntypedPointerKHR Workgroup
)";
CompileSuccessfully(str.c_str(), SPV_ENV_UNIVERSAL_1_4);
ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
}
} // namespace
} // namespace val
} // namespace spvtools