From dcd41f1f81bb15f828a3ae188bffcfc43f5cdf65 Mon Sep 17 00:00:00 2001 From: Abir Deol Date: Sun, 25 Oct 2026 13:04:08 -0700 Subject: [PATCH 1/8] [SPIR-V] Reject non-bool SV_CullPrimitive The DXIL path rejects a non-bool SV_CullPrimitive with "SV_CullPrimitive must be bool.", but the SPIR-V backend passed the declared type through to the CullPrimitiveEXT builtin, so spirv-val failed with VUID-CullPrimitiveEXT-CullPrimitiveEXT-07036 and DXC asked the user to file a bug. Emit an error for a non-bool SV_CullPrimitive, and make meshshading.ext.triangle.mesh.hlsl declare it as bool. That test only passed with an int because it runs with -fcgl. --- docs/ReleaseNotes.md | 2 ++ tools/clang/lib/SPIRV/DeclResultIdMapper.cpp | 7 ++++++ ...eshshading.ext.cullprimitive.non-bool.hlsl | 23 +++++++++++++++++++ .../meshshading.ext.triangle.mesh.hlsl | 12 +++++----- 4 files changed, 38 insertions(+), 6 deletions(-) create mode 100644 tools/clang/test/CodeGenSPIRV/meshshading.ext.cullprimitive.non-bool.hlsl diff --git a/docs/ReleaseNotes.md b/docs/ReleaseNotes.md index b48054bf209..672c405c475 100644 --- a/docs/ReleaseNotes.md +++ b/docs/ReleaseNotes.md @@ -25,6 +25,8 @@ The included licenses apply to the following files: buffer-like resources (`StructuredBuffer`, `ByteAddressBuffer`, `ConstantBuffer`, `TextureBuffer`, and RW variants) via `SPV_KHR_untyped_pointers`. Requires `-fspv-target-env=vulkan1.3`. +- `SV_CullPrimitive` declared with a non-bool type is now an error, matching + DXIL, instead of producing invalid SPIR-V. Place release notes for the upcoming release below this line and remove this line upon naming the release. Refer to previous for appropriate section names. diff --git a/tools/clang/lib/SPIRV/DeclResultIdMapper.cpp b/tools/clang/lib/SPIRV/DeclResultIdMapper.cpp index 9aaa36c97ba..7b54d3b6749 100644 --- a/tools/clang/lib/SPIRV/DeclResultIdMapper.cpp +++ b/tools/clang/lib/SPIRV/DeclResultIdMapper.cpp @@ -4772,6 +4772,13 @@ SpirvVariable *DeclResultIdMapper::createSpirvStageVar( // According to Vulkan spec, the CullPrimitiveEXT BuiltIn can only // be used as MSOut. case hlsl::Semantic::Kind::CullPrimitive: { + QualType elemType = type; + while (const auto *arrayType = astContext.getAsConstantArrayType(elemType)) + elemType = arrayType->getElementType(); + if (!elemType->isBooleanType()) { + emitError("semantic CullPrimitive must be bool", srcLoc); + return nullptr; + } switch (sigPointKind) { case hlsl::SigPoint::Kind::PSIn: stageVar->setIsSpirvBuiltin(); diff --git a/tools/clang/test/CodeGenSPIRV/meshshading.ext.cullprimitive.non-bool.hlsl b/tools/clang/test/CodeGenSPIRV/meshshading.ext.cullprimitive.non-bool.hlsl new file mode 100644 index 00000000000..dd442fb272a --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/meshshading.ext.cullprimitive.non-bool.hlsl @@ -0,0 +1,23 @@ +// RUN: not %dxc -T ms_6_5 -fspv-target-env=vulkan1.3 -E main -fcgl %s -spirv 2>&1 | FileCheck %s + +struct MeshPerVertex { + float4 position : SV_Position; +}; + +struct MeshPerPrimitive { +// CHECK: :[[@LINE+1]]:15: error: semantic CullPrimitive must be bool + uint cull : SV_CullPrimitive; +}; + +[outputtopology("triangle")] +[numthreads(1, 1, 1)] +void main(out vertices MeshPerVertex verts[3], + out indices uint3 tris[1], + out primitives MeshPerPrimitive prims[1]) { + SetMeshOutputCounts(3, 1); + verts[0].position = 0; + verts[1].position = 0; + verts[2].position = 0; + tris[0] = uint3(0, 1, 2); + prims[0].cull = 1; +} diff --git a/tools/clang/test/CodeGenSPIRV/meshshading.ext.triangle.mesh.hlsl b/tools/clang/test/CodeGenSPIRV/meshshading.ext.triangle.mesh.hlsl index 904c45c69fe..81df96ef511 100644 --- a/tools/clang/test/CodeGenSPIRV/meshshading.ext.triangle.mesh.hlsl +++ b/tools/clang/test/CodeGenSPIRV/meshshading.ext.triangle.mesh.hlsl @@ -49,7 +49,7 @@ // CHECK: %gl_PrimitiveID = OpVariable %_ptr_Output__arr_int_uint_81 Output // CHECK: %gl_Layer = OpVariable %_ptr_Output__arr_int_uint_81 Output // CHECK: %gl_ViewportIndex = OpVariable %_ptr_Output__arr_int_uint_81 Output -// CHECK: [[cullprim]] = OpVariable %_ptr_Output__arr_int_uint_81 Output +// CHECK: [[cullprim]] = OpVariable %_ptr_Output__arr_bool_uint_81 Output // CHECK: [[primshadingrate]] = OpVariable %_ptr_Output__arr_uint_uint_81 Output // CHECK: %out_var_PRIM_USER = OpVariable %_ptr_Output__arr_v3float_uint_81 Output // CHECK: %out_var_PRIM_USER_ARR = OpVariable %_ptr_Output__arr__arr_v4float_uint_2_uint_81 Output @@ -70,7 +70,7 @@ struct MeshPerPrimitive { int primId : SV_PrimitiveID; // -> Builtin PrimitiveId int layer : SV_RenderTargetArrayIndex; // -> Builtin Layer int vpIdx : SV_ViewportArrayIndex; // -> Builtin ViewportIndex - int cullPrim : SV_CullPrimitive; // -> BUiltin CullPrimitiveEXT + bool cullPrim : SV_CullPrimitive; // -> BUiltin CullPrimitiveEXT uint shadingRate : SV_ShadingRate; // -> Builtin PrimitiveShadingRateEXT float3 userPrimAttr : PRIM_USER; float4 userPrimAttrArr[2] : PRIM_USER_ARR; @@ -198,9 +198,9 @@ void main( // CHECK: OpAccessChain %_ptr_Output_int %gl_ViewportIndex {{%[0-9]+}} // CHECK: OpStore {{%[0-9]+}} %int_12 prims[tig].vpIdx = 12; -// CHECK: OpAccessChain %_ptr_Output_int [[cullprim]] {{%[0-9]+}} -// CHECK: OpStore {{%[0-9]+}} %int_13 - prims[tig].cullPrim = 13; +// CHECK: OpAccessChain %_ptr_Output_bool [[cullprim]] {{%[0-9]+}} +// CHECK: OpStore {{%[0-9]+}} %true + prims[tig].cullPrim = true; // CHECK: OpAccessChain %_ptr_Output_uint [[primshadingrate]] {{%[0-9]+}} // CHECK: OpStore {{%[0-9]+}} %uint_14 prims[tig].shadingRate = 14; @@ -220,7 +220,7 @@ void main( prim.primId = 10; prim.layer = 11; prim.vpIdx = 12; - prim.cullPrim = 1; + prim.cullPrim = true; prim.shadingRate = 0xa; prim.userPrimAttrArr[0] = float4(4.0,5.0,6.0,7.0); prim.userPrimAttrArr[1] = float4(8.0,9.0,10.0,11.0); From 376c90377aac21d05e9e86dd1c2110fc82c74ce7 Mon Sep 17 00:00:00 2001 From: Abir Deol Date: Wed, 28 Oct 2026 17:52:49 -0700 Subject: [PATCH 2/8] [SPIR-V] Only check SV_CullPrimitive type on mesh primitive outputs DXIL replaces SV_CullPrimitive on pixel shader inputs with a constant 0, so only MSPOut has to be bool. Check the type before the per-primitive array is added, so a bool[2] is rejected like DXIL does, and accept bool1 and bool1x1. --- docs/ReleaseNotes.md | 4 +-- tools/clang/lib/SPIRV/DeclResultIdMapper.cpp | 20 +++++++++----- .../meshshading.ext.cullprimitive.array.hlsl | 23 ++++++++++++++++ .../meshshading.ext.cullprimitive.bool1.hlsl | 26 +++++++++++++++++++ ...eshshading.ext.cullprimitive.non-bool.hlsl | 2 +- .../meshshading.ext.triangle.mesh.hlsl | 2 +- 6 files changed, 66 insertions(+), 11 deletions(-) create mode 100644 tools/clang/test/CodeGenSPIRV/meshshading.ext.cullprimitive.array.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/meshshading.ext.cullprimitive.bool1.hlsl diff --git a/docs/ReleaseNotes.md b/docs/ReleaseNotes.md index 672c405c475..b69ce2f0c2c 100644 --- a/docs/ReleaseNotes.md +++ b/docs/ReleaseNotes.md @@ -25,8 +25,8 @@ The included licenses apply to the following files: buffer-like resources (`StructuredBuffer`, `ByteAddressBuffer`, `ConstantBuffer`, `TextureBuffer`, and RW variants) via `SPV_KHR_untyped_pointers`. Requires `-fspv-target-env=vulkan1.3`. -- `SV_CullPrimitive` declared with a non-bool type is now an error, matching - DXIL, instead of producing invalid SPIR-V. +- `SV_CullPrimitive` on a mesh shader primitive output must now be a scalar + bool, matching DXIL, instead of producing invalid SPIR-V. Place release notes for the upcoming release below this line and remove this line upon naming the release. Refer to previous for appropriate section names. diff --git a/tools/clang/lib/SPIRV/DeclResultIdMapper.cpp b/tools/clang/lib/SPIRV/DeclResultIdMapper.cpp index 7b54d3b6749..fd182102e71 100644 --- a/tools/clang/lib/SPIRV/DeclResultIdMapper.cpp +++ b/tools/clang/lib/SPIRV/DeclResultIdMapper.cpp @@ -3609,6 +3609,19 @@ DeclResultIdMapper::getBaseVertexVariable(const hlsl::SigPoint *sigPoint, SpirvVariable *DeclResultIdMapper::createSpirvInterfaceVariable( const StageVarDataBundle &stageVarData) { + // DXIL only checks the SV_CullPrimitive type for mesh primitive outputs. + // PS inputs are replaced with a constant 0, so any type is accepted there. + if (stageVarData.semantic->getKind() == hlsl::Semantic::Kind::CullPrimitive && + stageVarData.sigPoint->GetKind() == hlsl::SigPoint::Kind::MSPOut) { + QualType scalarType; + if (!isScalarType(stageVarData.type, &scalarType) || + !scalarType->isBooleanType()) { + emitError("semantic CullPrimitive must be a bool scalar", + stageVarData.semantic->loc); + return nullptr; + } + } + // The evalType will be the type of the interface variable in SPIR-V. // The type of the variable used in the body of the function will still be // `stageVarData.type`. @@ -4772,13 +4785,6 @@ SpirvVariable *DeclResultIdMapper::createSpirvStageVar( // According to Vulkan spec, the CullPrimitiveEXT BuiltIn can only // be used as MSOut. case hlsl::Semantic::Kind::CullPrimitive: { - QualType elemType = type; - while (const auto *arrayType = astContext.getAsConstantArrayType(elemType)) - elemType = arrayType->getElementType(); - if (!elemType->isBooleanType()) { - emitError("semantic CullPrimitive must be bool", srcLoc); - return nullptr; - } switch (sigPointKind) { case hlsl::SigPoint::Kind::PSIn: stageVar->setIsSpirvBuiltin(); diff --git a/tools/clang/test/CodeGenSPIRV/meshshading.ext.cullprimitive.array.hlsl b/tools/clang/test/CodeGenSPIRV/meshshading.ext.cullprimitive.array.hlsl new file mode 100644 index 00000000000..4e9367e8ad6 --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/meshshading.ext.cullprimitive.array.hlsl @@ -0,0 +1,23 @@ +// RUN: not %dxc -T ms_6_5 -fspv-target-env=vulkan1.3 -E main -fcgl %s -spirv 2>&1 | FileCheck %s + +struct MeshPerVertex { + float4 position : SV_Position; +}; + +struct MeshPerPrimitive { +// CHECK: :[[@LINE+1]]:18: error: semantic CullPrimitive must be a bool scalar + bool cull[2] : SV_CullPrimitive; +}; + +[outputtopology("triangle")] +[numthreads(1, 1, 1)] +void main(out vertices MeshPerVertex verts[3], + out indices uint3 tris[1], + out primitives MeshPerPrimitive prims[1]) { + SetMeshOutputCounts(3, 1); + verts[0].position = 0; + verts[1].position = 0; + verts[2].position = 0; + tris[0] = uint3(0, 1, 2); + prims[0].cull[0] = true; +} diff --git a/tools/clang/test/CodeGenSPIRV/meshshading.ext.cullprimitive.bool1.hlsl b/tools/clang/test/CodeGenSPIRV/meshshading.ext.cullprimitive.bool1.hlsl new file mode 100644 index 00000000000..62ead1eda67 --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/meshshading.ext.cullprimitive.bool1.hlsl @@ -0,0 +1,26 @@ +// RUN: %dxc -T ms_6_5 -fspv-target-env=vulkan1.3 -E main -fcgl %s -spirv -DCULL_TYPE=bool1 | FileCheck %s +// RUN: %dxc -T ms_6_5 -fspv-target-env=vulkan1.3 -E main -fcgl %s -spirv -DCULL_TYPE=bool1x1 | FileCheck %s + +// CHECK: OpDecorate [[cullprim:%[0-9]+]] BuiltIn CullPrimitiveEXT +// CHECK: [[cullprim]] = OpVariable %_ptr_Output__arr_bool_uint_1 Output + +struct MeshPerVertex { + float4 position : SV_Position; +}; + +struct MeshPerPrimitive { + CULL_TYPE cull : SV_CullPrimitive; +}; + +[outputtopology("triangle")] +[numthreads(1, 1, 1)] +void main(out vertices MeshPerVertex verts[3], + out indices uint3 tris[1], + out primitives MeshPerPrimitive prims[1]) { + SetMeshOutputCounts(3, 1); + verts[0].position = 0; + verts[1].position = 0; + verts[2].position = 0; + tris[0] = uint3(0, 1, 2); + prims[0].cull = true; +} diff --git a/tools/clang/test/CodeGenSPIRV/meshshading.ext.cullprimitive.non-bool.hlsl b/tools/clang/test/CodeGenSPIRV/meshshading.ext.cullprimitive.non-bool.hlsl index dd442fb272a..2d90b51a6be 100644 --- a/tools/clang/test/CodeGenSPIRV/meshshading.ext.cullprimitive.non-bool.hlsl +++ b/tools/clang/test/CodeGenSPIRV/meshshading.ext.cullprimitive.non-bool.hlsl @@ -5,7 +5,7 @@ struct MeshPerVertex { }; struct MeshPerPrimitive { -// CHECK: :[[@LINE+1]]:15: error: semantic CullPrimitive must be bool +// CHECK: :[[@LINE+1]]:15: error: semantic CullPrimitive must be a bool scalar uint cull : SV_CullPrimitive; }; diff --git a/tools/clang/test/CodeGenSPIRV/meshshading.ext.triangle.mesh.hlsl b/tools/clang/test/CodeGenSPIRV/meshshading.ext.triangle.mesh.hlsl index 81df96ef511..7651fedb90e 100644 --- a/tools/clang/test/CodeGenSPIRV/meshshading.ext.triangle.mesh.hlsl +++ b/tools/clang/test/CodeGenSPIRV/meshshading.ext.triangle.mesh.hlsl @@ -70,7 +70,7 @@ struct MeshPerPrimitive { int primId : SV_PrimitiveID; // -> Builtin PrimitiveId int layer : SV_RenderTargetArrayIndex; // -> Builtin Layer int vpIdx : SV_ViewportArrayIndex; // -> Builtin ViewportIndex - bool cullPrim : SV_CullPrimitive; // -> BUiltin CullPrimitiveEXT + bool cullPrim : SV_CullPrimitive; // -> Builtin CullPrimitiveEXT uint shadingRate : SV_ShadingRate; // -> Builtin PrimitiveShadingRateEXT float3 userPrimAttr : PRIM_USER; float4 userPrimAttrArr[2] : PRIM_USER_ARR; From 0aeba46e5d91a8fad495af93f9641fd011eae1f9 Mon Sep 17 00:00:00 2001 From: Abir Deol Date: Thu, 29 Oct 2026 11:56:16 -0700 Subject: [PATCH 3/8] [SPIR-V] Move the SV_CullPrimitive type check to validateShaderStageVarType Keeps the semantic type checks together, next to SV_InnerCoverage. Adds a bool2 negative test. --- tools/clang/lib/SPIRV/DeclResultIdMapper.cpp | 27 ++++++++++--------- .../meshshading.ext.cullprimitive.vector.hlsl | 23 ++++++++++++++++ 2 files changed, 37 insertions(+), 13 deletions(-) create mode 100644 tools/clang/test/CodeGenSPIRV/meshshading.ext.cullprimitive.vector.hlsl diff --git a/tools/clang/lib/SPIRV/DeclResultIdMapper.cpp b/tools/clang/lib/SPIRV/DeclResultIdMapper.cpp index fd182102e71..2e3a8f001b8 100644 --- a/tools/clang/lib/SPIRV/DeclResultIdMapper.cpp +++ b/tools/clang/lib/SPIRV/DeclResultIdMapper.cpp @@ -3510,6 +3510,20 @@ bool DeclResultIdMapper::validateShaderStageVarType( return false; } break; + case hlsl::Semantic::Kind::CullPrimitive: { + // DXIL only checks the type for mesh primitive outputs. PS inputs are + // replaced with a constant 0, so any type is accepted there. + if (stageVarData.sigPoint->GetKind() != hlsl::SigPoint::Kind::MSPOut) + break; + QualType scalarType; + if (!isScalarType(stageVarData.type, &scalarType) || + !scalarType->isBooleanType()) { + emitError("semantic CullPrimitive must be a bool scalar", + stageVarData.semantic->loc); + return false; + } + break; + } default: break; } @@ -3609,19 +3623,6 @@ DeclResultIdMapper::getBaseVertexVariable(const hlsl::SigPoint *sigPoint, SpirvVariable *DeclResultIdMapper::createSpirvInterfaceVariable( const StageVarDataBundle &stageVarData) { - // DXIL only checks the SV_CullPrimitive type for mesh primitive outputs. - // PS inputs are replaced with a constant 0, so any type is accepted there. - if (stageVarData.semantic->getKind() == hlsl::Semantic::Kind::CullPrimitive && - stageVarData.sigPoint->GetKind() == hlsl::SigPoint::Kind::MSPOut) { - QualType scalarType; - if (!isScalarType(stageVarData.type, &scalarType) || - !scalarType->isBooleanType()) { - emitError("semantic CullPrimitive must be a bool scalar", - stageVarData.semantic->loc); - return nullptr; - } - } - // The evalType will be the type of the interface variable in SPIR-V. // The type of the variable used in the body of the function will still be // `stageVarData.type`. diff --git a/tools/clang/test/CodeGenSPIRV/meshshading.ext.cullprimitive.vector.hlsl b/tools/clang/test/CodeGenSPIRV/meshshading.ext.cullprimitive.vector.hlsl new file mode 100644 index 00000000000..1fc5094e375 --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/meshshading.ext.cullprimitive.vector.hlsl @@ -0,0 +1,23 @@ +// RUN: not %dxc -T ms_6_5 -fspv-target-env=vulkan1.3 -E main -fcgl %s -spirv 2>&1 | FileCheck %s + +struct MeshPerVertex { + float4 position : SV_Position; +}; + +struct MeshPerPrimitive { +// CHECK: :[[@LINE+1]]:16: error: semantic CullPrimitive must be a bool scalar + bool2 cull : SV_CullPrimitive; +}; + +[outputtopology("triangle")] +[numthreads(1, 1, 1)] +void main(out vertices MeshPerVertex verts[3], + out indices uint3 tris[1], + out primitives MeshPerPrimitive prims[1]) { + SetMeshOutputCounts(3, 1); + verts[0].position = 0; + verts[1].position = 0; + verts[2].position = 0; + tris[0] = uint3(0, 1, 2); + prims[0].cull = true; +} From a189742365737a1c41e24036824b8854af878984 Mon Sep 17 00:00:00 2001 From: Abir Deol Date: Mon, 2 Nov 2026 17:41:59 -0700 Subject: [PATCH 4/8] Diagnose non-bool SV_CullPrimitive mesh outputs in Sema Move the check from the SPIR-V backend to Sema so DXIL and SPIR-V give the same error. DXIL only caught it at validation before. Only mesh shader primitive outputs are checked, bool1 and bool1x1 still count as scalar bools, and pixel shader inputs are left alone since DXIL replaces them with 0. The error tests move to SemaHLSL. Co-authored-by: Greg Roth --- docs/ReleaseNotes.md | 3 +- .../clang/Basic/DiagnosticSemaKinds.td | 2 + tools/clang/lib/SPIRV/DeclResultIdMapper.cpp | 14 ----- tools/clang/lib/Sema/SemaHLSL.cpp | 38 ++++++++++++++ .../meshshading.ext.cullprimitive.array.hlsl | 23 -------- ...eshshading.ext.cullprimitive.non-bool.hlsl | 23 -------- .../meshshading.ext.cullprimitive.vector.hlsl | 23 -------- .../test/SemaHLSL/cullprimitive-type.hlsl | 52 +++++++++++++++++++ 8 files changed, 94 insertions(+), 84 deletions(-) delete mode 100644 tools/clang/test/CodeGenSPIRV/meshshading.ext.cullprimitive.array.hlsl delete mode 100644 tools/clang/test/CodeGenSPIRV/meshshading.ext.cullprimitive.non-bool.hlsl delete mode 100644 tools/clang/test/CodeGenSPIRV/meshshading.ext.cullprimitive.vector.hlsl create mode 100644 tools/clang/test/SemaHLSL/cullprimitive-type.hlsl diff --git a/docs/ReleaseNotes.md b/docs/ReleaseNotes.md index b69ce2f0c2c..3b013343094 100644 --- a/docs/ReleaseNotes.md +++ b/docs/ReleaseNotes.md @@ -26,7 +26,8 @@ The included licenses apply to the following files: `ConstantBuffer`, `TextureBuffer`, and RW variants) via `SPV_KHR_untyped_pointers`. Requires `-fspv-target-env=vulkan1.3`. - `SV_CullPrimitive` on a mesh shader primitive output must now be a scalar - bool, matching DXIL, instead of producing invalid SPIR-V. + bool. This is now a front-end error for both DXIL and SPIR-V, instead of a + DXIL validation error or invalid SPIR-V. Place release notes for the upcoming release below this line and remove this line upon naming the release. Refer to previous for appropriate section names. diff --git a/tools/clang/include/clang/Basic/DiagnosticSemaKinds.td b/tools/clang/include/clang/Basic/DiagnosticSemaKinds.td index 7d798010d9c..9f8a471c2b2 100644 --- a/tools/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/tools/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -7964,6 +7964,8 @@ def warn_hlsl_numthreads_group_size : Warning< InGroup; def err_hlsl_wg_nodetrackrwinputsharing_missing : Error< "Use of FinishedCrossGroupSharing() requires NodeTrackRWInputSharing attribute to be specified on the record struct type">; +def err_hlsl_incorrect_sv_type + : Error<"%0 must be applied to a %1, but %2 has type %3">; def err_hlsl_wg_nodetrackrwinputsharing_invalid : Error< "NodeTrackRWInputSharing attribute cannot be applied to Input Records that are not RWDispatchNodeInputRecord">; def err_hlsl_wg_input_kind : Error< diff --git a/tools/clang/lib/SPIRV/DeclResultIdMapper.cpp b/tools/clang/lib/SPIRV/DeclResultIdMapper.cpp index 2e3a8f001b8..9aaa36c97ba 100644 --- a/tools/clang/lib/SPIRV/DeclResultIdMapper.cpp +++ b/tools/clang/lib/SPIRV/DeclResultIdMapper.cpp @@ -3510,20 +3510,6 @@ bool DeclResultIdMapper::validateShaderStageVarType( return false; } break; - case hlsl::Semantic::Kind::CullPrimitive: { - // DXIL only checks the type for mesh primitive outputs. PS inputs are - // replaced with a constant 0, so any type is accepted there. - if (stageVarData.sigPoint->GetKind() != hlsl::SigPoint::Kind::MSPOut) - break; - QualType scalarType; - if (!isScalarType(stageVarData.type, &scalarType) || - !scalarType->isBooleanType()) { - emitError("semantic CullPrimitive must be a bool scalar", - stageVarData.semantic->loc); - return false; - } - break; - } default: break; } diff --git a/tools/clang/lib/Sema/SemaHLSL.cpp b/tools/clang/lib/Sema/SemaHLSL.cpp index fa8b6893b17..116914d6afb 100644 --- a/tools/clang/lib/Sema/SemaHLSL.cpp +++ b/tools/clang/lib/Sema/SemaHLSL.cpp @@ -17250,6 +17250,41 @@ void DiagnoseVertexEntry(Sema &S, FunctionDecl *FD, llvm::StringRef StageName) { return; } +// SV_CullPrimitive on a mesh shader primitive output must be a scalar bool +// (bool1 and bool1x1 count). Checked here so DXIL and SPIR-V give the same +// error. Pixel shader inputs are not checked: DXIL replaces them with 0. +static void DiagnoseCullPrimitiveFields(Sema &S, QualType Ty) { + while (const ArrayType *AT = S.Context.getAsArrayType(Ty)) + Ty = AT->getElementType(); + const RecordType *RT = Ty->getAs(); + if (!RT || hlsl::IsHLSLVecMatType(Ty)) + return; + for (const FieldDecl *FD : RT->getDecl()->fields()) { + for (const hlsl::UnusualAnnotation *UA : FD->getUnusualAnnotations()) { + const auto *SD = dyn_cast(UA); + if (!SD) + continue; + llvm::StringRef BaseName; + uint32_t Index; + hlsl::Semantic::DecomposeNameAndIndex(SD->SemanticName, &BaseName, + &Index); + if (hlsl::Semantic::GetByName(BaseName)->GetKind() != + hlsl::Semantic::Kind::CullPrimitive) + continue; + QualType FieldTy = FD->getType().getUnqualifiedType(); + bool IsScalarBool = + hlsl::IsHLSLVecMatType(FieldTy) + ? hlsl::GetElementCount(FieldTy) == 1 && + hlsl::GetElementTypeOrType(FieldTy)->isBooleanType() + : FieldTy->isBooleanType(); + if (!IsScalarBool) + S.Diags.Report(FD->getLocation(), diag::err_hlsl_incorrect_sv_type) + << SD->SemanticName << "scalar bool" << FD << FD->getType(); + } + DiagnoseCullPrimitiveFields(S, FD->getType()); + } +} + void DiagnoseMeshEntry(Sema &S, FunctionDecl *FD, llvm::StringRef StageName) { if (!(FD->getAttr())) @@ -17258,6 +17293,9 @@ void DiagnoseMeshEntry(Sema &S, FunctionDecl *FD, llvm::StringRef StageName) { if (!(FD->getAttr())) S.Diags.Report(FD->getLocation(), diag::err_hlsl_missing_attr) << StageName << "outputtopology"; + for (const ParmVarDecl *Param : FD->params()) + if (Param->hasAttr()) + DiagnoseCullPrimitiveFields(S, Param->getType()); return; } diff --git a/tools/clang/test/CodeGenSPIRV/meshshading.ext.cullprimitive.array.hlsl b/tools/clang/test/CodeGenSPIRV/meshshading.ext.cullprimitive.array.hlsl deleted file mode 100644 index 4e9367e8ad6..00000000000 --- a/tools/clang/test/CodeGenSPIRV/meshshading.ext.cullprimitive.array.hlsl +++ /dev/null @@ -1,23 +0,0 @@ -// RUN: not %dxc -T ms_6_5 -fspv-target-env=vulkan1.3 -E main -fcgl %s -spirv 2>&1 | FileCheck %s - -struct MeshPerVertex { - float4 position : SV_Position; -}; - -struct MeshPerPrimitive { -// CHECK: :[[@LINE+1]]:18: error: semantic CullPrimitive must be a bool scalar - bool cull[2] : SV_CullPrimitive; -}; - -[outputtopology("triangle")] -[numthreads(1, 1, 1)] -void main(out vertices MeshPerVertex verts[3], - out indices uint3 tris[1], - out primitives MeshPerPrimitive prims[1]) { - SetMeshOutputCounts(3, 1); - verts[0].position = 0; - verts[1].position = 0; - verts[2].position = 0; - tris[0] = uint3(0, 1, 2); - prims[0].cull[0] = true; -} diff --git a/tools/clang/test/CodeGenSPIRV/meshshading.ext.cullprimitive.non-bool.hlsl b/tools/clang/test/CodeGenSPIRV/meshshading.ext.cullprimitive.non-bool.hlsl deleted file mode 100644 index 2d90b51a6be..00000000000 --- a/tools/clang/test/CodeGenSPIRV/meshshading.ext.cullprimitive.non-bool.hlsl +++ /dev/null @@ -1,23 +0,0 @@ -// RUN: not %dxc -T ms_6_5 -fspv-target-env=vulkan1.3 -E main -fcgl %s -spirv 2>&1 | FileCheck %s - -struct MeshPerVertex { - float4 position : SV_Position; -}; - -struct MeshPerPrimitive { -// CHECK: :[[@LINE+1]]:15: error: semantic CullPrimitive must be a bool scalar - uint cull : SV_CullPrimitive; -}; - -[outputtopology("triangle")] -[numthreads(1, 1, 1)] -void main(out vertices MeshPerVertex verts[3], - out indices uint3 tris[1], - out primitives MeshPerPrimitive prims[1]) { - SetMeshOutputCounts(3, 1); - verts[0].position = 0; - verts[1].position = 0; - verts[2].position = 0; - tris[0] = uint3(0, 1, 2); - prims[0].cull = 1; -} diff --git a/tools/clang/test/CodeGenSPIRV/meshshading.ext.cullprimitive.vector.hlsl b/tools/clang/test/CodeGenSPIRV/meshshading.ext.cullprimitive.vector.hlsl deleted file mode 100644 index 1fc5094e375..00000000000 --- a/tools/clang/test/CodeGenSPIRV/meshshading.ext.cullprimitive.vector.hlsl +++ /dev/null @@ -1,23 +0,0 @@ -// RUN: not %dxc -T ms_6_5 -fspv-target-env=vulkan1.3 -E main -fcgl %s -spirv 2>&1 | FileCheck %s - -struct MeshPerVertex { - float4 position : SV_Position; -}; - -struct MeshPerPrimitive { -// CHECK: :[[@LINE+1]]:16: error: semantic CullPrimitive must be a bool scalar - bool2 cull : SV_CullPrimitive; -}; - -[outputtopology("triangle")] -[numthreads(1, 1, 1)] -void main(out vertices MeshPerVertex verts[3], - out indices uint3 tris[1], - out primitives MeshPerPrimitive prims[1]) { - SetMeshOutputCounts(3, 1); - verts[0].position = 0; - verts[1].position = 0; - verts[2].position = 0; - tris[0] = uint3(0, 1, 2); - prims[0].cull = true; -} diff --git a/tools/clang/test/SemaHLSL/cullprimitive-type.hlsl b/tools/clang/test/SemaHLSL/cullprimitive-type.hlsl new file mode 100644 index 00000000000..5966863c21d --- /dev/null +++ b/tools/clang/test/SemaHLSL/cullprimitive-type.hlsl @@ -0,0 +1,52 @@ +// RUN: %dxc -T lib_6_8 -verify %s + +// SV_CullPrimitive on a mesh shader primitive output must be a scalar bool. +// Pixel shader inputs are replaced with 0, so any type is accepted there. + +struct Vert { + float4 pos : SV_Position; +}; + +struct PrimBool { + bool cull : SV_CullPrimitive; +}; +struct PrimBool1 { + bool1 cull : SV_CullPrimitive; +}; +struct PrimBool1x1 { + bool1x1 cull : SV_CullPrimitive; +}; +struct PrimUint { + uint cull : SV_CullPrimitive; // expected-error {{SV_CullPrimitive must be applied to a scalar bool, but 'cull' has type 'uint'}} +}; +struct PrimBool2 { + bool2 cull : SV_CullPrimitive; // expected-error {{SV_CullPrimitive must be applied to a scalar bool, but 'cull' has type 'bool2'}} +}; +struct PrimArray { + bool cull[2] : SV_CullPrimitive; // expected-error {{SV_CullPrimitive must be applied to a scalar bool, but 'cull' has type 'bool [2]'}} +}; +struct Inner { + uint cull : SV_CullPrimitive; // expected-error {{SV_CullPrimitive must be applied to a scalar bool, but 'cull' has type 'uint'}} +}; +struct PrimNested { + float4 col : COLOR0; + Inner inner; +}; + +#define MESH(name, Prim) \ + [shader("mesh")] [numthreads(1, 1, 1)] [outputtopology("triangle")] \ + void name(out vertices Vert verts[3], out indices uint3 tris[1], \ + out primitives Prim prims[1]) { \ + SetMeshOutputCounts(0, 0); \ + } + +MESH(ms_bool, PrimBool) +MESH(ms_bool1, PrimBool1) +MESH(ms_bool1x1, PrimBool1x1) +MESH(ms_uint, PrimUint) +MESH(ms_bool2, PrimBool2) +MESH(ms_array, PrimArray) +MESH(ms_nested, PrimNested) + +[shader("pixel")] +float4 ps_uint(uint cull : SV_CullPrimitive) : SV_Target { return cull; } From 120c19b47bf23429f812bd3d00ebc4b8be4b4f1d Mon Sep 17 00:00:00 2001 From: Abir Deol Date: Tue, 3 Nov 2026 11:29:30 -0700 Subject: [PATCH 5/8] Move the SV_CullPrimitive release note to Bug Fixes --- docs/ReleaseNotes.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/ReleaseNotes.md b/docs/ReleaseNotes.md index 0fd955ee38a..14788221b7a 100644 --- a/docs/ReleaseNotes.md +++ b/docs/ReleaseNotes.md @@ -25,9 +25,6 @@ The included licenses apply to the following files: buffer-like resources (`StructuredBuffer`, `ByteAddressBuffer`, `ConstantBuffer`, `TextureBuffer`, and RW variants) via `SPV_KHR_untyped_pointers`. Requires `-fspv-target-env=vulkan1.3`. -- `SV_CullPrimitive` on a mesh shader primitive output must now be a scalar - bool. This is now a front-end error for both DXIL and SPIR-V, instead of a - DXIL validation error or invalid SPIR-V. - Extended `-fspv-use-descriptor-heap` to support `RaytracingAccelerationStructure` loaded from `ResourceDescriptorHeap`. Outside ray-tracing stages, list `-fspv-extension=SPV_KHR_ray_query`, `SPV_KHR_ray_tracing` or @@ -104,6 +101,9 @@ line upon naming the release. Refer to previous for appropriate section names. after bit-tracking dead code elimination. - Fixed an optimizer miscompile where selects of bitwise-not values guarded by signed less-than or unsigned compares were treated as a signed minimum. +- Fixed non-bool `SV_CullPrimitive` mesh shader outputs producing invalid + SPIR-V. They are now a front-end error for both DXIL and SPIR-V, instead of a + DXIL validation error. ### Upcoming Preview Release From 75b015f18c2f64d0e87737f7e6380c969c2dd40e Mon Sep 17 00:00:00 2001 From: Abir Deol Date: Thu, 5 Nov 2026 00:33:30 -0700 Subject: [PATCH 6/8] Check base classes and parameter semantics for SV_CullPrimitive Look through base classes, check a semantic declared directly on the primitives parameter, and stop at a semantic on an enclosing field or parameter since it overrides the ones inside. --- tools/clang/lib/Sema/SemaHLSL.cpp | 73 ++++++++++++------- .../test/SemaHLSL/cullprimitive-type.hlsl | 25 +++++++ 2 files changed, 70 insertions(+), 28 deletions(-) diff --git a/tools/clang/lib/Sema/SemaHLSL.cpp b/tools/clang/lib/Sema/SemaHLSL.cpp index ad32d9a24b9..26049ce58d5 100644 --- a/tools/clang/lib/Sema/SemaHLSL.cpp +++ b/tools/clang/lib/Sema/SemaHLSL.cpp @@ -17275,36 +17275,47 @@ void DiagnoseVertexEntry(Sema &S, FunctionDecl *FD, llvm::StringRef StageName) { // SV_CullPrimitive on a mesh shader primitive output must be a scalar bool // (bool1 and bool1x1 count). Checked here so DXIL and SPIR-V give the same // error. Pixel shader inputs are not checked: DXIL replaces them with 0. -static void DiagnoseCullPrimitiveFields(Sema &S, QualType Ty) { +static void DiagnoseCullPrimitiveRecord(Sema &S, QualType Ty); + +// A semantic on a field or parameter overrides any semantics inside its type, +// so only look inside when D has none. +static void DiagnoseCullPrimitiveDecl(Sema &S, const NamedDecl *D, + QualType Ty) { + for (const hlsl::UnusualAnnotation *UA : D->getUnusualAnnotations()) { + const auto *SD = dyn_cast(UA); + if (!SD) + continue; + llvm::StringRef BaseName; + uint32_t Index; + hlsl::Semantic::DecomposeNameAndIndex(SD->SemanticName, &BaseName, &Index); + if (hlsl::Semantic::GetByName(BaseName)->GetKind() != + hlsl::Semantic::Kind::CullPrimitive) + return; + QualType ElemTy = Ty.getUnqualifiedType(); + bool IsScalarBool = + hlsl::IsHLSLVecMatType(ElemTy) + ? hlsl::GetElementCount(ElemTy) == 1 && + hlsl::GetElementTypeOrType(ElemTy)->isBooleanType() + : ElemTy->isBooleanType(); + if (!IsScalarBool) + S.Diags.Report(D->getLocation(), diag::err_hlsl_incorrect_sv_type) + << SD->SemanticName << "scalar bool" << D << ElemTy; + return; + } + DiagnoseCullPrimitiveRecord(S, Ty); +} + +static void DiagnoseCullPrimitiveRecord(Sema &S, QualType Ty) { while (const ArrayType *AT = S.Context.getAsArrayType(Ty)) Ty = AT->getElementType(); const RecordType *RT = Ty->getAs(); if (!RT || hlsl::IsHLSLVecMatType(Ty)) return; - for (const FieldDecl *FD : RT->getDecl()->fields()) { - for (const hlsl::UnusualAnnotation *UA : FD->getUnusualAnnotations()) { - const auto *SD = dyn_cast(UA); - if (!SD) - continue; - llvm::StringRef BaseName; - uint32_t Index; - hlsl::Semantic::DecomposeNameAndIndex(SD->SemanticName, &BaseName, - &Index); - if (hlsl::Semantic::GetByName(BaseName)->GetKind() != - hlsl::Semantic::Kind::CullPrimitive) - continue; - QualType FieldTy = FD->getType().getUnqualifiedType(); - bool IsScalarBool = - hlsl::IsHLSLVecMatType(FieldTy) - ? hlsl::GetElementCount(FieldTy) == 1 && - hlsl::GetElementTypeOrType(FieldTy)->isBooleanType() - : FieldTy->isBooleanType(); - if (!IsScalarBool) - S.Diags.Report(FD->getLocation(), diag::err_hlsl_incorrect_sv_type) - << SD->SemanticName << "scalar bool" << FD << FD->getType(); - } - DiagnoseCullPrimitiveFields(S, FD->getType()); - } + if (const auto *RD = dyn_cast(RT->getDecl())) + for (const CXXBaseSpecifier &Base : RD->bases()) + DiagnoseCullPrimitiveRecord(S, Base.getType()); + for (const FieldDecl *FD : RT->getDecl()->fields()) + DiagnoseCullPrimitiveDecl(S, FD, FD->getType()); } void DiagnoseMeshEntry(Sema &S, FunctionDecl *FD, llvm::StringRef StageName) { @@ -17315,9 +17326,15 @@ void DiagnoseMeshEntry(Sema &S, FunctionDecl *FD, llvm::StringRef StageName) { if (!(FD->getAttr())) S.Diags.Report(FD->getLocation(), diag::err_hlsl_missing_attr) << StageName << "outputtopology"; - for (const ParmVarDecl *Param : FD->params()) - if (Param->hasAttr()) - DiagnoseCullPrimitiveFields(S, Param->getType()); + // The semantic applies to each primitive, so check the element type. + for (const ParmVarDecl *Param : FD->params()) { + if (!Param->hasAttr()) + continue; + QualType Ty = Param->getType(); + if (const ArrayType *AT = S.Context.getAsArrayType(Ty)) + Ty = AT->getElementType(); + DiagnoseCullPrimitiveDecl(S, Param, Ty); + } return; } diff --git a/tools/clang/test/SemaHLSL/cullprimitive-type.hlsl b/tools/clang/test/SemaHLSL/cullprimitive-type.hlsl index 5966863c21d..b98d99b13bf 100644 --- a/tools/clang/test/SemaHLSL/cullprimitive-type.hlsl +++ b/tools/clang/test/SemaHLSL/cullprimitive-type.hlsl @@ -32,6 +32,17 @@ struct PrimNested { float4 col : COLOR0; Inner inner; }; +struct Base { + uint cull : SV_CullPrimitive; // expected-error {{SV_CullPrimitive must be applied to a scalar bool, but 'cull' has type 'uint'}} +}; +struct PrimDerived : Base {}; +// The semantic on 'data' overrides the one inside Overridden. +struct Overridden { + uint cull : SV_CullPrimitive; +}; +struct PrimOverride { + Overridden data : TEXCOORD0; +}; #define MESH(name, Prim) \ [shader("mesh")] [numthreads(1, 1, 1)] [outputtopology("triangle")] \ @@ -47,6 +58,20 @@ MESH(ms_uint, PrimUint) MESH(ms_bool2, PrimBool2) MESH(ms_array, PrimArray) MESH(ms_nested, PrimNested) +MESH(ms_derived, PrimDerived) +MESH(ms_override, PrimOverride) + +// SV_CullPrimitive directly on the parameter applies to each primitive. +[shader("mesh")] [numthreads(1, 1, 1)] [outputtopology("triangle")] +void ms_param_bool(out vertices Vert verts[3], out indices uint3 tris[1], + out primitives bool prims[1] : SV_CullPrimitive) { + SetMeshOutputCounts(0, 0); +} +[shader("mesh")] [numthreads(1, 1, 1)] [outputtopology("triangle")] +void ms_param_uint(out vertices Vert verts[3], out indices uint3 tris[1], + out primitives uint prims[1] : SV_CullPrimitive) { // expected-error {{SV_CullPrimitive must be applied to a scalar bool, but 'prims' has type 'uint'}} + SetMeshOutputCounts(0, 0); +} [shader("pixel")] float4 ps_uint(uint cull : SV_CullPrimitive) : SV_Target { return cull; } From 112bf063d58cce5f86109cb0621bde9321cfcc1b Mon Sep 17 00:00:00 2001 From: Abir Deol Date: Sun, 8 Nov 2026 17:36:10 -0700 Subject: [PATCH 7/8] Sema: don't walk incomplete records in the SV_CullPrimitive check A primitive struct that is only forward declared, never defined, or has an incomplete field hit the 'queried property of class with no definition' assert when the check asked for its bases. Sema already reports the incomplete type, so stop there. --- tools/clang/lib/Sema/SemaHLSL.cpp | 10 ++++-- .../SemaHLSL/cullprimitive-incomplete.hlsl | 36 +++++++++++++++++++ 2 files changed, 43 insertions(+), 3 deletions(-) create mode 100644 tools/clang/test/SemaHLSL/cullprimitive-incomplete.hlsl diff --git a/tools/clang/lib/Sema/SemaHLSL.cpp b/tools/clang/lib/Sema/SemaHLSL.cpp index 26049ce58d5..a5e4d058e2c 100644 --- a/tools/clang/lib/Sema/SemaHLSL.cpp +++ b/tools/clang/lib/Sema/SemaHLSL.cpp @@ -17311,10 +17311,14 @@ static void DiagnoseCullPrimitiveRecord(Sema &S, QualType Ty) { const RecordType *RT = Ty->getAs(); if (!RT || hlsl::IsHLSLVecMatType(Ty)) return; - if (const auto *RD = dyn_cast(RT->getDecl())) - for (const CXXBaseSpecifier &Base : RD->bases()) + // An incomplete type has already been diagnosed and has nothing to walk. + const RecordDecl *RD = RT->getDecl()->getDefinition(); + if (!RD) + return; + if (const auto *CRD = dyn_cast(RD)) + for (const CXXBaseSpecifier &Base : CRD->bases()) DiagnoseCullPrimitiveRecord(S, Base.getType()); - for (const FieldDecl *FD : RT->getDecl()->fields()) + for (const FieldDecl *FD : RD->fields()) DiagnoseCullPrimitiveDecl(S, FD, FD->getType()); } diff --git a/tools/clang/test/SemaHLSL/cullprimitive-incomplete.hlsl b/tools/clang/test/SemaHLSL/cullprimitive-incomplete.hlsl new file mode 100644 index 00000000000..3c585634606 --- /dev/null +++ b/tools/clang/test/SemaHLSL/cullprimitive-incomplete.hlsl @@ -0,0 +1,36 @@ +// RUN: %dxc -T lib_6_8 -verify %s + +// The SV_CullPrimitive check must not crash on incomplete primitive types. + +struct Vert { + float4 pos : SV_Position; +}; + +struct DefinedLater; +struct NeverDefined; +struct Incomplete; +struct HasIncompleteField { + Incomplete inner; // expected-error {{field has incomplete type 'Incomplete'}} expected-note@-2 {{forward declaration of 'Incomplete'}} +}; + +[shader("mesh")] [numthreads(1, 1, 1)] [outputtopology("triangle")] +void ms_later(out vertices Vert verts[3], out indices uint3 tris[1], + out primitives DefinedLater prims[1]) { // expected-error {{variable has incomplete type 'DefinedLater __restrict[1]'}} + SetMeshOutputCounts(0, 0); +} + +[shader("mesh")] [numthreads(1, 1, 1)] [outputtopology("triangle")] +void ms_never(out vertices Vert verts[3], out indices uint3 tris[1], + out primitives NeverDefined prims[1]) { // expected-error {{variable has incomplete type 'NeverDefined __restrict[1]'}} + SetMeshOutputCounts(0, 0); +} + +[shader("mesh")] [numthreads(1, 1, 1)] [outputtopology("triangle")] +void ms_field(out vertices Vert verts[3], out indices uint3 tris[1], + out primitives HasIncompleteField prims[1]) { + SetMeshOutputCounts(0, 0); +} + +struct DefinedLater { + bool cull : SV_CullPrimitive; +}; From d3abc8c45104a7ff61463bbe1a5fa28381c251c0 Mon Sep 17 00:00:00 2001 From: Abir Deol Date: Sun, 8 Nov 2026 17:36:10 -0700 Subject: [PATCH 8/8] Sema: check each leaf of a struct with SV_CullPrimitive, report it once Both backends apply a semantic on a struct to each of its leaf fields, so struct { bool value; } is a valid SV_CullPrimitive and struct { uint value; } is not. Check the leaves the same way, and report each error once when entry points share a struct. Add tests for bool1 on a parameter, bool1x2, an index suffix, lowercase, an enum, an array of structs, wrapped, mixed and inherited fields, and a ms_6_5 profile entry. Move the diagnostic next to the mesh output ones. --- .../clang/Basic/DiagnosticSemaKinds.td | 4 +- tools/clang/include/clang/Sema/Sema.h | 5 ++ tools/clang/lib/Sema/SemaHLSL.cpp | 65 ++++++++++++----- .../SemaHLSL/cullprimitive-incomplete.hlsl | 6 ++ .../SemaHLSL/cullprimitive-type-entry.hlsl | 24 +++++++ .../test/SemaHLSL/cullprimitive-type.hlsl | 69 ++++++++++++++++++- 6 files changed, 152 insertions(+), 21 deletions(-) create mode 100644 tools/clang/test/SemaHLSL/cullprimitive-type-entry.hlsl diff --git a/tools/clang/include/clang/Basic/DiagnosticSemaKinds.td b/tools/clang/include/clang/Basic/DiagnosticSemaKinds.td index b23737272ed..e3767dc9795 100644 --- a/tools/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/tools/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -7915,6 +7915,8 @@ def err_hlsl_missing_in_attr: Error< "%0 object must be an in parameter">; def err_hlsl_load_from_mesh_out_arrays: Error< "output arrays of a mesh shader can not be read from">; +def err_hlsl_incorrect_sv_type : Error< + "%0 must be applied to a %1, but %2 has type %3">; def err_hlsl_out_indices_array_incorrect_access: Error< "a vector in out indices array must be accessed as a whole">; def err_hlsl_unsupported_long_vector @@ -7970,8 +7972,6 @@ def warn_hlsl_numthreads_group_size : Warning< InGroup; def err_hlsl_wg_nodetrackrwinputsharing_missing : Error< "Use of FinishedCrossGroupSharing() requires NodeTrackRWInputSharing attribute to be specified on the record struct type">; -def err_hlsl_incorrect_sv_type - : Error<"%0 must be applied to a %1, but %2 has type %3">; def err_hlsl_wg_nodetrackrwinputsharing_invalid : Error< "NodeTrackRWInputSharing attribute cannot be applied to Input Records that are not RWDispatchNodeInputRecord">; def err_hlsl_wg_input_kind : Error< diff --git a/tools/clang/include/clang/Sema/Sema.h b/tools/clang/include/clang/Sema/Sema.h index 6eb0aba8018..e742fa0032f 100644 --- a/tools/clang/include/clang/Sema/Sema.h +++ b/tools/clang/include/clang/Sema/Sema.h @@ -46,6 +46,7 @@ #include "llvm/ADT/Optional.h" #include "llvm/ADT/SetVector.h" #include "llvm/ADT/SmallPtrSet.h" +#include "llvm/ADT/SmallSet.h" // HLSL Change #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/TinyPtrVector.h" #include @@ -4089,6 +4090,10 @@ class Sema { /// cbuffer/tbuffer llvm::SmallVector HLSLBuffers; + /// (semantic decl, leaf) pairs already given an SV_CullPrimitive type error, + /// so entry points that share a struct report it once. + llvm::SmallSet, 4> + HLSLDiagnosedCullPrimitives; Decl* ActOnStartHLSLBuffer(Scope* bufferScope, bool cbuffer, SourceLocation KwLoc, IdentifierInfo *Ident, SourceLocation IdentLoc, std::vector& BufferAttributes, diff --git a/tools/clang/lib/Sema/SemaHLSL.cpp b/tools/clang/lib/Sema/SemaHLSL.cpp index a5e4d058e2c..15007151772 100644 --- a/tools/clang/lib/Sema/SemaHLSL.cpp +++ b/tools/clang/lib/Sema/SemaHLSL.cpp @@ -17274,11 +17274,53 @@ void DiagnoseVertexEntry(Sema &S, FunctionDecl *FD, llvm::StringRef StageName) { // SV_CullPrimitive on a mesh shader primitive output must be a scalar bool // (bool1 and bool1x1 count). Checked here so DXIL and SPIR-V give the same -// error. Pixel shader inputs are not checked: DXIL replaces them with 0. +// error. Only primitive outputs are checked. Both backends already reject +// SV_CullPrimitive on vertex outputs whatever its type, and pixel shader +// inputs are odd: DXIL accepts any type there and replaces the input with 0. + +// Returns the definition of a struct type, or null for anything else. An +// incomplete struct has already been diagnosed and has nothing to walk. +static const RecordDecl *GetCullPrimitiveRecord(QualType Ty) { + const RecordType *RT = Ty->getAs(); + if (!RT || hlsl::IsHLSLVecMatType(Ty)) + return nullptr; + return RT->getDecl()->getDefinition(); +} + +// The backends apply a semantic on a struct to each of its leaf fields, so +// check every leaf of Ty. Errors point at SemDecl, which holds the semantic, +// and name the leaf. +static void DiagnoseCullPrimitiveLeaves(Sema &S, const NamedDecl *SemDecl, + llvm::StringRef SemName, + const NamedDecl *Leaf, QualType Ty) { + Ty = Ty.getUnqualifiedType(); + if (Ty->isIncompleteType()) + return; + if (const RecordDecl *RD = GetCullPrimitiveRecord(Ty)) { + if (const auto *CRD = dyn_cast(RD)) + for (const CXXBaseSpecifier &Base : CRD->bases()) + DiagnoseCullPrimitiveLeaves(S, SemDecl, SemName, Leaf, Base.getType()); + for (const FieldDecl *FD : RD->fields()) + DiagnoseCullPrimitiveLeaves(S, SemDecl, SemName, FD, FD->getType()); + return; + } + bool IsScalarBool = hlsl::IsHLSLVecMatType(Ty) + ? hlsl::GetElementCount(Ty) == 1 && + hlsl::GetElementTypeOrType(Ty)->isBooleanType() + : Ty->isBooleanType(); + if (IsScalarBool) + return; + // Entry points that share a struct would otherwise report it once each. + if (!S.HLSLDiagnosedCullPrimitives.insert({SemDecl, Leaf}).second) + return; + S.Diags.Report(SemDecl->getLocation(), diag::err_hlsl_incorrect_sv_type) + << SemName << "scalar bool" << Leaf << Ty; +} + static void DiagnoseCullPrimitiveRecord(Sema &S, QualType Ty); // A semantic on a field or parameter overrides any semantics inside its type, -// so only look inside when D has none. +// so only look for them inside when D has none. static void DiagnoseCullPrimitiveDecl(Sema &S, const NamedDecl *D, QualType Ty) { for (const hlsl::UnusualAnnotation *UA : D->getUnusualAnnotations()) { @@ -17288,18 +17330,9 @@ static void DiagnoseCullPrimitiveDecl(Sema &S, const NamedDecl *D, llvm::StringRef BaseName; uint32_t Index; hlsl::Semantic::DecomposeNameAndIndex(SD->SemanticName, &BaseName, &Index); - if (hlsl::Semantic::GetByName(BaseName)->GetKind() != + if (hlsl::Semantic::GetByName(BaseName)->GetKind() == hlsl::Semantic::Kind::CullPrimitive) - return; - QualType ElemTy = Ty.getUnqualifiedType(); - bool IsScalarBool = - hlsl::IsHLSLVecMatType(ElemTy) - ? hlsl::GetElementCount(ElemTy) == 1 && - hlsl::GetElementTypeOrType(ElemTy)->isBooleanType() - : ElemTy->isBooleanType(); - if (!IsScalarBool) - S.Diags.Report(D->getLocation(), diag::err_hlsl_incorrect_sv_type) - << SD->SemanticName << "scalar bool" << D << ElemTy; + DiagnoseCullPrimitiveLeaves(S, D, SD->SemanticName, D, Ty); return; } DiagnoseCullPrimitiveRecord(S, Ty); @@ -17308,11 +17341,7 @@ static void DiagnoseCullPrimitiveDecl(Sema &S, const NamedDecl *D, static void DiagnoseCullPrimitiveRecord(Sema &S, QualType Ty) { while (const ArrayType *AT = S.Context.getAsArrayType(Ty)) Ty = AT->getElementType(); - const RecordType *RT = Ty->getAs(); - if (!RT || hlsl::IsHLSLVecMatType(Ty)) - return; - // An incomplete type has already been diagnosed and has nothing to walk. - const RecordDecl *RD = RT->getDecl()->getDefinition(); + const RecordDecl *RD = GetCullPrimitiveRecord(Ty); if (!RD) return; if (const auto *CRD = dyn_cast(RD)) diff --git a/tools/clang/test/SemaHLSL/cullprimitive-incomplete.hlsl b/tools/clang/test/SemaHLSL/cullprimitive-incomplete.hlsl index 3c585634606..aa082943f44 100644 --- a/tools/clang/test/SemaHLSL/cullprimitive-incomplete.hlsl +++ b/tools/clang/test/SemaHLSL/cullprimitive-incomplete.hlsl @@ -25,6 +25,12 @@ void ms_never(out vertices Vert verts[3], out indices uint3 tris[1], SetMeshOutputCounts(0, 0); } +[shader("mesh")] [numthreads(1, 1, 1)] [outputtopology("triangle")] +void ms_param(out vertices Vert verts[3], out indices uint3 tris[1], + out primitives NeverDefined prims[1] : SV_CullPrimitive) { // expected-error {{variable has incomplete type 'NeverDefined __restrict[1]'}} + SetMeshOutputCounts(0, 0); +} + [shader("mesh")] [numthreads(1, 1, 1)] [outputtopology("triangle")] void ms_field(out vertices Vert verts[3], out indices uint3 tris[1], out primitives HasIncompleteField prims[1]) { diff --git a/tools/clang/test/SemaHLSL/cullprimitive-type-entry.hlsl b/tools/clang/test/SemaHLSL/cullprimitive-type-entry.hlsl new file mode 100644 index 00000000000..ed92993d330 --- /dev/null +++ b/tools/clang/test/SemaHLSL/cullprimitive-type-entry.hlsl @@ -0,0 +1,24 @@ +// RUN: %dxc -T ms_6_5 -E main -verify %s + +// The SV_CullPrimitive check also runs for a profile entry point, which +// reaches DiagnoseEntry by a different path than a library shader. + +struct Vert { + float4 pos : SV_Position; +}; +struct Prim { + bool ok : SV_CullPrimitive; +}; +struct BadPrim { + uint cull : SV_CullPrimitive; // expected-error {{SV_CullPrimitive must be applied to a scalar bool, but 'cull' has type 'uint'}} +}; +struct Prims { + Prim good; + BadPrim bad; +}; + +[numthreads(1, 1, 1)] [outputtopology("triangle")] +void main(out vertices Vert verts[3], out indices uint3 tris[1], + out primitives Prims prims[1]) { + SetMeshOutputCounts(0, 0); +} diff --git a/tools/clang/test/SemaHLSL/cullprimitive-type.hlsl b/tools/clang/test/SemaHLSL/cullprimitive-type.hlsl index b98d99b13bf..bd5ae7a0cd7 100644 --- a/tools/clang/test/SemaHLSL/cullprimitive-type.hlsl +++ b/tools/clang/test/SemaHLSL/cullprimitive-type.hlsl @@ -1,7 +1,8 @@ // RUN: %dxc -T lib_6_8 -verify %s // SV_CullPrimitive on a mesh shader primitive output must be a scalar bool. -// Pixel shader inputs are replaced with 0, so any type is accepted there. +// A semantic on a struct applies to each of its leaf fields. Pixel shader +// inputs are replaced with 0, so any type is accepted there. struct Vert { float4 pos : SV_Position; @@ -25,6 +26,21 @@ struct PrimBool2 { struct PrimArray { bool cull[2] : SV_CullPrimitive; // expected-error {{SV_CullPrimitive must be applied to a scalar bool, but 'cull' has type 'bool [2]'}} }; +struct PrimBool1x2 { + bool1x2 cull : SV_CullPrimitive; // expected-error {{SV_CullPrimitive must be applied to a scalar bool, but 'cull' has type 'bool1x2'}} +}; +struct PrimIndexed { + uint cull : SV_CullPrimitive1; // expected-error {{SV_CullPrimitive1 must be applied to a scalar bool, but 'cull' has type 'uint'}} +}; +struct PrimLowercase { + uint cull : sv_cullprimitive; // expected-error {{sv_cullprimitive must be applied to a scalar bool, but 'cull' has type 'uint'}} +}; +enum CullEnum { CullOff, CullOn }; +struct PrimEnum { + CullEnum cull : SV_CullPrimitive; // expected-error {{SV_CullPrimitive must be applied to a scalar bool, but 'cull' has type 'CullEnum'}} +}; +// Inner is reached by two entry points and through an array, but its error +// is reported once. struct Inner { uint cull : SV_CullPrimitive; // expected-error {{SV_CullPrimitive must be applied to a scalar bool, but 'cull' has type 'uint'}} }; @@ -32,6 +48,9 @@ struct PrimNested { float4 col : COLOR0; Inner inner; }; +struct PrimArrayOfStructs { + Inner inner[2]; +}; struct Base { uint cull : SV_CullPrimitive; // expected-error {{SV_CullPrimitive must be applied to a scalar bool, but 'cull' has type 'uint'}} }; @@ -43,6 +62,30 @@ struct Overridden { struct PrimOverride { Overridden data : TEXCOORD0; }; +// A semantic on a struct field applies to each leaf inside it. +struct WrapBool { + bool value; +}; +struct WrapUint { + uint value; +}; +struct WrapMixed { + bool ok; + uint bad; +}; +struct WrapDerived : WrapUint {}; +struct PrimWrapBool { + WrapBool data : SV_CullPrimitive; +}; +struct PrimWrapUint { + WrapUint data : SV_CullPrimitive; // expected-error {{SV_CullPrimitive must be applied to a scalar bool, but 'value' has type 'uint'}} +}; +struct PrimWrapMixed { + WrapMixed data : SV_CullPrimitive; // expected-error {{SV_CullPrimitive must be applied to a scalar bool, but 'bad' has type 'uint'}} +}; +struct PrimWrapDerived { + WrapDerived data : SV_CullPrimitive; // expected-error {{SV_CullPrimitive must be applied to a scalar bool, but 'value' has type 'uint'}} +}; #define MESH(name, Prim) \ [shader("mesh")] [numthreads(1, 1, 1)] [outputtopology("triangle")] \ @@ -57,9 +100,18 @@ MESH(ms_bool1x1, PrimBool1x1) MESH(ms_uint, PrimUint) MESH(ms_bool2, PrimBool2) MESH(ms_array, PrimArray) +MESH(ms_bool1x2, PrimBool1x2) +MESH(ms_indexed, PrimIndexed) +MESH(ms_lowercase, PrimLowercase) +MESH(ms_enum, PrimEnum) MESH(ms_nested, PrimNested) +MESH(ms_array_of_structs, PrimArrayOfStructs) MESH(ms_derived, PrimDerived) MESH(ms_override, PrimOverride) +MESH(ms_wrap_bool, PrimWrapBool) +MESH(ms_wrap_uint, PrimWrapUint) +MESH(ms_wrap_mixed, PrimWrapMixed) +MESH(ms_wrap_derived, PrimWrapDerived) // SV_CullPrimitive directly on the parameter applies to each primitive. [shader("mesh")] [numthreads(1, 1, 1)] [outputtopology("triangle")] @@ -68,6 +120,21 @@ void ms_param_bool(out vertices Vert verts[3], out indices uint3 tris[1], SetMeshOutputCounts(0, 0); } [shader("mesh")] [numthreads(1, 1, 1)] [outputtopology("triangle")] +void ms_param_bool1(out vertices Vert verts[3], out indices uint3 tris[1], + out primitives bool1 prims[1] : SV_CullPrimitive) { + SetMeshOutputCounts(0, 0); +} +[shader("mesh")] [numthreads(1, 1, 1)] [outputtopology("triangle")] +void ms_param_wrap_bool(out vertices Vert verts[3], out indices uint3 tris[1], + out primitives WrapBool prims[1] : SV_CullPrimitive) { + SetMeshOutputCounts(0, 0); +} +[shader("mesh")] [numthreads(1, 1, 1)] [outputtopology("triangle")] +void ms_param_wrap_uint(out vertices Vert verts[3], out indices uint3 tris[1], + out primitives WrapUint prims[1] : SV_CullPrimitive) { // expected-error {{SV_CullPrimitive must be applied to a scalar bool, but 'value' has type 'uint'}} + SetMeshOutputCounts(0, 0); +} +[shader("mesh")] [numthreads(1, 1, 1)] [outputtopology("triangle")] void ms_param_uint(out vertices Vert verts[3], out indices uint3 tris[1], out primitives uint prims[1] : SV_CullPrimitive) { // expected-error {{SV_CullPrimitive must be applied to a scalar bool, but 'prims' has type 'uint'}} SetMeshOutputCounts(0, 0);