[clang] [HLSL] Implement variable templates for HLSL 202x (PR #235273)
via cfe-commits
cfe-commits at lists.llvm.org
Sat Oct 24 04:05:30 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-hlsl
Author: Divyansh Yadav (schizophrenicmaniac)
<details>
<summary>Changes</summary>
Variable templates are part of HLSL 202x (proposal 0010), but until now HLSL always hit the C++14 compat diagnostic for them. This adds a "variable templates are a HLSL 202x extension" warning under a new `-Whlsl-202x-extensions` group (part of `-Whlsl-extensions`). It fires only for HLSL 2021 and earlier, the same way the 202y `auto` warning is handled.
Testing it showed that instantiated specializations never went through `SemaHLSL::ActOnVariableDeclarator`, so they skipped the usual handling for HLSL globals. A `static` variable template ended up in the default address space, so on SPIR-V the global was in addrspace(0) instead of addrspace(10). A non-static one became a plain mutable global instead of a `$Globals` constant. Meanwhile the template pattern itself was added to `$Globals` when its type didn't depend on the template parameters. Now:
- instantiated specializations go through `ActOnVariableDeclarator` right after their attributes are instantiated, as normal declarations do;
- the pattern and partial specializations are skipped there;
- `CompleteVarTemplateSpecializationDecl` keeps the address space when it re-substitutes the type for the definition;
- an explicit specialization of a `static` variable template is treated as static (explicit specializations can't spell a storage class themselves).
Open question: I made non-static variable template specializations behave like any other non-static global, so each one used becomes a member of `$Globals`. If the proposal intends something else for those, such as requiring `static` or treating them differently, I'm happy to change it.
Fixes #<!-- -->235123
---
Full diff: https://github.com/llvm/llvm-project/pull/235273.diff
7 Files Affected:
- (modified) clang/include/clang/Basic/DiagnosticGroups.td (+2-1)
- (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+2)
- (modified) clang/lib/Sema/SemaDecl.cpp (+5-2)
- (modified) clang/lib/Sema/SemaHLSL.cpp (+13)
- (modified) clang/lib/Sema/SemaTemplateInstantiateDecl.cpp (+11-2)
- (added) clang/test/CodeGenHLSL/variable-templates.hlsl (+51)
- (added) clang/test/SemaHLSL/Language/VariableTemplates.hlsl (+39)
``````````diff
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index b3bb2fb1b53fd..6f900d26e3a9b 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -1974,10 +1974,11 @@ def PedanticMacros : DiagGroup<"pedantic-macros",
def BranchProtection : DiagGroup<"branch-protection">;
// HLSL diagnostic groups
+def HLSL202x : DiagGroup<"hlsl-202x-extensions">;
def HLSL202y : DiagGroup<"hlsl-202y-extensions">;
// Warnings for HLSL Clang extensions
-def HLSLExtension : DiagGroup<"hlsl-extensions", [HLSL202y]>;
+def HLSLExtension : DiagGroup<"hlsl-extensions", [HLSL202x, HLSL202y]>;
// Warning for mix packoffset and non-packoffset.
def HLSLMixPackOffset : DiagGroup<"mix-packoffset">;
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index dcb5732c3c19e..a2438a2b78718 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -13855,6 +13855,8 @@ def err_hlsl_rootsignature_entry: Error<
def err_hlsl_operator_unsupported : Error<
"the '%select{&|*|->}0' operator is unsupported in HLSL">;
+def ext_hlsl_variable_template : ExtWarn<
+ "variable templates are a HLSL 202x extension">, InGroup<HLSL202x>;
def warn_hlsl_groupshared_202x : Warning<
"support for groupshared parameter annotation not added until HLSL 202x">,
InGroup<HLSLAvailability>;
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index fba96fe7abb2b..46a454e6e8bb9 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -8015,8 +8015,11 @@ NamedDecl *Sema::ActOnVariableDeclarator(
// This is a template declaration.
IsVariableTemplate = true;
- // Only C++1y supports variable templates (N3651).
- DiagCompat(D.getIdentifierLoc(), diag_compat::variable_template);
+ // Only C++1y and HLSL 202x support variable templates (N3651).
+ if (!getLangOpts().HLSL)
+ DiagCompat(D.getIdentifierLoc(), diag_compat::variable_template);
+ else if (getLangOpts().getHLSLVersion() < LangOptions::HLSL_202x)
+ Diag(D.getIdentifierLoc(), diag::ext_hlsl_variable_template);
}
}
} else {
diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index f3fd1fb68c072..f119edb6ebdc4 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -6238,6 +6238,19 @@ void SemaHLSL::handleGlobalStructOrArrayOfWithResources(VarDecl *VD) {
}
void SemaHLSL::ActOnVariableDeclarator(VarDecl *VD) {
+ // Variable templates are handled when their specializations are
+ // instantiated.
+ if (VD->getDescribedVarTemplate() ||
+ isa<VarTemplatePartialSpecializationDecl>(VD))
+ return;
+
+ // An explicit specialization cannot have a storage class of its own, so it is
+ // static if the variable template it specializes is.
+ if (auto *VTSD = dyn_cast<VarTemplateSpecializationDecl>(VD))
+ if (VTSD->getSpecializedTemplate()->getTemplatedDecl()->getStorageClass() ==
+ SC_Static)
+ VD->setStorageClass(SC_Static);
+
if (VD->hasGlobalStorage()) {
// make sure the declaration has a complete type
if (SemaRef.RequireCompleteType(
diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
index b4881d0c6db5c..6f1b3e37ef286 100644
--- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -6405,8 +6405,13 @@ VarTemplateSpecializationDecl *Sema::CompleteVarTemplateSpecializationDecl(
if (!TSI)
return nullptr;
- // Update the type of this variable template specialization.
- VarSpec->setType(TSI->getType());
+ // Update the type of this variable template specialization. HLSL assigns
+ // the address space when the specialization is declared, so keep it.
+ QualType T = TSI->getType();
+ if (getLangOpts().HLSL && VarSpec->getType().hasAddressSpace() &&
+ !T.hasAddressSpace())
+ T = Context.getAddrSpaceQualType(T, VarSpec->getType().getAddressSpace());
+ VarSpec->setType(T);
// Convert the declaration into a definition now.
VarSpec->setCompleteDefinition();
@@ -6467,6 +6472,10 @@ void Sema::BuildVariableInstantiation(
InstantiateAttrs(TemplateArgs, OldVar, NewVar, LateAttrs, StartingScope);
+ if (getLangOpts().HLSL && isa<VarTemplateSpecializationDecl>(NewVar) &&
+ !isa<VarTemplatePartialSpecializationDecl>(NewVar))
+ HLSL().ActOnVariableDeclarator(NewVar);
+
LookupResult Previous(
*this, NewVar->getDeclName(), NewVar->getLocation(),
NewVar->isLocalExternDecl() ? Sema::LookupRedeclarationWithLinkage
diff --git a/clang/test/CodeGenHLSL/variable-templates.hlsl b/clang/test/CodeGenHLSL/variable-templates.hlsl
new file mode 100644
index 0000000000000..6bb81c035b75c
--- /dev/null
+++ b/clang/test/CodeGenHLSL/variable-templates.hlsl
@@ -0,0 +1,51 @@
+// RUN: %clang_cc1 -finclude-default-header -triple dxil-pc-shadermodel6.3-library -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s --check-prefixes=CHECK,DXIL
+// RUN: %clang_cc1 -finclude-default-header -triple spirv-unknown-vulkan1.3-library -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s --check-prefixes=CHECK,SPIRV
+
+template <typename T> static T Counter = T(1);
+template <> int Counter<int> = 7;
+
+template <typename T> static const T Scale[2] = {T(2), T(3)};
+
+template <typename T> T Global;
+template <typename T> uint Index;
+
+template <typename T> groupshared T Shared[4];
+
+struct S {
+ template <typename T> static T Member;
+};
+template <typename T> T S::Member = T(5);
+
+// DXIL-DAG: @_ZL7CounterIfE = internal global float 1.000000e+00, align 4
+// DXIL-DAG: @_ZL7CounterIiE = internal global i32 7, align 4
+// DXIL-DAG: @_ZL5ScaleIfE = internal constant [2 x float] [float 2.000000e+00, float 3.000000e+00], align 4
+// DXIL-DAG: @_Z6GlobalIfE = external hidden addrspace(2) global float, align 4
+// DXIL-DAG: @_Z5IndexIfE = external hidden addrspace(2) global i32, align 4
+// DXIL-DAG: @_Z6SharedIfE = external hidden addrspace(3) global [4 x float], align 4
+// DXIL-DAG: @_ZN1S6MemberIfEE = linkonce_odr hidden global float 5.000000e+00, align 4
+
+// SPIRV-DAG: @_ZL7CounterIfE = internal addrspace(10) global float 1.000000e+00, align 4
+// SPIRV-DAG: @_ZL7CounterIiE = internal addrspace(10) global i32 7, align 4
+// SPIRV-DAG: @_ZL5ScaleIfE = internal addrspace(10) constant [2 x float] [float 2.000000e+00, float 3.000000e+00], align 4
+// SPIRV-DAG: @_Z6GlobalIfE = external hidden addrspace(12) global float, align 4
+// SPIRV-DAG: @_Z5IndexIfE = external hidden addrspace(12) global i32, align 4
+// SPIRV-DAG: @_Z6SharedIfE = external hidden addrspace(3) global [4 x float], align 4
+// SPIRV-DAG: @_ZN1S6MemberIfEE = linkonce_odr hidden addrspace(10) global float 5.000000e+00, comdat, align 4
+
+// CHECK-LABEL: define {{.*}}float @_Z2fnj(
+export float fn(uint I) {
+// DXIL: store float {{.*}}, ptr @_ZL7CounterIfE, align 4
+// SPIRV: store float {{.*}}, ptr addrspace(10) @_ZL7CounterIfE, align 4
+ Counter<float> += 1.0;
+// DXIL: load float, ptr addrspace(2) @_Z6GlobalIfE, align 4
+// SPIRV: load float, ptr addrspace(12) @_Z6GlobalIfE, align 4
+ Shared<float>[I] = Global<float>;
+// DXIL: load i32, ptr @_ZL7CounterIiE, align 4
+// SPIRV: load i32, ptr addrspace(10) @_ZL7CounterIiE, align 4
+ return Counter<float> + Counter<int> + Scale<float>[I] + Shared<float>[I] +
+ S::Member<float> + Index<float>;
+}
+
+// CHECK: !hlsl.cbs = !{![[CB:.*]]}
+// DXIL: ![[CB]] = !{ptr @"$Globals.cb", ptr addrspace(2) @_Z6GlobalIfE, ptr addrspace(2) @_Z5IndexIfE}
+// SPIRV: ![[CB]] = !{ptr @"$Globals.cb", ptr addrspace(12) @_Z6GlobalIfE, ptr addrspace(12) @_Z5IndexIfE}
diff --git a/clang/test/SemaHLSL/Language/VariableTemplates.hlsl b/clang/test/SemaHLSL/Language/VariableTemplates.hlsl
new file mode 100644
index 0000000000000..89c370dfc7709
--- /dev/null
+++ b/clang/test/SemaHLSL/Language/VariableTemplates.hlsl
@@ -0,0 +1,39 @@
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-library -finclude-default-header -std=hlsl2021 -fsyntax-only -verify %s
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-library -finclude-default-header -std=hlsl202x -fsyntax-only -verify %s
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-library -finclude-default-header -std=hlsl202y -fsyntax-only -verify %s
+
+#if __HLSL_VERSION < 2028
+// expected-warning@*{{support for HLSL language version hlsl2021 is incomplete, recommend using hlsl202x instead}}
+// expected-warning@#pi{{variable templates are a HLSL 202x extension}}
+// expected-warning@#zero{{variable templates are a HLSL 202x extension}}
+// expected-warning@#arr{{variable templates are a HLSL 202x extension}}
+// expected-warning@#max{{variable templates are a HLSL 202x extension}}
+#endif
+
+template <typename T> static const T Pi = T(3.14159265); // #pi
+template <> const int Pi<int> = 3;
+
+template <typename T> static T Zero = T(0); // #zero
+
+template <typename T, uint N> static const T Arr[N] = {T(1), T(2)}; // #arr
+
+struct S {
+ template <typename T> static const T Max = T(100); // #max
+};
+
+_Static_assert(Pi<int> == 3, "explicit specialization");
+_Static_assert(Pi<uint> == 3, "implicit instantiation");
+_Static_assert(S::Max<int> == 100, "static data member template");
+
+template <typename T> T twice() { return Pi<T> * 2; }
+
+export float fn(uint I) {
+ Zero<float> += 1.0;
+ return twice<float>() + Zero<float> + Arr<float, 2>[I];
+}
+
+export float bad() {
+ // expected-error at +1 {{use of variable template 'Pi' requires template arguments}}
+ return Pi;
+ // expected-note@#pi {{template is declared here}}
+}
``````````
</details>
https://github.com/llvm/llvm-project/pull/235273
More information about the cfe-commits
mailing list