From 47a3a1f4f0ba164cf91db60d18a6bd6816fa94b2 Mon Sep 17 00:00:00 2001 From: spencer-lunarg Date: Sat, 17 Jan 2026 15:47:04 -0500 Subject: [PATCH 1/3] spirv-val: Add OpSizeOf --- source/val/validate_misc.cpp | 14 ++++++++++ test/val/val_misc_test.cpp | 51 ++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/source/val/validate_misc.cpp b/source/val/validate_misc.cpp index a404134b6e..92d7af7945 100644 --- a/source/val/validate_misc.cpp +++ b/source/val/validate_misc.cpp @@ -82,6 +82,15 @@ spv_result_t ValidateShaderClock(ValidationState_t& _, return SPV_SUCCESS; } +spv_result_t ValidateSizeOf(ValidationState_t& _, const Instruction* inst) { + const uint32_t result_type = inst->type_id(); + if (!_.IsIntScalarType(result_type, 32)) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "Expected OpSizeOf Result Type to be a 32-bit int scalar."; + } + return SPV_SUCCESS; +} + spv_result_t ValidateAssumeTrue(ValidationState_t& _, const Instruction* inst) { const auto operand_type_id = _.GetOperandTypeId(inst, 0); if (!operand_type_id || !_.IsBoolScalarType(operand_type_id)) { @@ -193,6 +202,11 @@ spv_result_t MiscPass(ValidationState_t& _, const Instruction* inst) { return error; } break; + case spv::Op::OpSizeOf: + if (auto error = ValidateSizeOf(_, inst)) { + return error; + } + break; case spv::Op::OpAssumeTrueKHR: if (auto error = ValidateAssumeTrue(_, inst)) { return error; diff --git a/test/val/val_misc_test.cpp b/test/val/val_misc_test.cpp index f54b20cdcf..ffc71fccee 100644 --- a/test/val/val_misc_test.cpp +++ b/test/val/val_misc_test.cpp @@ -105,6 +105,57 @@ TEST_F(ValidateMisc, SizeOfValid) { EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_1)); } +TEST_F(ValidateMisc, SizeOfFloat) { + const std::string spirv = R"( + OpCapability Addresses + OpCapability Kernel + OpMemoryModel Physical64 OpenCL + OpEntryPoint Kernel %f "f" + %void = OpTypeVoid + %f32 = OpTypeFloat 32 + %ptr = OpTypePointer CrossWorkgroup %f32 + %fnTy = OpTypeFunction %void + %f = OpFunction %void None %fnTy + %entry = OpLabel + %s = OpSizeOf %f32 %ptr + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_1); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_1)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected OpSizeOf Result Type to be a 32-bit int scalar")); +} + +TEST_F(ValidateMisc, SizeOfVector) { + const std::string spirv = R"( + OpCapability Addresses + OpCapability Kernel + OpMemoryModel Physical64 OpenCL + OpEntryPoint Kernel %f "f" + %void = OpTypeVoid + %i32 = OpTypeInt 32 0 + %v2i32 = OpTypeVector %i32 2 + %ptr = OpTypePointer CrossWorkgroup %v2i32 + %fnTy = OpTypeFunction %void + %f = OpFunction %void None %fnTy + %entry = OpLabel + %s = OpSizeOf %v2i32 %ptr + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_1); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_1)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected OpSizeOf Result Type to be a 32-bit int scalar")); +} + const std::string ShaderClockSpirv = R"( OpCapability Shader OpCapability Int64 From 9633b7601fd04d147eea9d95c244342d5303d31f Mon Sep 17 00:00:00 2001 From: spencer-lunarg Date: Sat, 17 Jan 2026 22:16:57 -0500 Subject: [PATCH 2/3] spirv-val: Add Concrete pointer check --- source/val/validate_misc.cpp | 7 ++++ source/val/validation_state.cpp | 66 ++++++++++++++++++++++++++++----- source/val/validation_state.h | 3 ++ test/val/val_misc_test.cpp | 50 +++++++++++++++++++++++++ 4 files changed, 116 insertions(+), 10 deletions(-) diff --git a/source/val/validate_misc.cpp b/source/val/validate_misc.cpp index 92d7af7945..f63fc66782 100644 --- a/source/val/validate_misc.cpp +++ b/source/val/validate_misc.cpp @@ -88,6 +88,13 @@ spv_result_t ValidateSizeOf(ValidationState_t& _, const Instruction* inst) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Expected OpSizeOf Result Type to be a 32-bit int scalar."; } + + uint32_t pointer_id = inst->GetOperandAs(2); + if (!_.IsConcreteType(pointer_id)) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "OpSizeOf Pointer operand is not concrete."; + } + return SPV_SUCCESS; } diff --git a/source/val/validation_state.cpp b/source/val/validation_state.cpp index 8e9e617bb6..bfee48d359 100644 --- a/source/val/validation_state.cpp +++ b/source/val/validation_state.cpp @@ -30,6 +30,7 @@ #include "source/val/construct.h" #include "source/val/function.h" #include "spirv-tools/libspirv.h" +#include "spirv/unified1/spirv.hpp11" namespace spvtools { namespace val { @@ -927,22 +928,16 @@ uint32_t ValidationState_t::GetComponentType(uint32_t id) const { case spv::Op::OpTypeArray: case spv::Op::OpTypeRuntimeArray: - return inst->word(2); - case spv::Op::OpTypeVector: - return inst->word(2); - - case spv::Op::OpTypeMatrix: - return GetComponentType(inst->word(2)); - + case spv::Op::OpTypeVectorIdEXT: case spv::Op::OpTypeCooperativeMatrixNV: case spv::Op::OpTypeCooperativeMatrixKHR: - case spv::Op::OpTypeVectorIdEXT: - return inst->word(2); - case spv::Op::OpTypeTensorARM: return inst->word(2); + case spv::Op::OpTypeMatrix: + return GetComponentType(inst->word(2)); + default: break; } @@ -1632,6 +1627,57 @@ bool ValidationState_t::IsDescriptorHeapBaseVariable(const Instruction* inst) { is_heap_base); } +// From the spec (SPIRV.html#PhysicalPointerType) +bool ValidationState_t::IsPhysicalPointerType(uint32_t id) const { + const Instruction* inst = FindDef(id); + const spv::Op opcode = inst->opcode(); + if (opcode != spv::Op::OpTypePointer && + opcode != spv::Op::OpTypeUntypedPointerKHR) { + return false; + } + + const spv::AddressingModel am = addressing_model(); + if (am == spv::AddressingModel::Logical) { + return false; + } else if (am == spv::AddressingModel::Physical32 || + am == spv::AddressingModel::Physical64) { + return true; + } else if (am == spv::AddressingModel::PhysicalStorageBuffer64) { + const spv::StorageClass storage_class = spv::StorageClass(inst->word(2)); + return storage_class == spv::StorageClass::PhysicalStorageBuffer; + } + + assert(0); + return false; +} + +// From the spec (SPIRV.html#Numerical) +bool ValidationState_t::IsNumericalType(uint32_t id) const { + const Instruction* inst = FindDef(id); + const spv::Op opcode = inst->opcode(); + return opcode == spv::Op::OpTypeInt || opcode == spv::Op::OpTypeFloat; +} + +// From the spec (SPIRV.html#Concrete) +bool ValidationState_t::IsConcreteType(uint32_t id) const { + const Instruction* inst = FindDef(id); + const spv::Op opcode = inst->opcode(); + + if (opcode == spv::Op::OpTypeStruct) { + // all elements must be concrete + for (uint32_t i = 1; i < inst->operands().size(); ++i) { + if (!IsConcreteType(inst->GetOperandAs(i))) { + return false; + } + } + return true; + } + + const uint32_t component_type = GetComponentType(id); + return IsNumericalType(component_type) || + IsPhysicalPointerType(component_type); +} + spv_result_t ValidationState_t::CooperativeMatrixShapesMatch( const Instruction* inst, uint32_t result_type_id, uint32_t m2, bool is_conversion, bool swap_row_col) { diff --git a/source/val/validation_state.h b/source/val/validation_state.h index 560122433b..07d6e96f73 100644 --- a/source/val/validation_state.h +++ b/source/val/validation_state.h @@ -733,6 +733,9 @@ class ValidationState_t { bool IsTensorType(uint32_t id) const; bool IsDescriptorType(spv::Op opcode) const; bool IsDescriptorType(uint32_t id) const; + bool IsPhysicalPointerType(uint32_t id) const; + bool IsNumericalType(uint32_t id) const; + bool IsConcreteType(uint32_t id) const; // When |length| is not 0, return true only if the array length is equal to // |length| and the array length is not defined by a specialization constant. bool IsArrayType(uint32_t id, uint64_t length = 0) const; diff --git a/test/val/val_misc_test.cpp b/test/val/val_misc_test.cpp index ffc71fccee..41674a5146 100644 --- a/test/val/val_misc_test.cpp +++ b/test/val/val_misc_test.cpp @@ -105,6 +105,56 @@ TEST_F(ValidateMisc, SizeOfValid) { EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_1)); } +TEST_F(ValidateMisc, SizeOfStructValid) { + const std::string spirv = R"( + OpCapability Addresses + OpCapability Kernel + OpMemoryModel Physical64 OpenCL + OpEntryPoint Kernel %f "f" + %void = OpTypeVoid + %i32 = OpTypeInt 32 0 + %ptr = OpTypePointer CrossWorkgroup %i32 + %struct_a = OpTypeStruct %ptr %i32 + %struct_b = OpTypeStruct %struct_a %ptr + %fnTy = OpTypeFunction %void + %f = OpFunction %void None %fnTy + %entry = OpLabel + %s = OpSizeOf %i32 %struct_b + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_1); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_1)); +} + +TEST_F(ValidateMisc, SizeOfStructWithAbstract) { + const std::string spirv = R"( + OpCapability Addresses + OpCapability Kernel + OpMemoryModel Physical64 OpenCL + OpEntryPoint Kernel %f "f" + %void = OpTypeVoid + %i32 = OpTypeInt 32 0 + %bool = OpTypeBool + %ptr = OpTypePointer CrossWorkgroup %i32 + %struct_a = OpTypeStruct %ptr %bool + %struct_b = OpTypeStruct %struct_a %ptr + %fnTy = OpTypeFunction %void + %f = OpFunction %void None %fnTy + %entry = OpLabel + %s = OpSizeOf %i32 %struct_b + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_1); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_1)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpSizeOf Pointer operand is not concrete")); +} + TEST_F(ValidateMisc, SizeOfFloat) { const std::string spirv = R"( OpCapability Addresses From c3e05853b2a23b019b1b1653b14eda8b7cc1163e Mon Sep 17 00:00:00 2001 From: spencer-lunarg Date: Sat, 24 Jan 2026 19:46:29 -0500 Subject: [PATCH 3/3] Ban untyped pointers in OpSizeOf --- source/val/validate_misc.cpp | 5 +++++ test/val/val_misc_test.cpp | 27 +++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/source/val/validate_misc.cpp b/source/val/validate_misc.cpp index f63fc66782..cb18d0ec42 100644 --- a/source/val/validate_misc.cpp +++ b/source/val/validate_misc.cpp @@ -93,6 +93,11 @@ spv_result_t ValidateSizeOf(ValidationState_t& _, const Instruction* inst) { if (!_.IsConcreteType(pointer_id)) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "OpSizeOf Pointer operand is not concrete."; + } else if (_.FindDef(pointer_id)->opcode() == + spv::Op::OpTypeUntypedPointerKHR) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "OpSizeOf Pointer operand is to an untyped pointer, which size " + "is not well defined."; } return SPV_SUCCESS; diff --git a/test/val/val_misc_test.cpp b/test/val/val_misc_test.cpp index 41674a5146..4d52ba0c7e 100644 --- a/test/val/val_misc_test.cpp +++ b/test/val/val_misc_test.cpp @@ -155,6 +155,33 @@ TEST_F(ValidateMisc, SizeOfStructWithAbstract) { HasSubstr("OpSizeOf Pointer operand is not concrete")); } +TEST_F(ValidateMisc, SizeOfUntyped) { + const std::string spirv = R"( + OpCapability Addresses + OpCapability UntypedPointersKHR + OpCapability Kernel + OpExtension "SPV_KHR_untyped_pointers" + OpMemoryModel Physical64 OpenCL + OpEntryPoint Kernel %f "f" + %void = OpTypeVoid + %i32 = OpTypeInt 32 0 + %fnTy = OpTypeFunction %void +%untyped_ptr = OpTypeUntypedPointerKHR CrossWorkgroup + %f = OpFunction %void None %fnTy + %entry = OpLabel + %s = OpSizeOf %i32 %untyped_ptr + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_1); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_1)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpSizeOf Pointer operand is to an untyped pointer, " + "which size is not well defined")); +} + TEST_F(ValidateMisc, SizeOfFloat) { const std::string spirv = R"( OpCapability Addresses