Skip to content

Diagnose non-bool SV_CullPrimitive mesh outputs in Sema - #9073

Open
Abir Deol (apollo-2006) wants to merge 7 commits into
microsoft:mainfrom
apollo-2006:spirv-cullprimitive-bool
Open

Abir Deol (apollo-2006) wants to merge 7 commits into
microsoft:mainfrom
apollo-2006:spirv-cullprimitive-bool

Conversation

@apollo-2006

@apollo-2006 Abir Deol (apollo-2006) commented Oct 27, 2026 •

Copy link
Copy Markdown

A non-bool SV_CullPrimitive on a mesh shader primitive output used to crash SPIR-V codegen with the "please file a bug report" error. DXIL only caught it at validation. It's now a Sema error for both:

error: SV_CullPrimitive must be applied to a scalar bool, but 'cull' has type 'uint'

Following Gregory Roth (@pow2clk)'s suggestion, the check runs in DiagnoseMeshEntry on out primitives params (nested structs and arrays included). bool1 and bool1x1 count as scalar bools, and pixel shader inputs are left alone since DXIL replaces them with 0.

Tests:

  • SemaHLSL/cullprimitive-type.hlsl covers the rejected and accepted types (fails without the change)
  • meshshading.ext.cullprimitive.bool1.hlsl checks that bool1 still lowers to the builtin
  • meshshading.ext.triangle.mesh.hlsl used int (it only passed because -fcgl skips validation), so it's bool now

Happy to change anything :)

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.
Copilot AI balanced review requested due to automatic review settings October 27, 2026 15:16
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The new check rejects valid scalar-equivalent bool1 and bool1x1 declarations that already lower to SPIR-V boolean scalars.

1 open issue
What changed in this PR

This PR adds an early SPIR-V diagnostic for non-boolean SV_CullPrimitive declarations, replacing a fatal validation failure with a normal compiler error. The related NV mesh-shader limitations remain outside its scope.

Changes:

  • Validate the semantic’s element type before stage-specific handling.
  • Add a negative regression test and correct the existing mesh test to use bool.
  • Document the behavior change in the upcoming release notes.
File Description
tools/​clang/​test/​CodeGenSPIRV/​meshshading.ext.triangle.mesh.hlsl Use boolean cull values and update SPIR-V checks.
tools/​clang/​test/​CodeGenSPIRV/​meshshading.ext.cullprimitive.non-bool.hlsl Test the diagnostic for a uint cull declaration.
tools/​clang/​lib/​SPIRV/​DeclResultIdMapper.cpp Check SV_CullPrimitive types before creating stage variables.
docs/​ReleaseNotes.md Document the new diagnostic.

Review effort: Balanced

@damyanp

Copy link
Copy Markdown
Member

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

int layer : SV_RenderTargetArrayIndex; // -> Builtin Layer
int vpIdx : SV_ViewportArrayIndex; // -> Builtin ViewportIndex
int cullPrim : SV_CullPrimitive; // -> BUiltin CullPrimitiveEXT
bool cullPrim : SV_CullPrimitive; // -> BUiltin CullPrimitiveEXT

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

super nit:

Suggested change
bool cullPrim : SV_CullPrimitive; // -> BUiltin CullPrimitiveEXT
bool cullPrim : SV_CullPrimitive; // -> Builtin CullPrimitiveEXT

