[clang] [HLSL] Add availability attributes to texture sample methods that require implicit derivatives (PR #212846)
Deric C. via cfe-commits
cfe-commits at lists.llvm.org
Wed Jul 29 11:59:15 PDT 2026
https://github.com/Icohedron created https://github.com/llvm/llvm-project/pull/212846
This PR adds availability attributes to texture sample methods that require implicit derivatives (fixes https://github.com/llvm/llvm-project/issues/198885)
To make these availability attributes actually get checked, `DiagnoseHLSLAvailability::HandleFunctionOrMethodRef` in `SemaHLSL.cpp` has been changed to check availability attributes regardless of whether or not a function has a body/definition (fixes https://github.com/llvm/llvm-project/issues/212842).
Assisted by: Claude Opus 5
>From 64503dc8e74a91f94e1a6adde4bb4a9e793009db Mon Sep 17 00:00:00 2001
From: Deric Cheung <cheung.deric at gmail.com>
Date: Wed, 29 Jul 2026 11:32:09 -0700
Subject: [PATCH 1/2] Check availability attributes regardless of if the
function has a definition
---
clang/lib/Sema/SemaHLSL.cpp | 17 ++++++-----------
.../avail-diag-default-compute.hlsl | 8 ++++++++
2 files changed, 14 insertions(+), 11 deletions(-)
diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index e26b18552d362..0af1cce24fbde 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -2987,19 +2987,14 @@ void DiagnoseHLSLAvailability::HandleFunctionOrMethodRef(FunctionDecl *FD,
assert((isa<DeclRefExpr>(RefExpr) || isa<MemberExpr>(RefExpr)) &&
"expected DeclRefExpr or MemberExpr");
- // has a definition -> add to stack to be scanned
- const FunctionDecl *FDWithBody = nullptr;
- if (FD->hasBody(FDWithBody)) {
- if (!WasAlreadyScannedInCurrentStage(FDWithBody))
- DeclsToScan.push_back(FDWithBody);
- return;
- }
-
- // no body -> diagnose availability
- const AvailabilityAttr *AA = FindAvailabilityAttr(FD);
- if (AA)
+ if (const AvailabilityAttr *AA = FindAvailabilityAttr(FD))
CheckDeclAvailability(
FD, AA, SourceRange(RefExpr->getBeginLoc(), RefExpr->getEndLoc()));
+
+ // has a definition -> add to stack to be scanned
+ const FunctionDecl *FDWithBody = nullptr;
+ if (FD->hasBody(FDWithBody) && !WasAlreadyScannedInCurrentStage(FDWithBody))
+ DeclsToScan.push_back(FDWithBody);
}
void DiagnoseHLSLAvailability::RunOnTranslationUnit(
diff --git a/clang/test/SemaHLSL/Availability/avail-diag-default-compute.hlsl b/clang/test/SemaHLSL/Availability/avail-diag-default-compute.hlsl
index 1424fe63242ae..75b5f160a3cb1 100644
--- a/clang/test/SemaHLSL/Availability/avail-diag-default-compute.hlsl
+++ b/clang/test/SemaHLSL/Availability/avail-diag-default-compute.hlsl
@@ -15,6 +15,11 @@ __attribute__((availability(shadermodel, introduced = 5.0, environment = pixel))
__attribute__((availability(shadermodel, introduced = 6.5, environment = mesh)))
float fz(float); // #fz
+// A function that has a definition is diagnosed based on its availability
+// attribute as well; having a body does not make it available everywhere.
+__attribute__((availability(shadermodel, introduced = 6.5)))
+float fdef(float f) { return f; } // #fdef
+
float also_alive(float f) {
// expected-error@#also_alive_fx_call {{'fx' is only available on Shader Model 6.5 or newer}}
// expected-note@#fx {{'fx' has been marked as being introduced in Shader Model 6.5 here, but the deployment target is Shader Model 6.0}}
@@ -115,4 +120,7 @@ void main() {
float c = C.makeF();
float d = test((float)1.0);
float e = test((half)1.0);
+ // expected-error@#main_fdef_call {{'fdef' is only available on Shader Model 6.5 or newer}}
+ // expected-note@#fdef {{'fdef' has been marked as being introduced in Shader Model 6.5 here, but the deployment target is Shader Model 6.0}}
+ float g = fdef(f); // #main_fdef_call
}
>From e1470028ad9c4040c3d5472629a49a0b99e37001 Mon Sep 17 00:00:00 2001
From: Deric Cheung <cheung.deric at gmail.com>
Date: Wed, 29 Jul 2026 11:53:26 -0700
Subject: [PATCH 2/2] Add availability attributes to samplers that require
implicit derivatives
---
clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp | 61 +++++++++++-
clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.h | 1 +
.../Textures-derivative-availability.hlsl | 98 +++++++++++++++++++
3 files changed, 155 insertions(+), 5 deletions(-)
create mode 100644 clang/test/SemaHLSL/Resources/Textures-derivative-availability.hlsl
diff --git a/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp b/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp
index 65b2da923d461..337e8795528c8 100644
--- a/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp
+++ b/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp
@@ -96,6 +96,32 @@ QualType getInoutParameterType(ASTContext &AST, QualType Ty) {
return Ty;
}
+// Attaches availability attributes to a method that requires implicit
+// derivatives. Implicit derivatives are always available in pixel
+// shaders. Shader Model 6.6 made derivatives available in compute, mesh and
+// amplification shaders as well. All other shader stages do not support
+// derivatives.
+void addDerivativeAvailabilityAttrs(ASTContext &AST, FunctionDecl *FD) {
+ struct DerivativeShaderStage {
+ StringRef Environment;
+ VersionTuple Introduced;
+ };
+ const DerivativeShaderStage Stages[] = {
+ {"pixel", VersionTuple(6, 0)},
+ {"compute", VersionTuple(6, 6)},
+ {"mesh", VersionTuple(6, 6)},
+ {"amplification", VersionTuple(6, 6)},
+ };
+
+ const IdentifierInfo *Platform = &AST.Idents.get("shadermodel");
+ for (const DerivativeShaderStage &Stage : Stages)
+ FD->addAttr(AvailabilityAttr::CreateImplicit(
+ AST, Platform, Stage.Introduced, /*Deprecated=*/VersionTuple(),
+ /*Obsoleted=*/VersionTuple(), /*Unavailable=*/false, /*Message=*/"",
+ /*Strict=*/false, /*Replacement=*/"", Sema::AP_Explicit,
+ &AST.Idents.get(Stage.Environment), /*InferredAttr=*/nullptr));
+}
+
} // namespace
// Builder for template arguments of builtin types. Used internally
@@ -1664,6 +1690,19 @@ BuiltinTypeDeclBuilder::addByteAddressBufferInterlockedMethods() {
return *this;
}
+BuiltinTypeDeclBuilder &
+BuiltinTypeDeclBuilder::addDerivativeAvailability(StringRef MethodName) {
+ ASTContext &AST = Record->getASTContext();
+ DeclarationName Name(&AST.Idents.get(MethodName, tok::TokenKind::identifier));
+ for (NamedDecl *D : Record->lookup(Name)) {
+ if (auto *FTD = dyn_cast<FunctionTemplateDecl>(D))
+ D = FTD->getTemplatedDecl();
+ if (auto *MD = dyn_cast<CXXMethodDecl>(D))
+ addDerivativeAvailabilityAttrs(AST, MD);
+ }
+ return *this;
+}
+
BuiltinTypeDeclBuilder &
BuiltinTypeDeclBuilder::addSampleMethods(ResourceDimension Dim, bool IsArray) {
assert(!Record->isCompleteDefinition() && "record is already complete");
@@ -1701,7 +1740,7 @@ BuiltinTypeDeclBuilder::addSampleMethods(ResourceDimension Dim, bool IsArray) {
.finalize();
// T Sample(SamplerState s, float2 location, int2 offset, float clamp)
- return BuiltinTypeMethodBuilder(*this, "Sample", ReturnType)
+ BuiltinTypeMethodBuilder(*this, "Sample", ReturnType)
.addParam("Sampler", SamplerStateType)
.addParam("Location", CoordTy)
.addParam("Offset", OffsetTy)
@@ -1711,6 +1750,9 @@ BuiltinTypeDeclBuilder::addSampleMethods(ResourceDimension Dim, bool IsArray) {
PH::LastStmt, PH::_1, PH::_2, PH::_3)
.returnValue(PH::LastStmt)
.finalize();
+
+ // Sample uses implicit derivatives to calculate the mip level.
+ return addDerivativeAvailability("Sample");
}
BuiltinTypeDeclBuilder &
@@ -1754,7 +1796,7 @@ BuiltinTypeDeclBuilder::addSampleBiasMethods(ResourceDimension Dim,
// T SampleBias(SamplerState s, float2 location, float bias, int2 offset,
// float clamp)
- return BuiltinTypeMethodBuilder(*this, "SampleBias", ReturnType)
+ BuiltinTypeMethodBuilder(*this, "SampleBias", ReturnType)
.addParam("Sampler", SamplerStateType)
.addParam("Location", CoordTy)
.addParam("Bias", FloatTy)
@@ -1765,6 +1807,9 @@ BuiltinTypeDeclBuilder::addSampleBiasMethods(ResourceDimension Dim,
PH::Handle, PH::LastStmt, PH::_1, PH::_2, PH::_3, PH::_4)
.returnValue(PH::LastStmt)
.finalize();
+
+ // SampleBias uses implicit derivatives to calculate the mip level.
+ return addDerivativeAvailability("SampleBias");
}
BuiltinTypeDeclBuilder &
@@ -1909,7 +1954,7 @@ BuiltinTypeDeclBuilder::addSampleCmpMethods(ResourceDimension Dim,
// T SampleCmp(SamplerComparisonState s, float2 location, float compare_value,
// int2 offset, float clamp)
- return BuiltinTypeMethodBuilder(*this, "SampleCmp", ReturnType)
+ BuiltinTypeMethodBuilder(*this, "SampleCmp", ReturnType)
.addParam("Sampler", SamplerComparisonStateType)
.addParam("Location", CoordTy)
.addParam("CompareValue", FloatTy)
@@ -1920,6 +1965,9 @@ BuiltinTypeDeclBuilder::addSampleCmpMethods(ResourceDimension Dim,
PH::LastStmt, PH::_1, PH::_2, PH::_3, PH::_4)
.returnValue(PH::LastStmt)
.finalize();
+
+ // SampleCmp uses implicit derivatives to calculate the mip level.
+ return addDerivativeAvailability("SampleCmp");
}
BuiltinTypeDeclBuilder &
@@ -2033,14 +2081,17 @@ BuiltinTypeDeclBuilder::addCalculateLodMethods(ResourceDimension Dim) {
.finalize();
// float CalculateLevelOfDetailUnclamped(SamplerState s, float2 location)
- return BuiltinTypeMethodBuilder(*this, "CalculateLevelOfDetailUnclamped",
- ReturnType)
+ BuiltinTypeMethodBuilder(*this, "CalculateLevelOfDetailUnclamped", ReturnType)
.addParam("Sampler", SamplerStateType)
.addParam("Location", LocationTy)
.accessHandleFieldOnResource(PH::_0)
.callBuiltin("__builtin_hlsl_resource_calculate_lod_unclamped",
ReturnType, PH::Handle, PH::LastStmt, PH::_1)
.finalize();
+
+ // Both methods use implicit derivatives to calculate the level of detail.
+ addDerivativeAvailability("CalculateLevelOfDetail");
+ return addDerivativeAvailability("CalculateLevelOfDetailUnclamped");
}
QualType BuiltinTypeDeclBuilder::getGatherReturnType() {
diff --git a/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.h b/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.h
index e809ef264198c..afb336045b1f7 100644
--- a/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.h
+++ b/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.h
@@ -168,6 +168,7 @@ class BuiltinTypeDeclBuilder {
QualType ElementTy,
AccessSpecifier Access = AccessSpecifier::AS_private);
QualType getGatherReturnType();
+ BuiltinTypeDeclBuilder &addDerivativeAvailability(StringRef MethodName);
FieldDecl *getResourceHandleField() const;
FieldDecl *getResourceCounterHandleField() const;
QualType getFirstTemplateTypeParam();
diff --git a/clang/test/SemaHLSL/Resources/Textures-derivative-availability.hlsl b/clang/test/SemaHLSL/Resources/Textures-derivative-availability.hlsl
new file mode 100644
index 0000000000000..9d6ce30309de9
--- /dev/null
+++ b/clang/test/SemaHLSL/Resources/Textures-derivative-availability.hlsl
@@ -0,0 +1,98 @@
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.6-library -finclude-default-header \
+// RUN: -fsyntax-only -verify=expected,sm66 -DTEXTURE=Texture2D -DCOORD_TYPE=float2 %s
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.5-library -finclude-default-header \
+// RUN: -fsyntax-only -verify=expected,sm65 -DTEXTURE=Texture2D -DCOORD_TYPE=float2 %s
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.6-library -finclude-default-header \
+// RUN: -fsyntax-only -verify=expected,sm66 -DTEXTURE=Texture2DArray -DCOORD_TYPE=float3 %s
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.5-library -finclude-default-header \
+// RUN: -fsyntax-only -verify=expected,sm65 -DTEXTURE=Texture2DArray -DCOORD_TYPE=float3 %s
+
+// Texture methods that rely on implicit derivatives are available in pixel
+// shaders since Shader Model 6.0 and in compute, mesh and amplification
+// shaders since Shader Model 6.6. They are not available in any other shader
+// stage.
+
+TEXTURE<float4> tex;
+SamplerState samp;
+SamplerComparisonState cmpSamp;
+
+// Derivatives are always available in pixel shaders; no diagnostics expected.
+[shader("pixel")]
+void PixelEntry() {
+ COORD_TYPE loc = (COORD_TYPE)0;
+ float2 lodLoc = float2(0, 0);
+
+ tex.Sample(samp, loc);
+ tex.SampleBias(samp, loc, 0.5);
+ tex.SampleCmp(cmpSamp, loc, 0.5);
+ tex.CalculateLevelOfDetail(samp, lodLoc);
+ tex.CalculateLevelOfDetailUnclamped(samp, lodLoc);
+}
+
+// Derivatives are available in compute shaders only since Shader Model 6.6.
+[shader("compute")]
+[numthreads(1, 1, 1)]
+void ComputeEntry() {
+ COORD_TYPE loc = (COORD_TYPE)0;
+ float2 lodLoc = float2(0, 0);
+
+ // sm65-error at +2 {{'Sample' is only available in compute environment on Shader Model 6.6 or newer}}
+ // sm65-note@* {{'Sample' has been marked as being introduced in Shader Model 6.6 in compute environment here, but the deployment target is Shader Model 6.5 compute environment}}
+ tex.Sample(samp, loc);
+
+ // sm65-error at +2 {{'SampleBias' is only available in compute environment on Shader Model 6.6 or newer}}
+ // sm65-note@* {{'SampleBias' has been marked as being introduced in Shader Model 6.6 in compute environment here}}
+ tex.SampleBias(samp, loc, 0.5);
+
+ // sm65-error at +2 {{'SampleCmp' is only available in compute environment on Shader Model 6.6 or newer}}
+ // sm65-note@* {{'SampleCmp' has been marked as being introduced in Shader Model 6.6 in compute environment here}}
+ tex.SampleCmp(cmpSamp, loc, 0.5);
+
+ // sm65-error at +2 {{'CalculateLevelOfDetail' is only available in compute environment on Shader Model 6.6 or newer}}
+ // sm65-note@* {{'CalculateLevelOfDetail' has been marked as being introduced in Shader Model 6.6 in compute environment here}}
+ tex.CalculateLevelOfDetail(samp, lodLoc);
+
+ // sm65-error at +2 {{'CalculateLevelOfDetailUnclamped' is only available in compute environment on Shader Model 6.6 or newer}}
+ // sm65-note@* {{'CalculateLevelOfDetailUnclamped' has been marked as being introduced in Shader Model 6.6 in compute environment here}}
+ tex.CalculateLevelOfDetailUnclamped(samp, lodLoc);
+}
+
+// Vertex shaders do not support derivatives in any shader model.
+[shader("vertex")]
+void VertexEntry() {
+ COORD_TYPE loc = (COORD_TYPE)0;
+ float2 lodLoc = float2(0, 0);
+
+ // expected-error at +2 {{'Sample' is unavailable}}
+ // expected-note@* {{'Sample' has been marked as being introduced in Shader Model}}
+ tex.Sample(samp, loc);
+
+ // expected-error at +2 {{'SampleBias' is unavailable}}
+ // expected-note@* {{'SampleBias' has been marked as being introduced in Shader Model}}
+ tex.SampleBias(samp, loc, 0.5);
+
+ // expected-error at +2 {{'SampleCmp' is unavailable}}
+ // expected-note@* {{'SampleCmp' has been marked as being introduced in Shader Model}}
+ tex.SampleCmp(cmpSamp, loc, 0.5);
+
+ // expected-error at +2 {{'CalculateLevelOfDetail' is unavailable}}
+ // expected-note@* {{'CalculateLevelOfDetail' has been marked as being introduced in Shader Model}}
+ tex.CalculateLevelOfDetail(samp, lodLoc);
+
+ // expected-error at +2 {{'CalculateLevelOfDetailUnclamped' is unavailable}}
+ // expected-note@* {{'CalculateLevelOfDetailUnclamped' has been marked as being introduced in Shader Model}}
+ tex.CalculateLevelOfDetailUnclamped(samp, lodLoc);
+}
+
+// Methods that take an explicit LOD or explicit gradients do not require
+// derivatives and are available in all shader stages; no diagnostics expected.
+[shader("vertex")]
+void ExplicitLodVertexEntry() {
+ COORD_TYPE loc = (COORD_TYPE)0;
+ float2 grad = float2(0, 0);
+
+ tex.SampleLevel(samp, loc, 0);
+ tex.SampleGrad(samp, loc, grad, grad);
+ tex.SampleCmpLevelZero(cmpSamp, loc, 0.5);
+ tex.Gather(samp, loc);
+}
More information about the cfe-commits
mailing list