From c746442d56369643218aab959ce5b90517e43342 Mon Sep 17 00:00:00 2001 From: David Neto Date: Thu, 21 May 2020 18:44:28 -0400 Subject: [PATCH] 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 --- source/name_mapper.cpp | 5 + source/opcode.cpp | 21 + source/opcode.h | 3 + source/val/validate_adjacency.cpp | 9 + source/val/validate_annotation.cpp | 10 +- source/val/validate_atomics.cpp | 39 +- source/val/validate_builtins.cpp | 4 + source/val/validate_cfg.cpp | 3 +- source/val/validate_constants.cpp | 1 + source/val/validate_decorations.cpp | 371 ++-- source/val/validate_function.cpp | 14 +- source/val/validate_id.cpp | 4 + source/val/validate_image.cpp | 29 +- source/val/validate_interfaces.cpp | 14 +- source/val/validate_logicals.cpp | 3 +- source/val/validate_memory.cpp | 508 ++++-- source/val/validate_type.cpp | 30 + source/val/validation_state.cpp | 47 +- source/val/validation_state.h | 3 + test/opcode_require_capabilities_test.cpp | 29 +- test/text_to_binary.extension_test.cpp | 47 + test/val/val_annotation_test.cpp | 27 + test/val/val_atomics_test.cpp | 128 +- test/val/val_conversion_test.cpp | 58 + test/val/val_decoration_test.cpp | 392 ++++ ..._spv_khr_subgroup_uniform_control_flow.cpp | 110 ++ test/val/val_fixtures.h | 10 +- test/val/val_function_test.cpp | 105 ++ test/val/val_id_test.cpp | 184 +- test/val/val_interfaces_test.cpp | 116 ++ test/val/val_memory_test.cpp | 1614 ++++++++++++++++- test/val/val_storage_test.cpp | 4 +- test/val/val_type_unique_test.cpp | 18 + 33 files changed, 3632 insertions(+), 328 deletions(-) create mode 100644 test/val/val_extension_spv_khr_subgroup_uniform_control_flow.cpp diff --git a/source/name_mapper.cpp b/source/name_mapper.cpp index bc18b7f1b4..b0debde9d2 100644 --- a/source/name_mapper.cpp +++ b/source/name_mapper.cpp @@ -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") + diff --git a/source/opcode.cpp b/source/opcode.cpp index 5076bbddc2..c4ec94e4b1 100644 --- a/source/opcode.cpp +++ b/source/opcode.cpp @@ -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; + } +} diff --git a/source/opcode.h b/source/opcode.h index cecd566330..08fc56d8a0 100644 --- a/source/opcode.h +++ b/source/opcode.h @@ -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_ diff --git a/source/val/validate_adjacency.cpp b/source/val/validate_adjacency.cpp index e6b00424d4..52519bfa9f 100644 --- a/source/val/validate_adjacency.cpp +++ b/source/val/validate_adjacency.cpp @@ -117,6 +117,15 @@ spv_result_t ValidateAdjacency(ValidationState_t& _) { "first instructions in the first block."; } break; + case spv::Op::OpUntypedVariableKHR: + if (inst.GetOperandAs(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; diff --git a/source/val/validate_annotation.cpp b/source/val/validate_annotation.cpp index dac3585788..dfafaaa855 100644 --- a/source/val/validate_annotation.cpp +++ b/source/val/validate_annotation.cpp @@ -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; diff --git a/source/val/validate_atomics.cpp b/source/val/validate_atomics.cpp index 8ddef17896..990ed31518 100644 --- a/source/val/validate_atomics.cpp +++ b/source/val/validate_atomics.cpp @@ -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(3))->type_id(); + break; + default: + break; + } } // Can't use result_type because OpAtomicStore doesn't have a result diff --git a/source/val/validate_builtins.cpp b/source/val/validate_builtins.cpp index 9e307fcb14..1305dc1f84 100644 --- a/source/val/validate_builtins.cpp +++ b/source/val/validate_builtins.cpp @@ -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)); } diff --git a/source/val/validate_cfg.cpp b/source/val/validate_cfg.cpp index 9b7161fc44..d401a2e7b7 100644 --- a/source/val/validate_cfg.cpp +++ b/source/val/validate_cfg.cpp @@ -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 " diff --git a/source/val/validate_constants.cpp b/source/val/validate_constants.cpp index 4deaa49688..1d40eedf91 100644 --- a/source/val/validate_constants.cpp +++ b/source/val/validate_constants.cpp @@ -324,6 +324,7 @@ bool IsTypeNullable(const std::vector& instruction, } return true; } + case spv::Op::OpTypeUntypedPointerKHR: case spv::Op::OpTypePointer: if (spv::StorageClass(instruction[2]) == spv::StorageClass::PhysicalStorageBuffer) { diff --git a/source/val/validate_decorations.cpp b/source/val/validate_decorations.cpp index 7364cab7ae..0d2ad10462 100644 --- a/source/val/validate_decorations.cpp +++ b/source/val/validate_decorations.cpp @@ -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 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 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,17 @@ spv_result_t CheckDecorationsOfEntryPoints(ValidationState_t& vstate) { std::unordered_set 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(2); + var_instr->GetOperandAs(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 +810,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 +881,23 @@ 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 +978,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 +1110,16 @@ 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(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 +1198,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(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(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 +1285,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 (!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 ((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::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 (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 (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 (!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, + 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,20 +1378,99 @@ spv_result_t CheckDecorationsOfBuffers(ValidationState_t& vstate) { } else if (type_inst && type_inst->opcode() == spv::Op::OpTypePointer && type_inst->GetOperandAs(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(2u); - const auto* data_type_inst = vstate.FindDef(data_type_id); + const auto pointee_type_id = type_inst->GetOperandAs(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, data_type_id, + ComputeMemberConstraintsForStruct(&constraints, pointee_type_id, LayoutConstraints(), vstate); } - if (auto res = checkLayout(data_type_id, "PhysicalStorageBuffer", "Block", + 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(2); + break; + case spv::Op::OpLoad: + if (vstate.GetIdOpcode(vstate.GetOperandTypeId(&inst, 2)) == + spv::Op::OpTypeUntypedPointerKHR) { + const auto ptr_id = inst.GetOperandAs(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(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(3))->type_id(); + data_type_id = inst.GetOperandAs(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(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_id = inst.GetOperandAs(2); + 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); + } + 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; + } } } return SPV_SUCCESS; @@ -1585,15 +1702,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(2) - : spv::StorageClass::Max; + const auto var_storage_class = + opcode == spv::Op::OpVariable + ? inst.GetOperandAs(2) + : opcode == spv::Op::OpUntypedVariableKHR + ? inst.GetOperandAs(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) { diff --git a/source/val/validate_function.cpp b/source/val/validate_function.cpp index 639817fef4..3780a4b39b 100644 --- a/source/val/validate_function.cpp +++ b/source/val/validate_function.cpp @@ -156,7 +156,8 @@ spv_result_t ValidateFunctionParameter(ValidationState_t& _, param_nonarray_type_id = _.FindDef(param_nonarray_type_id)->GetOperandAs(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(1u) == spv::StorageClass::PhysicalStorageBuffer) { @@ -185,7 +186,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(2); const auto pointee_type = _.FindDef(pointee_type_id); @@ -288,7 +289,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(1u); @@ -317,9 +319,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; diff --git a/source/val/validate_id.cpp b/source/val/validate_id.cpp index 23512125d0..0d1e84123e 100644 --- a/source/val/validate_id.cpp +++ b/source/val/validate_id.cpp @@ -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 || diff --git a/source/val/validate_image.cpp b/source/val/validate_image.cpp index 9af97b79b7..27b8728bd1 100644 --- a/source/val/validate_image.cpp +++ b/source/val/validate_image.cpp @@ -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,19 @@ spv_result_t ValidateImageTexelPointer(ValidationState_t& _, "operand is Image"; } - const auto ptr_type = result_type->GetOperandAs(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(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 +1167,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 && diff --git a/source/val/validate_interfaces.cpp b/source/val/validate_interfaces.cpp index 8f10b9d07f..817ce35a23 100644 --- a/source/val/validate_interfaces.cpp +++ b/source/val/validate_interfaces.cpp @@ -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(2u) != spv::StorageClass::Function; } else { - return inst->opcode() == spv::Op::OpVariable && + return (inst->opcode() == spv::Op::OpVariable || + inst->opcode() == spv::Op::OpUntypedVariableKHR) && (inst->GetOperandAs(2u) == spv::StorageClass::Input || inst->GetOperandAs(2u) == @@ -242,8 +244,9 @@ spv_result_t GetLocationsForVariable( std::unordered_set* output_index1_locations) { const bool is_fragment = entry_point->GetOperandAs(0) == spv::ExecutionModel::Fragment; - const bool is_output = - variable->GetOperandAs(2) == spv::StorageClass::Output; + const auto sc_index = 2u; + const bool is_output = variable->GetOperandAs(sc_index) == + spv::StorageClass::Output; auto ptr_type_id = variable->GetOperandAs(0); auto ptr_type = _.FindDef(ptr_type_id); auto type_id = ptr_type->GetOperandAs(2); @@ -525,7 +528,8 @@ spv_result_t ValidateLocations(ValidationState_t& _, for (uint32_t i = 3; i < entry_point->operands().size(); ++i) { auto interface_id = entry_point->GetOperandAs(i); auto interface_var = _.FindDef(interface_id); - auto storage_class = interface_var->GetOperandAs(2); + const auto sc_index = 2u; + auto storage_class = interface_var->GetOperandAs(sc_index); if (storage_class != spv::StorageClass::Input && storage_class != spv::StorageClass::Output) { continue; diff --git a/source/val/validate_logicals.cpp b/source/val/validate_logicals.cpp index 4479e43958..f10de97020 100644 --- a/source/val/validate_logicals.cpp +++ b/source/val/validate_logicals.cpp @@ -159,9 +159,10 @@ 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"; diff --git a/source/val/validate_memory.cpp b/source/val/validate_memory.cpp index ef6676fb7b..1b1bec65f4 100644 --- a/source/val/validate_memory.cpp +++ b/source/val/validate_memory.cpp @@ -407,19 +407,57 @@ 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 " << _.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 " << _.getIdName(inst->type_id()) + << " is not a pointer type."; + } + } + + const auto storage_class_index = 2u; + auto storage_class = + inst->GetOperandAs(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(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 type_index = 2; - const auto value_id = result_type->GetOperandAs(type_index); - auto value_type = _.FindDef(value_id); + // 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(2); + } + auto value_type = value_id == 0 ? nullptr : _.FindDef(value_id); - const auto initializer_index = 3; - const auto storage_class_index = 2; + const auto initializer_index = untyped_pointer ? 4u : 3u; if (initializer_index < inst->operands().size()) { const auto initializer_id = inst->GetOperandAs(initializer_index); const auto initializer = _.FindDef(initializer_id); @@ -431,18 +469,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 " << _.getIdName(initializer_id) + << "Variable Initializer " << _.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(storage_class_index); if (storage_class != spv::StorageClass::Workgroup && storage_class != spv::StorageClass::CrossWorkgroup && storage_class != spv::StorageClass::Private && @@ -466,7 +501,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 +530,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 +552,17 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) { result_type->GetOperandAs(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 +581,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 " << _.getIdName(inst->id()) << " has illegal type.\n" @@ -558,11 +593,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 " << _.getIdName(inst->id()) << " has illegal type.\n" @@ -575,7 +610,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 " << _.getIdName(inst->id()) << " has illegal type.\n" @@ -588,7 +624,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 " << _.getIdName(inst->id()) << " has illegal type.\n" @@ -621,11 +658,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(3); + auto init_id = inst->GetOperandAs(initializer_index); auto init = _.FindDef(init_id); if (init->opcode() != spv::Op::OpConstantNull) { return _.diag(SPV_ERROR_INVALID_ID, inst) @@ -652,7 +695,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, " << _.getIdName(inst->id()) @@ -676,10 +719,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(1u)); } - if (pointee_base->opcode() == spv::Op::OpTypePointer) { + if (pointee_base && pointee_base->opcode() == spv::Op::OpTypePointer) { if (pointee_base->GetOperandAs(1u) == spv::StorageClass::PhysicalStorageBuffer) { // check for AliasedPointer/RestrictPointer @@ -769,7 +812,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 +828,7 @@ 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(1u); underlying_type = _.FindDef(underlying_type->GetOperandAs(2u)); @@ -801,7 +844,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 +893,7 @@ 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(1u); underlying_type = _.FindDef(underlying_type->GetOperandAs(2u)); @@ -865,7 +909,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 +975,22 @@ 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 " << _.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 " << _.getIdName(inst->type_id()) - << " does not match Pointer " << _.getIdName(pointer->id()) - << "s type."; + if (pointer_type->opcode() == spv::Op::OpTypePointer) { + const auto pointee_type = + _.FindDef(pointer_type->GetOperandAs(2)); + if (!pointee_type || result_type->id() != pointee_type->id()) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "OpLoad Result Type " << _.getIdName(inst->type_id()) + << " does not match Pointer " << _.getIdName(pointer->id()) + << "s type."; + } } if (!_.options()->before_hlsl_legalization && @@ -987,17 +1033,22 @@ 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 " << _.getIdName(pointer_id) << " is not a pointer type."; } - const auto type_id = pointer_type->GetOperandAs(2); - const auto type = _.FindDef(type_id); - if (!type || spv::Op::OpTypeVoid == type->opcode()) { - return _.diag(SPV_ERROR_INVALID_ID, inst) - << "OpStore Pointer " << _.getIdName(pointer_id) - << "s type is void."; + + Instruction* type = nullptr; + if (pointer_type->opcode() == spv::Op::OpTypePointer) { + const auto type_id = pointer_type->GetOperandAs(2); + type = _.FindDef(type_id); + if (!type || spv::Op::OpTypeVoid == type->opcode()) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "OpStore Pointer " << _.getIdName(pointer_id) + << "s type is void."; + } } // validate storage class @@ -1074,9 +1125,8 @@ spv_result_t ValidateStore(ValidationState_t& _, const Instruction* inst) { << "s type is void."; } - if (type->id() != object_type->id()) { - if (!_.options()->relax_struct_store || - type->opcode() != spv::Op::OpTypeStruct || + if (type && (type->id() != object_type->id())) { + if (!_.options()->relax_struct_store || type->opcode() != spv::Op::OpTypeStruct || object_type->opcode() != spv::Op::OpTypeStruct) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "OpStore Pointer " << _.getIdName(pointer_id) @@ -1179,7 +1229,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 " << _.getIdName(target_id) << " is not a pointer."; @@ -1187,35 +1238,50 @@ 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 " << _.getIdName(source_id) << " is not a pointer."; } if (inst->opcode() == spv::Op::OpCopyMemory) { - const auto target_type = - _.FindDef(target_pointer_type->GetOperandAs(2)); - if (!target_type || target_type->opcode() == spv::Op::OpTypeVoid) { - return _.diag(SPV_ERROR_INVALID_ID, inst) - << "Target operand " << _.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(2)); + + if (!target_type || target_type->opcode() == spv::Op::OpTypeVoid) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "Target operand " << _.getIdName(target_id) + << " cannot be a void pointer."; + } } - const auto source_type = - _.FindDef(source_pointer_type->GetOperandAs(2)); - if (!source_type || source_type->opcode() == spv::Op::OpTypeVoid) { - return _.diag(SPV_ERROR_INVALID_ID, inst) - << "Source operand " << _.getIdName(source_id) - << " cannot be a void pointer."; + if (source_typed) { + source_type = _.FindDef(source_pointer_type->GetOperandAs(2)); + if (!source_type || source_type->opcode() == spv::Op::OpTypeVoid) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "Source operand " << _.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 " << _.getIdName(source_id) << "s type does not match Source " << _.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(2); const auto size = _.FindDef(size_id); @@ -1231,7 +1297,6 @@ spv_result_t ValidateCopyMemory(ValidationState_t& _, const Instruction* inst) { << "Size operand " << _.getIdName(size_id) << " must be a scalar integer type."; } - bool is_zero = true; switch (size->opcode()) { case spv::Op::OpConstantNull: @@ -1258,18 +1323,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(1); + const auto target_sc = + target_pointer_type->GetOperandAs(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(2)); - while (sub_type->opcode() == spv::Op::OpTypePointer) { - sub_type = _.FindDef(sub_type->GetOperandAs(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(2)); + while (sub_type->opcode() == spv::Op::OpTypePointer) { + sub_type = _.FindDef(sub_type->GetOperandAs(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 +1452,49 @@ spv_result_t ValidateAccessChain(ValidationState_t& _, std::string instr_name = "Op" + std::string(spvOpcodeString(static_cast(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 << " " - << _.getIdName(inst->id()) << " must be OpTypePointer. Found Op" - << spvOpcodeString(static_cast(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 << " " + << _.getIdName(inst->id()) + << " must be OpTypeUntypedPointerKHR. Found Op" + << spvOpcodeString(static_cast(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 << " " + << _.getIdName(inst->id()) << " must be OpTypePointer. Found Op" + << spvOpcodeString(static_cast(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(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(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 " << _.getIdName(base_id) << " in " << instr_name << " instruction must be a pointer."; @@ -1318,14 +1512,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(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 +1542,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 +1611,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(result_type_pointee->opcode())) - << ") does not match the type that results from indexing into the " - "base " - " (Op" - << spvOpcodeString(static_cast(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(result_type_pointee->opcode())) + << ") does not match the type that results from indexing into the " + "base " + " (Op" + << spvOpcodeString(static_cast(type_pointee->opcode())) + << ")."; + } } return SPV_SUCCESS; @@ -1550,7 +1757,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 +1769,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(2); const auto base = _.FindDef(base_id); - const auto base_type = _.FindDef(base->type_id()); + const auto base_type = untyped_pointer + ? _.FindDef(inst->GetOperandAs(2)) + : _.FindDef(base->type_id()); const auto base_type_storage_class = base_type->GetOperandAs(1); @@ -1581,15 +1793,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 +1811,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 +1839,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(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 << " " << state.getIdName(inst->id()) << " must be a pointer to an OpTypeStruct."; } - auto structure_type = state.FindDef(pointer_type->GetOperandAs(2)); + Instruction* structure_type = nullptr; + if (untyped) { + structure_type = state.FindDef(inst->GetOperandAs(2)); + } else { + structure_type = state.FindDef(pointer_ty->GetOperandAs(2)); + } + if (structure_type->opcode() != spv::Op::OpTypeStruct) { return state.diag(SPV_ERROR_INVALID_ID, inst) << "The Structure's type in " << instr_name << " " @@ -1654,11 +1879,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(3) != num_of_members - 1) { + const auto index = untyped ? 4 : 3; + if (inst->GetOperandAs(index) != num_of_members - 1) { return state.diag(SPV_ERROR_INVALID_ID, inst) << "The array member in " << instr_name << " " << 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 +2069,15 @@ 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 " << _.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(storage_class_index); @@ -1863,13 +2092,15 @@ spv_result_t ValidateCooperativeMatrixLoadStoreKHR(ValidationState_t& _, << " is not Workgroup, StorageBuffer, or PhysicalStorageBuffer."; } - const auto pointee_id = pointer_type->GetOperandAs(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 " << _.getIdName(pointer->id()) - << "s Type must be a scalar or vector type."; + if (!untyped) { + const auto pointee_id = pointer_type->GetOperandAs(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 " << _.getIdName(pointer->id()) + << "s Type must be a scalar or vector type."; + } } const auto layout_index = @@ -1935,7 +2166,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 +2199,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 +2213,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: diff --git a/source/val/validate_type.cpp b/source/val/validate_type.cpp index cb26a527cc..d2609357f0 100644 --- a/source/val/validate_type.cpp +++ b/source/val/validate_type.cpp @@ -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,32 @@ 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(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 +655,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; } diff --git a/source/val/validation_state.cpp b/source/val/validation_state.cpp index b5ed65fc25..3f50cb73c9 100644 --- a/source/val/validation_state.cpp +++ b/source/val/validation_state.cpp @@ -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: @@ -1169,7 +1170,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( @@ -1179,6 +1182,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)); @@ -1689,6 +1698,42 @@ 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(1u)); + case spv::Op::OpTypePointer: + if (IsForwardPointer(id)) return false; + return ContainsUntypedPointer(inst->GetOperandAs(2u)); + case spv::Op::OpTypeFunction: + case spv::Op::OpTypeStruct: { + for (uint32_t i = 1; i < inst->operands().size(); ++i) { + if (ContainsUntypedPointer(inst->GetOperandAs(i))) + return true; + } + return false; + } + default: + return false; + } + + return false; +} + bool ValidationState_t::IsValidStorageClass( spv::StorageClass storage_class) const { if (spvIsVulkanEnv(context()->target_env)) { diff --git a/source/val/validation_state.h b/source/val/validation_state.h index 27acdcc2f2..1c7e8f0490 100644 --- a/source/val/validation_state.h +++ b/source/val/validation_state.h @@ -648,6 +648,9 @@ class ValidationState_t { const std::function& 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; diff --git a/test/opcode_require_capabilities_test.cpp b/test/opcode_require_capabilities_test.cpp index 37097c6ff2..615c09429e 100644 --- a/test/opcode_require_capabilities_test.cpp +++ b/test/opcode_require_capabilities_test.cpp @@ -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()}, diff --git a/test/text_to_binary.extension_test.cpp b/test/text_to_binary.extension_test.cpp index 2efd27b49a..8f3cf1f1bd 100644 --- a/test/text_to_binary.extension_test.cpp +++ b/test/text_to_binary.extension_test.cpp @@ -1327,5 +1327,52 @@ 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{ + {"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 diff --git a/test/val/val_annotation_test.cpp b/test/val/val_annotation_test.cpp index 97dde2df4a..e65e11f45e 100644 --- a/test/val/val_annotation_test.cpp +++ b/test/val/val_annotation_test.cpp @@ -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; TEST_P(MemberOnlyDecorations, MemberDecoration) { diff --git a/test/val/val_atomics_test.cpp b/test/val/val_atomics_test.cpp index 0f65634a95..1cec51eb8e 100644 --- a/test/val/val_atomics_test.cpp +++ b/test/val/val_atomics_test.cpp @@ -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 diff --git a/test/val/val_conversion_test.cpp b/test/val/val_conversion_test.cpp index 0128aa1f22..748ad64f86 100644 --- a/test/val/val_conversion_test.cpp +++ b/test/val/val_conversion_test.cpp @@ -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; CodeGenerator GetSmallConversionsCodeGenerator() { diff --git a/test/val/val_decoration_test.cpp b/test/val/val_decoration_test.cpp index 5aff68787c..56f592201f 100644 --- a/test/val/val_decoration_test.cpp +++ b/test/val/val_decoration_test.cpp @@ -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,367 @@ 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; + +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>; + +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 diff --git a/test/val/val_extension_spv_khr_subgroup_uniform_control_flow.cpp b/test/val/val_extension_spv_khr_subgroup_uniform_control_flow.cpp new file mode 100644 index 0000000000..f528cb9eef --- /dev/null +++ b/test/val/val_extension_spv_khr_subgroup_uniform_control_flow.cpp @@ -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 +#include + +#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; + +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 diff --git a/test/val/val_fixtures.h b/test/val/val_fixtures.h index 98d8d32a99..71dcc7ce10 100644 --- a/test/val/val_fixtures.h +++ b/test/val/val_fixtures.h @@ -76,6 +76,10 @@ 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 +88,7 @@ class ValidateBase : public ::testing::Test, spv_diagnostic diagnostic_; spv_validator_options options_; std::unique_ptr vstate_; + uint32_t assemble_options_ = SPV_TEXT_TO_BINARY_OPTION_NONE; }; template @@ -132,8 +137,9 @@ void ValidateBase::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" diff --git a/test/val/val_function_test.cpp b/test/val/val_function_test.cpp index 24b52638a2..9e39f69d16 100644 --- a/test/val/val_function_test.cpp +++ b/test/val/val_function_test.cpp @@ -836,6 +836,111 @@ TEST_F(ValidateFunctionCall, LogicallyMismatchedPointersArraySize) { HasSubstr("type does not match Function ")); } +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 '2[%var]'s type does not " + "match Function '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", diff --git a/test/val/val_id_test.cpp b/test/val/val_id_test.cpp index c1b8e41f94..a2d752255d 100644 --- a/test/val/val_id_test.cpp +++ b/test/val/val_id_test.cpp @@ -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,159 @@ 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 +2422,8 @@ OpFunctionEnd CompileSuccessfully(spirv.c_str()); EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); EXPECT_THAT(getDiagnosticString(), - HasSubstr(make_message( - "OpVariable Initializer '8[%8]' is not a constant " - "or module-scope variable"))); + HasSubstr("Variable Initializer '8[%8]' is not a constant " + "or module-scope variable")); } TEST_P(ValidateIdWithMessage, OpVariableInitializerIsModuleVarGood) { @@ -6404,9 +6555,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 +6583,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 +6613,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 +6643,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 +6677,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 +6711,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 +6746,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 )"; diff --git a/test/val/val_interfaces_test.cpp b/test/val/val_interfaces_test.cpp index 40e67184d5..50f45573f7 100644 --- a/test/val/val_interfaces_test.cpp +++ b/test/val/val_interfaces_test.cpp @@ -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 diff --git a/test/val/val_memory_test.cpp b/test/val/val_memory_test.cpp index dfddc98725..b4689f2e9d 100644 --- a/test/val/val_memory_test.cpp +++ b/test/val/val_memory_test.cpp @@ -352,12 +352,8 @@ OpFunctionEnd )"; CompileSuccessfully(spirv.c_str()); EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); - EXPECT_THAT( - getDiagnosticString(), - HasSubstr( - "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.")); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Storage class must match result type storage class")); } TEST_F(ValidateMemory, MatchingStorageClassesGood) { @@ -788,7 +784,7 @@ TEST_F(ValidateMemory, ArrayLenIndexNotLastMember) { EXPECT_THAT( getDiagnosticString(), HasSubstr( - "The array member in OpArrayLength '11[%11]' must be an the " + "The array member in OpArrayLength '11[%11]' must be the " "last member of the struct.\n %11 = OpArrayLength %uint %10 0\n")); } @@ -1465,6 +1461,7 @@ OpExtension "SPV_KHR_vulkan_memory_model" OpMemoryModel Logical VulkanKHR %void = OpTypeVoid %int = OpTypeInt 32 0 +%int_4 = OpConstant %int 4 %device = OpConstant %int 1 %int_ptr_ssbo = OpTypePointer StorageBuffer %int %var1 = OpVariable %int_ptr_ssbo StorageBuffer @@ -1472,7 +1469,7 @@ OpMemoryModel Logical VulkanKHR %voidfn = OpTypeFunction %void %func = OpFunction %void None %voidfn %entry = OpLabel -OpCopyMemorySized %var1 %var2 %device MakePointerAvailableKHR|NonPrivatePointerKHR %device +OpCopyMemorySized %var1 %var2 %int_4 MakePointerAvailableKHR|NonPrivatePointerKHR %device OpReturn OpFunctionEnd )"; @@ -1496,6 +1493,7 @@ OpExtension "SPV_KHR_vulkan_memory_model" OpMemoryModel Logical VulkanKHR %void = OpTypeVoid %int = OpTypeInt 32 0 +%int_4 = OpConstant %int 4 %device = OpConstant %int 1 %workgroup = OpConstant %int 1 %int_ptr_ssbo = OpTypePointer StorageBuffer %int @@ -1504,7 +1502,7 @@ OpMemoryModel Logical VulkanKHR %voidfn = OpTypeFunction %void %func = OpFunction %void None %voidfn %entry = OpLabel -OpCopyMemorySized %var1 %var2 %device Aligned|MakePointerVisibleKHR|MakePointerAvailableKHR|NonPrivatePointerKHR 4 %device %workgroup +OpCopyMemorySized %var1 %var2 %int_4 Aligned|MakePointerVisibleKHR|MakePointerAvailableKHR|NonPrivatePointerKHR 4 %device %workgroup OpReturn OpFunctionEnd )"; @@ -1528,6 +1526,7 @@ OpExtension "SPV_KHR_vulkan_memory_model" OpMemoryModel Logical VulkanKHR %void = OpTypeVoid %int = OpTypeInt 32 0 +%int_4 = OpConstant %int 4 %device = OpConstant %int 1 %workgroup = OpConstant %int 1 %int_ptr_ssbo = OpTypePointer StorageBuffer %int @@ -1536,7 +1535,7 @@ OpMemoryModel Logical VulkanKHR %voidfn = OpTypeFunction %void %func = OpFunction %void None %voidfn %entry = OpLabel -OpCopyMemorySized %var1 %var2 %device Aligned|MakePointerVisibleKHR|MakePointerAvailableKHR|NonPrivatePointerKHR 4 %workgroup %device +OpCopyMemorySized %var1 %var2 %int_4 Aligned|MakePointerVisibleKHR|MakePointerAvailableKHR|NonPrivatePointerKHR 4 %workgroup %device OpReturn OpFunctionEnd )"; @@ -1561,6 +1560,7 @@ OpExtension "SPV_KHR_vulkan_memory_model" OpMemoryModel Logical VulkanKHR %void = OpTypeVoid %int = OpTypeInt 32 0 +%int_4 = OpConstant %int 4 %device = OpConstant %int 1 %int_ptr_ssbo = OpTypePointer StorageBuffer %int %var1 = OpVariable %int_ptr_ssbo StorageBuffer @@ -1568,7 +1568,7 @@ OpMemoryModel Logical VulkanKHR %voidfn = OpTypeFunction %void %func = OpFunction %void None %voidfn %entry = OpLabel -OpCopyMemorySized %var1 %var2 %device MakePointerAvailableKHR|NonPrivatePointerKHR %device +OpCopyMemorySized %var1 %var2 %int_4 MakePointerAvailableKHR|NonPrivatePointerKHR %device OpReturn OpFunctionEnd )"; @@ -1588,6 +1588,7 @@ OpExtension "SPV_KHR_vulkan_memory_model" OpMemoryModel Logical VulkanKHR %void = OpTypeVoid %int = OpTypeInt 32 0 +%int_4 = OpConstant %int 4 %device = OpConstant %int 1 %workgroup = OpConstant %int 2 %int_ptr_ssbo = OpTypePointer StorageBuffer %int @@ -1596,7 +1597,7 @@ OpMemoryModel Logical VulkanKHR %voidfn = OpTypeFunction %void %func = OpFunction %void None %voidfn %entry = OpLabel -OpCopyMemorySized %var1 %var2 %device Aligned|MakePointerVisibleKHR|MakePointerAvailableKHR|NonPrivatePointerKHR 4 %device %workgroup +OpCopyMemorySized %var1 %var2 %int_4 Aligned|MakePointerVisibleKHR|MakePointerAvailableKHR|NonPrivatePointerKHR 4 %device %workgroup OpReturn OpFunctionEnd )"; @@ -1616,6 +1617,7 @@ OpExtension "SPV_KHR_vulkan_memory_model" OpMemoryModel Logical VulkanKHR %void = OpTypeVoid %int = OpTypeInt 32 0 +%int_4 = OpConstant %int 4 %device = OpConstant %int 1 %workgroup = OpConstant %int 2 %int_ptr_ssbo = OpTypePointer StorageBuffer %int @@ -1624,7 +1626,7 @@ OpMemoryModel Logical VulkanKHR %voidfn = OpTypeFunction %void %func = OpFunction %void None %voidfn %entry = OpLabel -OpCopyMemorySized %var1 %var2 %device Aligned|MakePointerVisibleKHR|MakePointerAvailableKHR|NonPrivatePointerKHR 4 %workgroup %device +OpCopyMemorySized %var1 %var2 %int_4 Aligned|MakePointerVisibleKHR|MakePointerAvailableKHR|NonPrivatePointerKHR 4 %workgroup %device OpReturn OpFunctionEnd )"; @@ -3857,8 +3859,7 @@ OpMemoryModel Logical GLSL450 CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3); EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); EXPECT_THAT(getDiagnosticString(), - HasSubstr("Initializer type must match the type pointed to by " - "the Result Type")); + HasSubstr("Initializer type must match the data type")); } TEST_F(ValidateMemory, StoreToUniformBlock) { @@ -5087,6 +5088,128 @@ OpFunctionEnd EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); } +TEST_F(ValidateMemory, UntypedVariableGood) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpCapability UntypedPointersKHR +OpExtension "SPV_KHR_untyped_pointers" +OpMemoryModel Logical GLSL450 +%int = OpTypeInt 32 0 +%int_0 = OpConstant %int 0 +%ptr = OpTypeUntypedPointerKHR Private +%var = OpUntypedVariableKHR %ptr Private %int %int_0 +)"; + + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateMemory, UntypedVariableNoDataType) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpCapability UntypedPointersKHR +OpExtension "SPV_KHR_untyped_pointers" +OpExtension "SPV_KHR_storage_buffer_storage_class" +OpMemoryModel Logical GLSL450 +%int = OpTypeInt 32 0 +%int_0 = OpConstant %int 0 +%ptr = OpTypeUntypedPointerKHR StorageBuffer +%var = OpUntypedVariableKHR %ptr StorageBuffer +)"; + + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateMemory, UntypedVariableNoDataTypeFunction) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpCapability UntypedPointersKHR +OpExtension "SPV_KHR_untyped_pointers" +OpMemoryModel Logical GLSL450 +%int = OpTypeInt 32 0 +%int_0 = OpConstant %int 0 +%ptr = OpTypeUntypedPointerKHR Function +%var = OpUntypedVariableKHR %ptr Function +)"; + + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Data type must be specified for Function, " + "Private, and Workgroup storage classes")); +} + +TEST_F(ValidateMemory, UntypedVariableNoDataTypePrivate) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpCapability UntypedPointersKHR +OpExtension "SPV_KHR_untyped_pointers" +OpMemoryModel Logical GLSL450 +%int = OpTypeInt 32 0 +%int_0 = OpConstant %int 0 +%ptr = OpTypeUntypedPointerKHR Private +%var = OpUntypedVariableKHR %ptr Private +)"; + + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Data type must be specified for Function, " + "Private, and Workgroup storage classes")); +} + +TEST_F(ValidateMemory, UntypedVariableNoDataTypeWorkgroup) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpCapability UntypedPointersKHR +OpExtension "SPV_KHR_untyped_pointers" +OpMemoryModel Logical GLSL450 +%int = OpTypeInt 32 0 +%int_0 = OpConstant %int 0 +%ptr = OpTypeUntypedPointerKHR Workgroup +%var = OpUntypedVariableKHR %ptr Workgroup +)"; + + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Data type must be specified for Function, " + "Private, and Workgroup storage classes")); +} + +TEST_F(ValidateMemory, UntypedVariableNoDataTypeVulkan) { + 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 +%int = OpTypeInt 32 0 +%int_0 = OpConstant %int 0 +%ptr = OpTypeUntypedPointerKHR StorageBuffer +%var = OpUntypedVariableKHR %ptr StorageBuffer +%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("Vulkan requires that data type be specified")); +} + TEST_F(ValidateMemory, PtrAccessChainArrayStrideBad) { const std::string spirv = R"( OpCapability Shader @@ -5359,6 +5482,1467 @@ OpFunctionEnd EXPECT_THAT(getDiagnosticString(), HasSubstr("cannot find index -224")); } +TEST_F(ValidateMemory, UntypedVariableFunctionOutsideFunction) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpCapability UntypedPointersKHR +OpExtension "SPV_KHR_untyped_pointers" +OpMemoryModel Logical GLSL450 +%int = OpTypeInt 32 0 +%int_0 = OpConstant %int 0 +%ptr = OpTypeUntypedPointerKHR Function +%var = OpUntypedVariableKHR %ptr Function %int +)"; + + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_ERROR_INVALID_LAYOUT, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Variables can not have a function[7] storage class " + "outside of a function")); +} + +TEST_F(ValidateMemory, UntypedVariableBadResultType) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpCapability UntypedPointersKHR +OpExtension "SPV_KHR_untyped_pointers" +OpMemoryModel Logical GLSL450 +%int = OpTypeInt 32 0 +%int_0 = OpConstant %int 0 +%ptr = OpTypeUntypedPointerKHR Workgroup +%var = OpUntypedVariableKHR %int Workgroup %int +)"; + + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Result type must be an untyped pointer")); +} + +TEST_F(ValidateMemory, UntypedVariableBadDataType) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpCapability UntypedPointersKHR +OpExtension "SPV_KHR_untyped_pointers" +OpMemoryModel Logical GLSL450 +%int = OpTypeInt 32 0 +%int_0 = OpConstant %int 0 +%ptr = OpTypeUntypedPointerKHR Workgroup +%var = OpUntypedVariableKHR %ptr Workgroup %int_0 +)"; + + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Data type must be a type instruction")); +} + +TEST_F(ValidateMemory, UntypedVariableBadStorageClass) { + const std::string spirv = R"( +OpCapability Kernel +OpCapability GenericPointer +OpCapability Linkage +OpCapability UntypedPointersKHR +OpExtension "SPV_KHR_untyped_pointers" +OpMemoryModel Logical OpenCL +%int = OpTypeInt 32 0 +%int_0 = OpConstant %int 0 +%ptr = OpTypeUntypedPointerKHR Generic +%var = OpUntypedVariableKHR %ptr Generic %int +)"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_2); + EXPECT_EQ(SPV_ERROR_INVALID_BINARY, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_2)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Variable storage class cannot be Generic")); +} + +TEST_F(ValidateMemory, UntypedVariableMismatchedStorageClass) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpCapability UntypedPointersKHR +OpExtension "SPV_KHR_untyped_pointers" +OpMemoryModel Logical GLSL450 +%int = OpTypeInt 32 0 +%int_0 = OpConstant %int 0 +%ptr = OpTypeUntypedPointerKHR Workgroup +%var = OpUntypedVariableKHR %ptr Private %int +)"; + + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Storage class must match result type storage class")); +} + +TEST_F(ValidateMemory, UntypedVariableBadInitializer) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpCapability UntypedPointersKHR +OpExtension "SPV_KHR_untyped_pointers" +OpMemoryModel Logical GLSL450 +%int = OpTypeInt 32 0 +%float = OpTypeFloat 32 +%float_0 = OpConstant %float 0 +%ptr = OpTypeUntypedPointerKHR Private +%var = OpUntypedVariableKHR %ptr Private %int %float_0 +)"; + + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Initializer type must match the data type")); +} + +TEST_F(ValidateMemory, AccessChainBaseUntypedPointer) { + const std::string spirv = R"( +OpCapability Shader +OpCapability UntypedPointersKHR +OpExtension "SPV_KHR_storage_buffer_storage_class" +OpExtension "SPV_KHR_untyped_pointers" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpName %var "var" +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%int_0 = OpConstant %int 0 +%block = OpTypeStruct %int +%ptr_ssbo = OpTypePointer StorageBuffer %block +%ptr_ssbo_int = OpTypePointer StorageBuffer %int +%ptr = OpTypeUntypedPointerKHR StorageBuffer +%var = OpUntypedVariableKHR %ptr StorageBuffer %int +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +%gep = OpAccessChain %ptr_ssbo_int %var %int_0 +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("The Base '2[%var]' in OpAccessChain " + "instruction must be a pointer")); +} + +using ValidateMemoryUntypedAccessChain = spvtest::ValidateBase; + +TEST_P(ValidateMemoryUntypedAccessChain, GoodTypedPointerBase) { + const std::string opcode = GetParam(); + const bool ptr = opcode == "OpUntypedPtrAccessChainKHR" || + opcode == "OpUntypedInBoundsPtrAccessChainKHR"; + const std::string extra_param = ptr ? "%int_0" : ""; + + const std::string spirv = R"( +OpCapability Shader +OpCapability UntypedPointersKHR +OpCapability VariablePointers +OpExtension "SPV_KHR_variable_pointers" +OpExtension "SPV_KHR_storage_buffer_storage_class" +OpExtension "SPV_KHR_untyped_pointers" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%int_0 = OpConstant %int 0 +%block = OpTypeStruct %int +%ptr_ssbo = OpTypePointer StorageBuffer %block +%var = OpVariable %ptr_ssbo StorageBuffer +%ptr = OpTypeUntypedPointerKHR StorageBuffer +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +%gep = )" + opcode + R"( %ptr %block %var )" + + extra_param + R"( %int_0 +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_P(ValidateMemoryUntypedAccessChain, GoodUntypedPointerBase) { + const std::string opcode = GetParam(); + const bool ptr = opcode == "OpUntypedPtrAccessChainKHR" || + opcode == "OpUntypedInBoundsPtrAccessChainKHR"; + const std::string extra_param = ptr ? "%int_0" : ""; + + const std::string spirv = R"( +OpCapability Shader +OpCapability UntypedPointersKHR +OpCapability VariablePointers +OpExtension "SPV_KHR_variable_pointers" +OpExtension "SPV_KHR_storage_buffer_storage_class" +OpExtension "SPV_KHR_untyped_pointers" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%int_0 = OpConstant %int 0 +%block = OpTypeStruct %int +%ptr_ssbo = OpTypePointer StorageBuffer %block +%ptr = OpTypeUntypedPointerKHR StorageBuffer +%var = OpUntypedVariableKHR %ptr StorageBuffer %int +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +%gep = )" + opcode + R"( %ptr %block %var )" + + extra_param + R"( %int_0 +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_P(ValidateMemoryUntypedAccessChain, ResultTypedPointer) { + const std::string opcode = GetParam(); + const bool ptr = opcode == "OpUntypedPtrAccessChainKHR" || + opcode == "OpUntypedInBoundsPtrAccessChainKHR"; + const std::string extra_param = ptr ? "%int_0" : ""; + + const std::string spirv = R"( +OpCapability Shader +OpCapability UntypedPointersKHR +OpCapability VariablePointers +OpExtension "SPV_KHR_variable_pointers" +OpExtension "SPV_KHR_storage_buffer_storage_class" +OpExtension "SPV_KHR_untyped_pointers" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpName %gep "gep" +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%int_0 = OpConstant %int 0 +%block = OpTypeStruct %int +%ptr_ssbo = OpTypePointer StorageBuffer %block +%var = OpVariable %ptr_ssbo StorageBuffer +%ptr = OpTypeUntypedPointerKHR StorageBuffer +%ptr_int = OpTypePointer StorageBuffer %int +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +%gep = )" + opcode + R"( %ptr_int %block %var )" + + extra_param + R"( %int_0 +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("The Result Type of " + opcode + + " '2[%gep]' must be OpTypeUntypedPointer")); +} + +TEST_P(ValidateMemoryUntypedAccessChain, BaseTypeNotAType) { + const std::string opcode = GetParam(); + const bool ptr = opcode == "OpUntypedPtrAccessChainKHR" || + opcode == "OpUntypedInBoundsPtrAccessChainKHR"; + const std::string extra_param = ptr ? "%int_0" : ""; + + const std::string spirv = R"( +OpCapability Shader +OpCapability UntypedPointersKHR +OpCapability VariablePointers +OpExtension "SPV_KHR_variable_pointers" +OpExtension "SPV_KHR_storage_buffer_storage_class" +OpExtension "SPV_KHR_untyped_pointers" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpName %gep "gep" +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%int_0 = OpConstant %int 0 +%block = OpTypeStruct %int +%ptr_ssbo = OpTypePointer StorageBuffer %block +%var = OpVariable %ptr_ssbo StorageBuffer +%ptr = OpTypeUntypedPointerKHR StorageBuffer +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +%gep = )" + opcode + R"( %ptr %int_0 %var )" + + extra_param + R"( %int_0 +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Base type must be a non-pointer type")); +} + +TEST_P(ValidateMemoryUntypedAccessChain, BaseTypedPointer) { + const std::string opcode = GetParam(); + const bool ptr = opcode == "OpUntypedPtrAccessChainKHR" || + opcode == "OpUntypedInBoundsPtrAccessChainKHR"; + const std::string extra_param = ptr ? "%int_0" : ""; + + const std::string spirv = R"( +OpCapability Shader +OpCapability UntypedPointersKHR +OpCapability VariablePointers +OpExtension "SPV_KHR_variable_pointers" +OpExtension "SPV_KHR_storage_buffer_storage_class" +OpExtension "SPV_KHR_untyped_pointers" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpName %gep "gep" +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%int_0 = OpConstant %int 0 +%block = OpTypeStruct %int +%ptr_ssbo = OpTypePointer StorageBuffer %block +%var = OpVariable %ptr_ssbo StorageBuffer +%ptr = OpTypeUntypedPointerKHR StorageBuffer +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +%gep = )" + opcode + R"( %ptr %ptr_ssbo %var )" + + extra_param + R"( %int_0 +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Base type must be a non-pointer type")); +} + +TEST_P(ValidateMemoryUntypedAccessChain, BaseUntypedPointer) { + const std::string opcode = GetParam(); + const bool ptr = opcode == "OpUntypedPtrAccessChainKHR" || + opcode == "OpUntypedInBoundsPtrAccessChainKHR"; + const std::string extra_param = ptr ? "%int_0" : ""; + + const std::string spirv = R"( +OpCapability Shader +OpCapability UntypedPointersKHR +OpCapability VariablePointers +OpExtension "SPV_KHR_variable_pointers" +OpExtension "SPV_KHR_storage_buffer_storage_class" +OpExtension "SPV_KHR_untyped_pointers" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpName %gep "gep" +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%int_0 = OpConstant %int 0 +%block = OpTypeStruct %int +%ptr_ssbo = OpTypePointer StorageBuffer %block +%var = OpVariable %ptr_ssbo StorageBuffer +%ptr = OpTypeUntypedPointerKHR StorageBuffer +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +%gep = )" + opcode + R"( %ptr %ptr %var )" + + extra_param + R"( %int_0 +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Base type must be a non-pointer type")); +} + +TEST_P(ValidateMemoryUntypedAccessChain, BaseNotAPointer) { + const std::string opcode = GetParam(); + const bool ptr = opcode == "OpUntypedPtrAccessChainKHR" || + opcode == "OpUntypedInBoundsPtrAccessChainKHR"; + const std::string extra_param = ptr ? "%int_0" : ""; + + const std::string spirv = R"( +OpCapability Shader +OpCapability UntypedPointersKHR +OpCapability VariablePointers +OpExtension "SPV_KHR_variable_pointers" +OpExtension "SPV_KHR_storage_buffer_storage_class" +OpExtension "SPV_KHR_untyped_pointers" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpName %int_0 "int_0" +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%int_0 = OpConstant %int 0 +%block = OpTypeStruct %int +%ptr_ssbo = OpTypePointer StorageBuffer %block +%var = OpVariable %ptr_ssbo StorageBuffer +%ptr = OpTypeUntypedPointerKHR StorageBuffer +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +%gep = )" + opcode + R"( %ptr %int %int_0 )" + + extra_param + R"( %int_0 +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("The Base '2[%int_0]' in " + opcode + + " instruction must be a pointer")); +} + +TEST_P(ValidateMemoryUntypedAccessChain, StorageClassMismatch) { + const std::string opcode = GetParam(); + const bool ptr = opcode == "OpUntypedPtrAccessChainKHR" || + opcode == "OpUntypedInBoundsPtrAccessChainKHR"; + const std::string extra_param = ptr ? "%int_0" : ""; + + const std::string spirv = R"( +OpCapability Shader +OpCapability UntypedPointersKHR +OpCapability VariablePointers +OpExtension "SPV_KHR_variable_pointers" +OpExtension "SPV_KHR_storage_buffer_storage_class" +OpExtension "SPV_KHR_untyped_pointers" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpName %int_0 "int_0" +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%int_0 = OpConstant %int 0 +%block = OpTypeStruct %int +%ptr_wg = OpTypePointer Workgroup %block +%var = OpVariable %ptr_wg Workgroup +%ptr = OpTypeUntypedPointerKHR StorageBuffer +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +%gep = )" + opcode + R"( %ptr %block %var )" + + extra_param + R"( %int_0 +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("The result pointer storage class and base pointer storage " + "class in " + + opcode + " do not match")); +} + +TEST_P(ValidateMemoryUntypedAccessChain, NonCompositeBase) { + const std::string opcode = GetParam(); + const bool ptr = opcode == "OpUntypedPtrAccessChainKHR" || + opcode == "OpUntypedInBoundsPtrAccessChainKHR"; + const std::string extra_param = ptr ? "%int_0" : ""; + + const std::string spirv = R"( +OpCapability Shader +OpCapability UntypedPointersKHR +OpCapability VariablePointers +OpExtension "SPV_KHR_variable_pointers" +OpExtension "SPV_KHR_storage_buffer_storage_class" +OpExtension "SPV_KHR_untyped_pointers" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpName %int_0 "int_0" +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%int_0 = OpConstant %int 0 +%block = OpTypeStruct %int +%ptr_wg = OpTypePointer StorageBuffer %block +%var = OpVariable %ptr_wg StorageBuffer +%ptr = OpTypeUntypedPointerKHR StorageBuffer +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +%gep = )" + opcode + R"( %ptr %int %var )" + + extra_param + R"( %int_0 +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr(opcode + " reached non-composite type while indexes " + "still remain to be traversed")); +} + +TEST_P(ValidateMemoryUntypedAccessChain, TooManyIndices) { + const std::string opcode = GetParam(); + const bool ptr = opcode == "OpUntypedPtrAccessChainKHR" || + opcode == "OpUntypedInBoundsPtrAccessChainKHR"; + const std::string extra_param = ptr ? "%int_0" : ""; + + const std::string spirv = R"( +OpCapability Shader +OpCapability UntypedPointersKHR +OpCapability VariablePointers +OpExtension "SPV_KHR_variable_pointers" +OpExtension "SPV_KHR_storage_buffer_storage_class" +OpExtension "SPV_KHR_untyped_pointers" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpName %int_0 "int_0" +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%int_0 = OpConstant %int 0 +%block = OpTypeStruct %int +%ptr_wg = OpTypePointer StorageBuffer %block +%var = OpVariable %ptr_wg StorageBuffer +%ptr = OpTypeUntypedPointerKHR StorageBuffer +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +%gep = )" + opcode + R"( %ptr %block %var )" + + extra_param + R"( %int_0 %int_0 +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr(opcode + " reached non-composite type while indexes " + "still remain to be traversed")); +} + +INSTANTIATE_TEST_SUITE_P( + ValidateUntypedAccessChains, ValidateMemoryUntypedAccessChain, + Values("OpUntypedAccessChainKHR", "OpUntypedInBoundsAccessChainKHR", + "OpUntypedPtrAccessChainKHR", "OpUntypedInBoundsPtrAccessChainKHR")); + +TEST_F(ValidateMemory, LoadUntypedPointerGood) { + 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 +%float = OpTypeFloat 32 +%struct = OpTypeStruct %int +%ptr = OpTypeUntypedPointerKHR Workgroup +%var = OpUntypedVariableKHR %ptr Workgroup %struct +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +%load = OpLoad %float %var +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4)); +} + +TEST_F(ValidateMemory, StoreUntypedPointerGood) { + 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 +%float = OpTypeFloat 32 +%float_0 = OpConstant %float 0 +%struct = OpTypeStruct %int +%ptr = OpTypeUntypedPointerKHR Workgroup +%var = OpUntypedVariableKHR %ptr Workgroup %struct +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +OpStore %var %float_0 +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4)); +} + +TEST_F(ValidateMemory, CopyMemoryUntypedPointerSourceGood) { + 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" %var1 %var2 +OpName %var1 "var1" +OpDecorate %struct Block +OpMemberDecorate %struct 0 Offset 0 +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%struct = OpTypeStruct %int +%ptr = OpTypeUntypedPointerKHR Workgroup +%var1 = OpUntypedVariableKHR %ptr Workgroup %struct +%ptr_wg = OpTypePointer Workgroup %int +%var2 = OpVariable %ptr_wg Workgroup +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +OpCopyMemory %var2 %var1 +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4)); +} + +TEST_F(ValidateMemory, CopyMemoryUntypedPointerTargetGood) { + 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" %var1 %var2 +OpName %var1 "var1" +OpDecorate %struct Block +OpMemberDecorate %struct 0 Offset 0 +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%struct = OpTypeStruct %int +%ptr = OpTypeUntypedPointerKHR Workgroup +%var1 = OpUntypedVariableKHR %ptr Workgroup %struct +%ptr_wg = OpTypePointer Workgroup %int +%var2 = OpVariable %ptr_wg Workgroup +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +OpCopyMemory %var1 %var2 +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4)); +} + +TEST_F(ValidateMemory, CopyMemoryUntypedPointerTargetAndSourceBad) { + 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" %var1 %var2 +OpName %var1 "var1" +OpDecorate %struct Block +OpMemberDecorate %struct 0 Offset 0 +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%struct = OpTypeStruct %int +%ptr = OpTypeUntypedPointerKHR Workgroup +%var1 = OpUntypedVariableKHR %ptr Workgroup %struct +%var2 = OpUntypedVariableKHR %ptr Workgroup %struct +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +OpCopyMemory %var1 %var2 +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("One of Source or Target must be a typed pointer")); +} + +TEST_F(ValidateMemory, CopyMemorySizedUntypedPointersGood) { + 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" %v1 %v2 +OpDecorate %struct Block +OpMemberDecorate %struct 0 Offset 0 +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%int_4 = OpConstant %int 4 +%struct = OpTypeStruct %int +%ptr = OpTypeUntypedPointerKHR Workgroup +%v1 = OpUntypedVariableKHR %ptr Workgroup %struct +%v2 = OpUntypedVariableKHR %ptr Workgroup %struct +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +OpCopyMemorySized %v2 %v1 %int_4 +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4)); +} + +TEST_F(ValidateMemory, CopyMemorySizedUntypedPointersSizeBad1) { + const std::string spirv = R"( +OpCapability Shader +OpCapability UntypedPointersKHR +OpCapability StorageBuffer16BitAccess +OpCapability WorkgroupMemoryExplicitLayoutKHR +OpExtension "SPV_KHR_workgroup_memory_explicit_layout" +OpExtension "SPV_KHR_untyped_pointers" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" %var_wg %var_ssbo +OpDecorate %struct Block +OpMemberDecorate %struct 0 Offset 0 +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%short = OpTypeInt 16 0 +%int_2 = OpConstant %int 2 +%struct = OpTypeStruct %int +%ptr_ssbo = OpTypeUntypedPointerKHR StorageBuffer +%ptr_wg = OpTypeUntypedPointerKHR Workgroup +%var_ssbo = OpUntypedVariableKHR %ptr_ssbo StorageBuffer %struct +%var_wg = OpUntypedVariableKHR %ptr_wg Workgroup %struct +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +OpCopyMemorySized %var_ssbo %var_wg %int_2 +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("Size must be a multiple of 4")); +} + +TEST_F(ValidateMemory, CopyMemorySizedUntypedPointersSizeBad2) { + const std::string spirv = R"( +OpCapability Shader +OpCapability UntypedPointersKHR +OpCapability StorageBuffer16BitAccess +OpCapability WorkgroupMemoryExplicitLayoutKHR +OpExtension "SPV_KHR_workgroup_memory_explicit_layout" +OpExtension "SPV_KHR_untyped_pointers" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" %var_ssbo %var_wg +OpDecorate %struct Block +OpMemberDecorate %struct 0 Offset 0 +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%short = OpTypeInt 16 0 +%int_2 = OpConstant %int 2 +%struct = OpTypeStruct %int +%ptr_ssbo = OpTypeUntypedPointerKHR StorageBuffer +%ptr_wg = OpTypeUntypedPointerKHR Workgroup +%var_ssbo = OpUntypedVariableKHR %ptr_ssbo StorageBuffer %struct +%var_wg = OpUntypedVariableKHR %ptr_wg Workgroup %struct +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +OpCopyMemorySized %var_wg %var_ssbo %int_2 +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("Size must be a multiple of 4")); +} + +TEST_F(ValidateMemory, CopyMemorySizedUntypedPointersSizeBad3) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Int16 +OpCapability UntypedPointersKHR +OpCapability StorageBuffer8BitAccess +OpCapability WorkgroupMemoryExplicitLayoutKHR +OpExtension "SPV_KHR_workgroup_memory_explicit_layout" +OpExtension "SPV_KHR_8bit_storage" +OpExtension "SPV_KHR_untyped_pointers" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" %var_ssbo %var_wg +OpDecorate %struct Block +OpMemberDecorate %struct 0 Offset 0 +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%short = OpTypeInt 16 0 +%int_1 = OpConstant %int 1 +%struct = OpTypeStruct %int +%ptr_ssbo = OpTypeUntypedPointerKHR StorageBuffer +%ptr_wg = OpTypeUntypedPointerKHR Workgroup +%var_ssbo = OpUntypedVariableKHR %ptr_ssbo StorageBuffer %struct +%var_wg = OpUntypedVariableKHR %ptr_wg Workgroup %struct +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +OpCopyMemorySized %var_ssbo %var_wg %int_1 +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("Size must be a multiple of 2")); +} + +TEST_F(ValidateMemory, CopyMemorySizedUntypedPointersSizeBad4) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Int16 +OpCapability UntypedPointersKHR +OpCapability StorageBuffer8BitAccess +OpCapability WorkgroupMemoryExplicitLayoutKHR +OpExtension "SPV_KHR_workgroup_memory_explicit_layout" +OpExtension "SPV_KHR_8bit_storage" +OpExtension "SPV_KHR_untyped_pointers" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" %var_ssbo %var_wg +OpDecorate %struct Block +OpMemberDecorate %struct 0 Offset 0 +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%short = OpTypeInt 16 0 +%int_1 = OpConstant %int 1 +%struct = OpTypeStruct %int +%ptr_ssbo = OpTypeUntypedPointerKHR StorageBuffer +%ptr_wg = OpTypeUntypedPointerKHR Workgroup +%var_ssbo = OpUntypedVariableKHR %ptr_ssbo StorageBuffer %struct +%var_wg = OpUntypedVariableKHR %ptr_wg Workgroup %struct +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +OpCopyMemorySized %var_wg %var_ssbo %int_1 +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("Size must be a multiple of 2")); +} + +TEST_F(ValidateMemory, PtrEqualUntypedPointersGood) { + const std::string spirv = R"( +OpCapability Shader +OpCapability VariablePointers +OpCapability UntypedPointersKHR +OpCapability WorkgroupMemoryExplicitLayoutKHR +OpExtension "SPV_KHR_workgroup_memory_explicit_layout" +OpExtension "SPV_KHR_untyped_pointers" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" %v1 %v2 +OpDecorate %struct Block +OpMemberDecorate %struct 0 Offset 0 +%void = OpTypeVoid +%bool = OpTypeBool +%int = OpTypeInt 32 0 +%struct = OpTypeStruct %int +%ptr = OpTypeUntypedPointerKHR Workgroup +%v1 = OpUntypedVariableKHR %ptr Workgroup %struct +%v2 = OpUntypedVariableKHR %ptr Workgroup %struct +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +%res = OpPtrEqual %bool %v1 %v2 +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4)); +} + +TEST_F(ValidateMemory, PtrNotEqualUntypedPointersGood) { + const std::string spirv = R"( +OpCapability Shader +OpCapability VariablePointers +OpCapability UntypedPointersKHR +OpCapability WorkgroupMemoryExplicitLayoutKHR +OpExtension "SPV_KHR_workgroup_memory_explicit_layout" +OpExtension "SPV_KHR_untyped_pointers" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" %v1 %v2 +OpDecorate %struct Block +OpMemberDecorate %struct 0 Offset 0 +%void = OpTypeVoid +%bool = OpTypeBool +%int = OpTypeInt 32 0 +%struct = OpTypeStruct %int +%ptr = OpTypeUntypedPointerKHR Workgroup +%v1 = OpUntypedVariableKHR %ptr Workgroup %struct +%v2 = OpUntypedVariableKHR %ptr Workgroup %struct +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +%res = OpPtrNotEqual %bool %v1 %v2 +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4)); +} + +TEST_F(ValidateMemory, PtrDiffUntypedPointersGood) { + const std::string spirv = R"( +OpCapability Shader +OpCapability VariablePointers +OpCapability UntypedPointersKHR +OpCapability WorkgroupMemoryExplicitLayoutKHR +OpExtension "SPV_KHR_workgroup_memory_explicit_layout" +OpExtension "SPV_KHR_untyped_pointers" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" %v1 +OpDecorate %struct Block +OpMemberDecorate %struct 0 Offset 0 +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%struct = OpTypeStruct %int +%ptr = OpTypeUntypedPointerKHR Workgroup +%v1 = OpUntypedVariableKHR %ptr Workgroup %struct +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +%res = OpPtrDiff %int %v1 %v1 +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4)); +} + +TEST_F(ValidateMemory, UntypedVariableVulkanPushConstantGood) { + 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 +OpMemberDecorate %struct 0 Offset 0 +%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_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0)); +} + +TEST_F(ValidateMemory, UntypedVariableVulkanStorageBufferGood) { + 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 +OpDecorate %struct Block +OpMemberDecorate %struct 0 Offset 0 +OpDecorate %var DescriptorSet 0 +OpDecorate %var Binding 0 +%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_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0)); +} + +TEST_F(ValidateMemory, UntypedVariableVulkanUniformGood) { + 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 +OpDecorate %struct Block +OpMemberDecorate %struct 0 Offset 0 +OpDecorate %var DescriptorSet 0 +OpDecorate %var Binding 0 +%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_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0)); +} + +TEST_F(ValidateMemory, UntypedVariableVulkanWorkgroupGood) { + 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" +OpExecutionMode %main LocalSize 1 1 1 +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_VULKAN_1_1_SPIRV_1_4); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1_SPIRV_1_4)); +} + +TEST_F(ValidateMemory, UntypedPointerAsVariableType) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpCapability VariablePointers +OpCapability UntypedPointersKHR +OpExtension "SPV_KHR_untyped_pointers" +OpMemoryModel Logical GLSL450 +%void = OpTypeVoid +%float = OpTypeFloat 32 +%ptr = OpTypeUntypedPointerKHR StorageBuffer +%priv_ptr = OpTypePointer Private %ptr +%var = OpVariable %priv_ptr Private +)"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); +} + +TEST_F(ValidateMemory, UntypedArrayLengthGood) { + 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 %var DescriptorSet 0 +OpDecorate %var Binding 0 +OpDecorate %block Block +OpMemberDecorate %block 0 Offset 0 +OpDecorate %array ArrayStride 4 +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%array = OpTypeRuntimeArray %int +%block = OpTypeStruct %array +%ptr = OpTypeUntypedPointerKHR StorageBuffer +%var = OpUntypedVariableKHR %ptr StorageBuffer %block +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +%length = OpUntypedArrayLengthKHR %int %block %var 0 +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); +} + +TEST_F(ValidateMemory, UntypedArrayLengthBadResultType) { + 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 %var DescriptorSet 0 +OpDecorate %var Binding 0 +OpDecorate %block Block +OpMemberDecorate %block 0 Offset 0 +OpDecorate %array ArrayStride 4 +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%float = OpTypeFloat 32 +%array = OpTypeRuntimeArray %int +%block = OpTypeStruct %array +%ptr = OpTypeUntypedPointerKHR StorageBuffer +%var = OpUntypedVariableKHR %ptr StorageBuffer %block +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +%length = OpUntypedArrayLengthKHR %float %block %var 0 +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("must be OpTypeInt with width 32 and signedness 0")); +} + +TEST_F(ValidateMemory, UntypedArrayLengthBadPointer) { + 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 %var DescriptorSet 0 +OpDecorate %var Binding 0 +OpDecorate %block Block +OpMemberDecorate %block 0 Offset 0 +OpDecorate %array ArrayStride 4 +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%array = OpTypeRuntimeArray %int +%block = OpTypeStruct %array +%ptr = OpTypeUntypedPointerKHR StorageBuffer +%typed_ptr = OpTypePointer StorageBuffer %block +%var = OpVariable %typed_ptr StorageBuffer +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +%length = OpUntypedArrayLengthKHR %int %block %var 0 +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Pointer must be an untyped pointer")); +} + +TEST_F(ValidateMemory, UntypedArrayLengtBadStruct) { + 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 %var DescriptorSet 0 +OpDecorate %var Binding 0 +OpDecorate %block Block +OpMemberDecorate %block 0 Offset 0 +OpDecorate %array ArrayStride 4 +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%array = OpTypeRuntimeArray %int +%block = OpTypeStruct %array +%ptr = OpTypeUntypedPointerKHR StorageBuffer +%var = OpUntypedVariableKHR %ptr StorageBuffer %block +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +%length = OpUntypedArrayLengthKHR %int %int %var 0 +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); + EXPECT_THAT(getDiagnosticString(), HasSubstr("to an OpTypeStruct")); +} + +TEST_F(ValidateMemory, UntypedArrayLengthLastMemberNotArray) { + 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 %var DescriptorSet 0 +OpDecorate %var Binding 0 +OpDecorate %block Block +OpMemberDecorate %block 0 Offset 0 +OpDecorate %array ArrayStride 4 +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%array = OpTypeRuntimeArray %int +%block = OpTypeStruct %int +%ptr = OpTypeUntypedPointerKHR StorageBuffer +%var = OpUntypedVariableKHR %ptr StorageBuffer %block +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +%length = OpUntypedArrayLengthKHR %int %block %var 0 +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("must be an OpTypeRuntimeArray")); +} + +TEST_F(ValidateMemory, UntypedArrayLengthBadIndex) { + 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 %var DescriptorSet 0 +OpDecorate %var Binding 0 +OpDecorate %block Block +OpMemberDecorate %block 0 Offset 0 +OpDecorate %array ArrayStride 4 +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%array = OpTypeRuntimeArray %int +%block = OpTypeStruct %array +%ptr = OpTypeUntypedPointerKHR StorageBuffer +%var = OpUntypedVariableKHR %ptr StorageBuffer %block +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +%length = OpUntypedArrayLengthKHR %int %block %var 1 +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("must be the last member of the struct")); +} + +TEST_F(ValidateMemory, UntypedCooperativeMatrixLoad) { + const std::string spirv = R"( +OpCapability Shader +OpCapability UntypedPointersKHR +OpCapability CooperativeMatrixKHR +OpCapability VulkanMemoryModel +OpExtension "SPV_KHR_untyped_pointers" +OpExtension "SPV_KHR_cooperative_matrix" +OpMemoryModel Logical Vulkan +OpEntryPoint GLCompute %main "main" %var +OpExecutionMode %main LocalSize 1 1 1 +OpDecorate %var DescriptorSet 0 +OpDecorate %var Binding 0 +OpDecorate %block Block +OpMemberDecorate %block 0 Offset 0 +OpDecorate %array ArrayStride 4 +%void = OpTypeVoid +%void_fn = OpTypeFunction %void +%untyped = OpTypeUntypedPointerKHR StorageBuffer +%float = OpTypeFloat 32 +%array = OpTypeRuntimeArray %float +%block = OpTypeStruct %array +%int = OpTypeInt 32 0 +%int_0 = OpConstant %int 0 +%subgroup = OpConstant %int 3 +%rows = OpSpecConstant %int 1 +%cols = OpSpecConstant %int 1 +%matrix_a = OpConstant %int 1 +%matrix = OpTypeCooperativeMatrixKHR %float %subgroup %rows %cols %matrix_a +%var = OpUntypedVariableKHR %untyped StorageBuffer %block +%main = OpFunction %void None %void_fn +%entry = OpLabel +%ld = OpCooperativeMatrixLoadKHR %matrix %var %int_0 +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_3); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_3)); +} + +TEST_F(ValidateMemory, UntypedCooperativeMatrixLoad2) { + const std::string spirv = R"( +OpCapability Shader +OpCapability UntypedPointersKHR +OpCapability CooperativeMatrixKHR +OpCapability VulkanMemoryModel +OpExtension "SPV_KHR_untyped_pointers" +OpExtension "SPV_KHR_cooperative_matrix" +OpMemoryModel Logical Vulkan +OpEntryPoint GLCompute %main "main" %var +OpExecutionMode %main LocalSize 1 1 1 +OpDecorate %var DescriptorSet 0 +OpDecorate %var Binding 0 +OpDecorate %block Block +OpMemberDecorate %block 0 Offset 0 +OpDecorate %array ArrayStride 4 +%void = OpTypeVoid +%void_fn = OpTypeFunction %void +%untyped = OpTypeUntypedPointerKHR StorageBuffer +%float = OpTypeFloat 32 +%array = OpTypeRuntimeArray %float +%block = OpTypeStruct %array +%int = OpTypeInt 32 0 +%int_0 = OpConstant %int 0 +%subgroup = OpConstant %int 3 +%rows = OpSpecConstant %int 1 +%cols = OpSpecConstant %int 1 +%matrix_a = OpConstant %int 1 +%matrix = OpTypeCooperativeMatrixKHR %float %subgroup %rows %cols %matrix_a +%var = OpUntypedVariableKHR %untyped StorageBuffer %block +%main = OpFunction %void None %void_fn +%entry = OpLabel +%gep = OpUntypedAccessChainKHR %untyped %block %var %int_0 %int_0 +%ld = OpCooperativeMatrixLoadKHR %matrix %gep %int_0 +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_3); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_3)); +} + +TEST_F(ValidateMemory, UntypedCooperativeMatrixStore) { + const std::string spirv = R"( +OpCapability Shader +OpCapability UntypedPointersKHR +OpCapability CooperativeMatrixKHR +OpCapability VulkanMemoryModel +OpExtension "SPV_KHR_untyped_pointers" +OpExtension "SPV_KHR_cooperative_matrix" +OpMemoryModel Logical Vulkan +OpEntryPoint GLCompute %main "main" %var1 %var2 +OpExecutionMode %main LocalSize 1 1 1 +OpDecorate %var1 DescriptorSet 0 +OpDecorate %var1 Binding 0 +OpDecorate %var2 DescriptorSet 0 +OpDecorate %var2 Binding 1 +OpDecorate %block Block +OpMemberDecorate %block 0 Offset 0 +OpDecorate %array ArrayStride 4 +%void = OpTypeVoid +%void_fn = OpTypeFunction %void +%untyped = OpTypeUntypedPointerKHR StorageBuffer +%float = OpTypeFloat 32 +%array = OpTypeRuntimeArray %float +%block = OpTypeStruct %array +%ptr = OpTypePointer StorageBuffer %block +%ptr_float = OpTypePointer StorageBuffer %float +%int = OpTypeInt 32 0 +%int_0 = OpConstant %int 0 +%subgroup = OpConstant %int 3 +%rows = OpSpecConstant %int 1 +%cols = OpSpecConstant %int 1 +%matrix_a = OpConstant %int 1 +%matrix = OpTypeCooperativeMatrixKHR %float %subgroup %rows %cols %matrix_a +%var1 = OpVariable %ptr StorageBuffer +%var2 = OpUntypedVariableKHR %untyped StorageBuffer %block +%main = OpFunction %void None %void_fn +%entry = OpLabel +%gep = OpAccessChain %ptr_float %var1 %int_0 %int_0 +%ld = OpCooperativeMatrixLoadKHR %matrix %gep %int_0 +OpCooperativeMatrixStoreKHR %var2 %ld %int_0 +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_3); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_3)); +} + +TEST_F(ValidateMemory, UntypedCooperativeMatrixStore2) { + const std::string spirv = R"( +OpCapability Shader +OpCapability UntypedPointersKHR +OpCapability CooperativeMatrixKHR +OpCapability VulkanMemoryModel +OpExtension "SPV_KHR_untyped_pointers" +OpExtension "SPV_KHR_cooperative_matrix" +OpMemoryModel Logical Vulkan +OpEntryPoint GLCompute %main "main" %var1 %var2 +OpExecutionMode %main LocalSize 1 1 1 +OpDecorate %var1 DescriptorSet 0 +OpDecorate %var1 Binding 0 +OpDecorate %var2 DescriptorSet 0 +OpDecorate %var2 Binding 1 +OpDecorate %block Block +OpMemberDecorate %block 0 Offset 0 +OpDecorate %array ArrayStride 4 +%void = OpTypeVoid +%void_fn = OpTypeFunction %void +%untyped = OpTypeUntypedPointerKHR StorageBuffer +%float = OpTypeFloat 32 +%array = OpTypeRuntimeArray %float +%block = OpTypeStruct %array +%ptr = OpTypePointer StorageBuffer %block +%ptr_float = OpTypePointer StorageBuffer %float +%int = OpTypeInt 32 0 +%int_0 = OpConstant %int 0 +%subgroup = OpConstant %int 3 +%rows = OpSpecConstant %int 1 +%cols = OpSpecConstant %int 1 +%matrix_a = OpConstant %int 1 +%matrix = OpTypeCooperativeMatrixKHR %float %subgroup %rows %cols %matrix_a +%var1 = OpVariable %ptr StorageBuffer +%var2 = OpUntypedVariableKHR %untyped StorageBuffer %block +%main = OpFunction %void None %void_fn +%entry = OpLabel +%gep = OpAccessChain %ptr_float %var1 %int_0 %int_0 +%ld = OpCooperativeMatrixLoadKHR %matrix %gep %int_0 +%gep2 = OpUntypedAccessChainKHR %untyped %block %var2 %int_0 %int_0 +OpCooperativeMatrixStoreKHR %gep2 %ld %int_0 +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_3); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_3)); +} + } // namespace } // namespace val } // namespace spvtools diff --git a/test/val/val_storage_test.cpp b/test/val/val_storage_test.cpp index 6a3e4bdb26..d4170e6dc7 100644 --- a/test/val/val_storage_test.cpp +++ b/test/val/val_storage_test.cpp @@ -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) { diff --git a/test/val/val_type_unique_test.cpp b/test/val/val_type_unique_test.cpp index 31ad3a6597..00e2e7f14f 100644 --- a/test/val/val_type_unique_test.cpp +++ b/test/val/val_type_unique_test.cpp @@ -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