case hlsl::Semantic::Kind::CullPrimitive: {
QualType elemType = type;
while (const auto *arrayType = astContext.getAsConstantArrayType(elemType))
elemType = arrayType->getElementType();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried this code on aggregate types, and it looks like it doesn't quite handle that case.

From what I see, this fails to catch bool cull[2]: DXIL gives the error "rows for system value semantic SV_CullPrimitive must be 1" meanwhile SPIR-V gives "invalid SPIR-V, please file a bug"

If you could fix this and add a test, that be great.

// be used as MSOut.
case hlsl::Semantic::Kind::CullPrimitive: {
QualType elemType = type;
while (const auto *arrayType = astContext.getAsConstantArrayType(elemType))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The bool check should apply only to MSPOut, I'll make a longer explanation below

@github-project-automation github-project-automation Bot moved this from New to In progress in HLSL Roadmap Oct 28, 2026
@jzakharovnv

Copy link
Copy Markdown
Collaborator

One interesting side effect of the current implementation is that it rejects uint SV_CullPrimitive on PS inputs, which is not what DXIL does.

Per hctdb.py and docs/DXIL.rst, CullPrimitive has the interpretations: PSIn = NotInSig, MSPOut = NotPacked, and NA elsewhere. NotInSig means the element isn't in the signature and is accessed through an intrinsic. For CullPrimitive specifically, HLSignatureLower.cpp replaces all uses of CullPrimitive with constant 0 regardless of the type. This all happens before DxilValidation.cpp so the bool check never actually get enforced in this case (hence why CullPrimitive is allowed to be uint in PSin).

However, the SPIRV backend doesn't implement this CullPrimitive PSIn replaced with constant 0 stuff, since to use CullPrimitiveEXT requires MeshShadingEXT which is illegal in fragment shaders per VUID 07034.

So, the 2 options are to mirror DXIL's replacement with constant 0 or conform to VUID 07034, but that is an issue out of scope for this PR. What do you think Diego Novillo (@dnovillo).

Suffice to say though Abir Deol (@apollo-2006), if you could just keep the bool check to only apply to MSPOut, that'd be great, thanks!

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.
Copilot AI balanced review requested due to automatic review settings October 29, 2026 01:02
@apollo-2006

Abir Deol (apollo-2006) commented Oct 29, 2026 •

Copy link
Copy Markdown
Author

Thanks a lot for this (and for the PSIn explanation, that cleared up what I was missing). Here's what changed:

  • MSPOut only now, so PS inputs are back to the old behavior (I left the 07034 question for its own discussion).
  • bool cull[2]: good catch, I missed this completely 😞 . The old check was stripping the field's own array along with the per-primitive one. It now checks the type before that array is added, so arrays are rejected like in DXIL (new test: cullprimitive.array.hlsl).
  • bool1 / bool1x1: accepted now through isScalarType() (new test: cullprimitive.bool1.hlsl).
  • Alignment nit: applied your suggestion (it fixed the "BUiltin" typo too).

The mesh output cases match DXIL now, and tests and clang-format are clean. Thanks again for making it so easy to fix lol

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

No active review issues are reported for the change that replaces fatal validation failures for invalid SV_CullPrimitive types with normal compiler errors.

No open issues 🎉

1 resolved since last review

Review effort: Balanced

@jzakharovnv

Copy link
Copy Markdown
Collaborator

Great! If you could add a bool2 negative test just to make certain aggregates don't get accepted, then it'd LGTM. Thanks!

@dnovillo

Copy link
Copy Markdown
Collaborator

So, the 2 options are to mirror DXIL's replacement with constant 0 or conform to VUID 07034, but that is an issue out of scope for this PR. What do you think Diego Novillo (Diego Novillo (@dnovillo)).

Agreed. I would mirror DXIL and replace it with false. It doesn't make sense for the SPIR-V backend to error out here since DXIL accepts it and we can do the obvious thing. Abir Deol (@apollo-2006), could you file an issue for it?

@dnovillo Diego Novillo (dnovillo) left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this fix. I agree with Jonathan Zakharov (@jzakharovnv) about negative tests for bool2.

Chris B (@llvm-beanz) or Gregory Roth (@pow2clk) or Damyan Pepper (@damyanp) could you please approve the CI bot? We still don't have maintainer access to trigger those. Thanks.

Comment on lines +3614 to +3623
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;
}
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Move this to validateShaderStageVarType? Next to SV_InnerCoverage so we do semantic type checks in one place.

…arType

Keeps the semantic type checks together, next to SV_InnerCoverage. Adds a bool2 negative test.
@apollo-2006

Copy link
Copy Markdown
Author

Moved the check into validateShaderStageVarType next to SV_InnerCoverage, and added a bool2 negative test (cullprimitive.vector.hlsl). Filed #9084 for the PSIn side. Thanks both :)

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

No active review issues were identified for the mesh-output SV_CullPrimitive type-validation change.

No open issues 🎉

Review effort: Balanced

@pow2clk

Copy link
Copy Markdown
Collaborator

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

@pow2clk
Gregory Roth (pow2clk) enabled auto-merge (squash) October 30, 2026 19:56

@pow2clk Gregory Roth (pow2clk) left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi Abir Deol (@apollo-2006)! I'm afraid I have a rather drastic alternative approach to suggest. While DXC often violates this, there are a lot of advantages to producing errors in Sema. I looked at the way DXIL errors for this and it actually isn't caught until validation, which is far from ideal.

If the error were produced in Sema, it would produce a common error for both DXIL and SPIR-V. I had Claude work up a quick change with a detailed description of how I envisioned the solution: https://github.com/pow2clk/DirectXShaderCompiler/tree/boolcullprim. You are free to take as much or as little of that commit into your change. If you do, please move your excellent tests for expected failures to the SemaHLSL subdirectory with appropriate changes to convert them to validation tests.

The branch: https://github.com/pow2clk/DirectXShaderCompiler/tree/boolcullprim
The commit: pow2clk@1098f70

Note that the test included is in the wrong place. It does demonstrate how you might use different entry point values to consolidate some of your tests though.

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 <groth@nvidia.com>
auto-merge was automatically disabled November 3, 2026 18:39

Head branch was pushed to by a user without write access

Copilot AI balanced review requested due to automatic review settings November 3, 2026 18:39
@apollo-2006 Abir Deol (apollo-2006) changed the title [SPIR-V] Reject non-bool SV_CullPrimitive Diagnose non-bool SV_CullPrimitive mesh outputs in Sema Nov 3, 2026
@apollo-2006

Copy link
Copy Markdown
Author

Thanks, this is way cleaner than doing it per backend. Moved the check into Sema (DXIL and SPIR-V now give the same error) and kept your diagnostic, with you as co-author.

Two small differences from your commit:

  • it only checks mesh primitive outputs (PS inputs are valid in DXIL since they get replaced with 0)
  • bool1 and bool1x1 still count as scalar bools

Error tests are now one -verify file in SemaHLSL. Let me know if that's not what you had in mind :)

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The SV_CullPrimitive validation misses inherited fields and parameter-level semantics while incorrectly rejecting nested fields with enclosing semantic overrides.

3 open issues

Review effort: Balanced

Comment thread tools/clang/lib/Sema/SemaHLSL.cpp Resolved
Comment thread tools/clang/lib/Sema/SemaHLSL.cpp Resolved
Comment thread tools/clang/lib/Sema/SemaHLSL.cpp Resolved
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.
Copilot AI balanced review requested due to automatic review settings November 5, 2026 07:35

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

SV_CullPrimitive validation rejects valid struct-wrapped boolean outputs because it checks record types instead of propagating the effective semantic and validating scalar-bool leaves.

1 open issue
3 resolved since last review

Review effort: Balanced

Comment on lines +17295 to +17299
bool IsScalarBool =
hlsl::IsHLSLVecMatType(ElemTy)
? hlsl::GetElementCount(ElemTy) == 1 &&
hlsl::GetElementTypeOrType(ElemTy)->isBooleanType()
: ElemTy->isBooleanType();

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In progress

Development

Successfully merging this pull request may close these issues.

6 participants