https://github.com/s-perron created https://github.com/llvm/llvm-project/pull/142638
The vk::constant_id attribute is used to indicate that a global const variable represents a specialization constant in SPIR-V. This PR adds this attribute to clang. The documetation for the attribute is [here](https://github.com/microsoft/DirectXShaderCompiler/blob/main/docs/SPIR-V.rst#specialization-constants). Fixes #142448 >From 022bc4d031ea1655b82dc701fff315abd002ca15 Mon Sep 17 00:00:00 2001 From: Steven Perron <stevenper...@google.com> Date: Fri, 30 May 2025 12:32:21 -0400 Subject: [PATCH] [HLSL][SPIRV] Add vk::constant_id attribute. The vk::constant_id attribute is used to indicate that a global const variable represents a specialization constant in SPIR-V. This PR adds this attribute to clang. The documetation for the attribute is [here](https://github.com/microsoft/DirectXShaderCompiler/blob/main/docs/SPIR-V.rst#specialization-constants). Fixes #142448 --- clang/include/clang/Basic/Attr.td | 8 +++ clang/include/clang/Basic/AttrDocs.td | 15 +++++ .../clang/Basic/DiagnosticSemaKinds.td | 15 +++++ clang/include/clang/Sema/SemaHLSL.h | 3 + clang/lib/AST/ExprConstant.cpp | 5 ++ clang/lib/Basic/Attributes.cpp | 3 +- clang/lib/CodeGen/CodeGenModule.cpp | 23 +++++++ clang/lib/Sema/SemaDecl.cpp | 18 ++++++ clang/lib/Sema/SemaDeclAttr.cpp | 3 + clang/lib/Sema/SemaHLSL.cpp | 61 ++++++++++++++++++- .../test/AST/HLSL/vk.spec-constnat.usage.hlsl | 12 ++++ .../SpirvType.alignment.hlsl | 0 .../SpirvType.hlsl | 0 .../vk-features/vk.spec-constant.hlsl | 44 +++++++++++++ .../SemaHLSL/vk.spec-constant.error1.hlsl | 39 ++++++++++++ 15 files changed, 247 insertions(+), 2 deletions(-) create mode 100644 clang/test/AST/HLSL/vk.spec-constnat.usage.hlsl rename clang/test/CodeGenHLSL/{inline-spirv => vk-features}/SpirvType.alignment.hlsl (100%) rename clang/test/CodeGenHLSL/{inline-spirv => vk-features}/SpirvType.hlsl (100%) create mode 100644 clang/test/CodeGenHLSL/vk-features/vk.spec-constant.hlsl create mode 100644 clang/test/SemaHLSL/vk.spec-constant.error1.hlsl diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td index db02449a3dd12..51d1ddf80c65f 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -4949,6 +4949,14 @@ def HLSLWaveSize: InheritableAttr { let Documentation = [WaveSizeDocs]; } +def HLSLVkConstantId : InheritableAttr { + let Spellings = [CXX11<"vk", "constant_id">]; + let Args = [IntArgument<"Id">]; + let Subjects = SubjectList<[Var]>; + let LangOpts = [HLSL]; + let Documentation = [VkConstantIdDocs]; +} + def RandomizeLayout : InheritableAttr { let Spellings = [GCC<"randomize_layout">]; let Subjects = SubjectList<[Record]>; diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td index 65d66dd398ad1..8ddf109b733c1 100644 --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -8195,6 +8195,21 @@ and https://microsoft.github.io/hlsl-specs/proposals/0013-wave-size-range.html }]; } +def VkConstantIdDocs : Documentation { + let Category = DocCatFunction; + let Content = [{ +The ``vk::constant_id`` attribute specify the id for a SPIR-V specialization +constant. The attribute applies to const global scalar variables. The variable must be initialized with a C++11 constexpr. +In SPIR-V, the +variable will be replaced with an `OpSpecConstant` with the given id. +The syntax is: + +.. code-block:: text + + ``[[vk::constant_id(<Id>)]] const T Name = <Init>`` +}]; +} + def RootSignatureDocs : Documentation { let Category = DocCatFunction; let Content = [{ diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index efc842bb4c42e..9214775246a15 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -12888,6 +12888,21 @@ def err_spirv_enum_not_int : Error< def err_spirv_enum_not_valid : Error< "invalid value for %select{storage class}0 argument">; +def err_specialization_const_lit_init + : Error<"variable with 'vk::constant_id' attribute cannot have an " + "initializer that is not a constexpr">; +def err_specialization_const_is_not_externally_visible + : Error<"variable with 'vk::constant_id' attribute must be externally " + "visible">; +def err_specialization_const_missing_initializer + : Error< + "variable with 'vk::constant_id' attribute must have an initializer">; +def err_specialization_const_missing_const + : Error<"variable with 'vk::constant_id' attribute must be const">; +def err_specialization_const_is_not_int_or_float + : Error<"variable with 'vk::constant_id' attribute must be an enum, bool, " + "integer, or floating point value">; + // errors of expect.with.probability def err_probability_not_constant_float : Error< "probability argument to __builtin_expect_with_probability must be constant " diff --git a/clang/include/clang/Sema/SemaHLSL.h b/clang/include/clang/Sema/SemaHLSL.h index 15182bb27bbdf..d9442f552c73f 100644 --- a/clang/include/clang/Sema/SemaHLSL.h +++ b/clang/include/clang/Sema/SemaHLSL.h @@ -98,6 +98,8 @@ class SemaHLSL : public SemaBase { HLSLWaveSizeAttr *mergeWaveSizeAttr(Decl *D, const AttributeCommonInfo &AL, int Min, int Max, int Preferred, int SpelledArgsCount); + HLSLVkConstantIdAttr * + mergeVkConstantIdAttr(Decl *D, const AttributeCommonInfo &AL, int Id); HLSLShaderAttr *mergeShaderAttr(Decl *D, const AttributeCommonInfo &AL, llvm::Triple::EnvironmentType ShaderType); HLSLParamModifierAttr * @@ -122,6 +124,7 @@ class SemaHLSL : public SemaBase { void handleRootSignatureAttr(Decl *D, const ParsedAttr &AL); void handleNumThreadsAttr(Decl *D, const ParsedAttr &AL); void handleWaveSizeAttr(Decl *D, const ParsedAttr &AL); + void handleVkConstantIdAttr(Decl *D, const ParsedAttr &AL); void handleSV_DispatchThreadIDAttr(Decl *D, const ParsedAttr &AL); void handleSV_GroupThreadIDAttr(Decl *D, const ParsedAttr &AL); void handleSV_GroupIDAttr(Decl *D, const ParsedAttr &AL); diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index b20e2690d0eee..f4b32f297bfcc 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -3570,6 +3570,11 @@ static bool evaluateVarDeclInit(EvalInfo &Info, const Expr *E, if (E->isValueDependent()) return false; + // The initializer on a specialization constant is only its default value + // when it is not externally initialized. This value cannot be evaluated. + if (VD->hasAttr<HLSLVkConstantIdAttr>()) + return false; + // Dig out the initializer, and use the declaration which it's attached to. // FIXME: We should eventually check whether the variable has a reachable // initializing declaration. diff --git a/clang/lib/Basic/Attributes.cpp b/clang/lib/Basic/Attributes.cpp index ebc86a5adf7a7..241fdf644c5ba 100644 --- a/clang/lib/Basic/Attributes.cpp +++ b/clang/lib/Basic/Attributes.cpp @@ -213,7 +213,8 @@ getScopeFromNormalizedScopeName(StringRef ScopeName) { .Case("hlsl", AttributeCommonInfo::Scope::HLSL) .Case("msvc", AttributeCommonInfo::Scope::MSVC) .Case("omp", AttributeCommonInfo::Scope::OMP) - .Case("riscv", AttributeCommonInfo::Scope::RISCV); + .Case("riscv", AttributeCommonInfo::Scope::RISCV) + .Case("vk", AttributeCommonInfo::Scope::HLSL); } unsigned AttributeCommonInfo::calculateAttributeSpellingListIndex() const { diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp index 6d2c705338ecf..20f85bbf3fc0e 100644 --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -5191,6 +5191,29 @@ CodeGenModule::GetOrCreateLLVMGlobal(StringRef MangledName, llvm::Type *Ty, if (const auto *CMA = D->getAttr<CodeModelAttr>()) GV->setCodeModel(CMA->getModel()); + if (const auto *ConstIdAttr = D->getAttr<HLSLVkConstantIdAttr>()) { + const Expr *Init = D->getInit(); + APValue InitValue; + bool IsConstExpr = Init->isCXX11ConstantExpr(getContext(), &InitValue); + assert(IsConstExpr && + "HLSLVkConstantIdAttr requires a constant initializer"); + llvm::SmallString<10> InitString; + switch (InitValue.getKind()) { + case APValue::ValueKind::Int: + InitValue.getInt().toString(InitString); + break; + case APValue::ValueKind::Float: + InitValue.getFloat().toString(InitString); + break; + default: + llvm_unreachable( + "HLSLVkConstantIdAttr requires an int or float initializer"); + } + std::string ConstIdStr = + (llvm::Twine(ConstIdAttr->getId()) + "," + InitString).str(); + GV->addAttribute("spirv-constant-id", ConstIdStr); + } + // Check if we a have a const declaration with an initializer, we may be // able to emit it as available_externally to expose it's value to the // optimizer. diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index 86b871396ec90..89185ef11f256 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -2889,6 +2889,8 @@ static bool mergeDeclAttribute(Sema &S, NamedDecl *D, NewAttr = S.HLSL().mergeWaveSizeAttr(D, *WS, WS->getMin(), WS->getMax(), WS->getPreferred(), WS->getSpelledArgsCount()); + else if (const auto *CI = dyn_cast<HLSLVkConstantIdAttr>(Attr)) + NewAttr = S.HLSL().mergeVkConstantIdAttr(D, *CI, CI->getId()); else if (const auto *SA = dyn_cast<HLSLShaderAttr>(Attr)) NewAttr = S.HLSL().mergeShaderAttr(D, *SA, SA->getType()); else if (isa<SuppressAttr>(Attr)) @@ -13757,6 +13759,14 @@ void Sema::AddInitializerToDecl(Decl *RealDecl, Expr *Init, bool DirectInit) { return; } + if (VDecl->hasAttr<HLSLVkConstantIdAttr>()) { + if (!Init->isCXX11ConstantExpr(Context)) { + Diag(VDecl->getLocation(), diag::err_specialization_const_lit_init); + VDecl->setInvalidDecl(); + return; + } + } + // Get the decls type and save a reference for later, since // CheckInitializerTypes may change it. QualType DclT = VDecl->getType(), SavT = DclT; @@ -14217,6 +14227,14 @@ void Sema::ActOnUninitializedDecl(Decl *RealDecl) { } } + // HLSL variable with the `vk::constant_id` attribute must be initialized. + if (!Var->isInvalidDecl() && Var->hasAttr<HLSLVkConstantIdAttr>()) { + Diag(Var->getLocation(), + diag::err_specialization_const_missing_initializer); + Var->setInvalidDecl(); + return; + } + if (!Var->isInvalidDecl() && RealDecl->hasAttr<LoaderUninitializedAttr>()) { if (Var->getStorageClass() == SC_Extern) { Diag(Var->getLocation(), diag::err_loader_uninitialized_extern_decl) diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index 119ba8486b09f..af617c04dd0d3 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -7505,6 +7505,9 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL, case ParsedAttr::AT_HLSLWaveSize: S.HLSL().handleWaveSizeAttr(D, AL); break; + case ParsedAttr::AT_HLSLVkConstantId: + S.HLSL().handleVkConstantIdAttr(D, AL); + break; case ParsedAttr::AT_HLSLSV_GroupThreadID: S.HLSL().handleSV_GroupThreadIDAttr(D, AL); break; diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp index 43db85594de3d..a39787fb27d5d 100644 --- a/clang/lib/Sema/SemaHLSL.cpp +++ b/clang/lib/Sema/SemaHLSL.cpp @@ -505,6 +505,7 @@ static CXXRecordDecl *createHostLayoutStruct(Sema &S, // - empty structs // - zero-sized arrays // - non-variable declarations +// - SPIR-V specialization constants // The layout struct will be added to the HLSLBufferDecl declarations. void createHostLayoutStructForBuffer(Sema &S, HLSLBufferDecl *BufDecl) { ASTContext &AST = S.getASTContext(); @@ -520,7 +521,8 @@ void createHostLayoutStructForBuffer(Sema &S, HLSLBufferDecl *BufDecl) { for (Decl *D : BufDecl->buffer_decls()) { VarDecl *VD = dyn_cast<VarDecl>(D); if (!VD || VD->getStorageClass() == SC_Static || - VD->getType().getAddressSpace() == LangAS::hlsl_groupshared) + VD->getType().getAddressSpace() == LangAS::hlsl_groupshared || + VD->hasAttr<HLSLVkConstantIdAttr>()) continue; const Type *Ty = VD->getType()->getUnqualifiedDesugaredType(); if (FieldDecl *FD = @@ -607,6 +609,54 @@ HLSLWaveSizeAttr *SemaHLSL::mergeWaveSizeAttr(Decl *D, return Result; } +HLSLVkConstantIdAttr * +SemaHLSL::mergeVkConstantIdAttr(Decl *D, const AttributeCommonInfo &AL, + int Id) { + + auto &TargetInfo = getASTContext().getTargetInfo(); + if (TargetInfo.getTriple().getArch() != llvm::Triple::spirv) { + Diag(AL.getLoc(), diag::warn_attribute_ignored) << AL; + return nullptr; + } + + auto *VD = cast<VarDecl>(D); + + if (!VD->getType()->isIntegerType() && !VD->getType()->isFloatingType()) { + Diag(VD->getLocation(), diag::err_specialization_const_is_not_int_or_float); + return nullptr; + } + + if (VD->getStorageClass() != StorageClass::SC_None && + VD->getStorageClass() != StorageClass::SC_Extern) { + Diag(VD->getLocation(), + diag::err_specialization_const_is_not_externally_visible); + return nullptr; + } + + if (VD->isLocalVarDecl()) { + Diag(VD->getLocation(), + diag::err_specialization_const_is_not_externally_visible); + return nullptr; + } + + if (!VD->getType().isConstQualified()) { + Diag(VD->getLocation(), diag::err_specialization_const_missing_const); + return nullptr; + } + + if (HLSLVkConstantIdAttr *CI = D->getAttr<HLSLVkConstantIdAttr>()) { + if (CI->getId() != Id) { + Diag(CI->getLocation(), diag::err_hlsl_attribute_param_mismatch) << AL; + Diag(AL.getLoc(), diag::note_conflicting_attribute); + } + return nullptr; + } + + HLSLVkConstantIdAttr *Result = + ::new (getASTContext()) HLSLVkConstantIdAttr(getASTContext(), AL, Id); + return Result; +} + HLSLShaderAttr * SemaHLSL::mergeShaderAttr(Decl *D, const AttributeCommonInfo &AL, llvm::Triple::EnvironmentType ShaderType) { @@ -1117,6 +1167,15 @@ void SemaHLSL::handleWaveSizeAttr(Decl *D, const ParsedAttr &AL) { D->addAttr(NewAttr); } +void SemaHLSL::handleVkConstantIdAttr(Decl *D, const ParsedAttr &AL) { + uint32_t Id; + if (!SemaRef.checkUInt32Argument(AL, AL.getArgAsExpr(0), Id)) + return; + HLSLVkConstantIdAttr *NewAttr = mergeVkConstantIdAttr(D, AL, Id); + if (NewAttr) + D->addAttr(NewAttr); +} + bool SemaHLSL::diagnoseInputIDType(QualType T, const ParsedAttr &AL) { const auto *VT = T->getAs<VectorType>(); diff --git a/clang/test/AST/HLSL/vk.spec-constnat.usage.hlsl b/clang/test/AST/HLSL/vk.spec-constnat.usage.hlsl new file mode 100644 index 0000000000000..2383e3ad2a1cd --- /dev/null +++ b/clang/test/AST/HLSL/vk.spec-constnat.usage.hlsl @@ -0,0 +1,12 @@ +// RUN: %clang_cc1 -finclude-default-header -triple spirv-unknown-vulkan-compute -x hlsl -ast-dump -o - %s | FileCheck %s + +// CHECK: VarDecl {{.*}} specConst 'const hlsl_constant int' cinit +// CHECK-NEXT: IntegerLiteral {{.*}} 'int' 12 +// CHECK-NEXT: HLSLVkConstantIdAttr {{.*}} 10 +[[vk::constant_id(10)]] +const int specConst = 12; + +// CHECK: CXXRecordDecl {{.*}} implicit struct __cblayout_$Globals definition +// CHECK-NOT: FieldDecl {{.*}} specConst 'int' + + diff --git a/clang/test/CodeGenHLSL/inline-spirv/SpirvType.alignment.hlsl b/clang/test/CodeGenHLSL/vk-features/SpirvType.alignment.hlsl similarity index 100% rename from clang/test/CodeGenHLSL/inline-spirv/SpirvType.alignment.hlsl rename to clang/test/CodeGenHLSL/vk-features/SpirvType.alignment.hlsl diff --git a/clang/test/CodeGenHLSL/inline-spirv/SpirvType.hlsl b/clang/test/CodeGenHLSL/vk-features/SpirvType.hlsl similarity index 100% rename from clang/test/CodeGenHLSL/inline-spirv/SpirvType.hlsl rename to clang/test/CodeGenHLSL/vk-features/SpirvType.hlsl diff --git a/clang/test/CodeGenHLSL/vk-features/vk.spec-constant.hlsl b/clang/test/CodeGenHLSL/vk-features/vk.spec-constant.hlsl new file mode 100644 index 0000000000000..5f2b0767bcf9b --- /dev/null +++ b/clang/test/CodeGenHLSL/vk-features/vk.spec-constant.hlsl @@ -0,0 +1,44 @@ +// RUN: %clang_cc1 -finclude-default-header -x hlsl -triple \ +// RUN: spirv-unknown-vulkan-compute %s -emit-llvm -disable-llvm-passes \ +// RUN: -o - | FileCheck %s + + +// CHECK-DAG: @_ZL3sc0 = external addrspace(12) constant i32, align 4 [[A0:#[0-9]+]] +// CHECK-DAG: attributes [[A0]] = { "spirv-constant-id"="0,1" } +[[vk::constant_id(0)]] +const bool sc0 = true; + +// CHECK-DAG: @_ZL3sc1 = external addrspace(12) constant i32, align 4 [[A1:#[0-9]+]] +// CHECK-DAG: attributes [[A1]] = { "spirv-constant-id"="1,10" } +[[vk::constant_id(1)]] +const int sc1 = 10; + +// CHECK-DAG: @_ZL3sc2 = external addrspace(12) constant i32, align 4 [[A2:#[0-9]+]] +// CHECK-DAG: attributes [[A2]] = { "spirv-constant-id"="2,-20" } +[[vk::constant_id(2)]] +const int sc2 = 10-30; + +// CHECK-DAG: @_ZL3sc3 = external addrspace(12) constant float, align 4 [[A3:#[0-9]+]] +// CHECK-DAG: attributes [[A3]] = { "spirv-constant-id"="3,0.25" } +[[vk::constant_id(3)]] +const float sc3 = 0.5*0.5; + +// CHECK-DAG: @_ZL3sc4 = external addrspace(12) constant i32, align 4 [[A4:#[0-9]+]] +// CHECK-DAG: attributes [[A4]] = { "spirv-constant-id"="4,2" } +enum E { + A, + B, + C +}; + +[[vk::constant_id(4)]] +const E sc4 = E::C; + +[numthreads(1,1,1)] +void main() { + bool b = sc0; + int i = sc1; + int j = sc2; + float f = sc3; + E e = sc4; +} diff --git a/clang/test/SemaHLSL/vk.spec-constant.error1.hlsl b/clang/test/SemaHLSL/vk.spec-constant.error1.hlsl new file mode 100644 index 0000000000000..2b6ac85ebc3ca --- /dev/null +++ b/clang/test/SemaHLSL/vk.spec-constant.error1.hlsl @@ -0,0 +1,39 @@ +// RUN: %clang_cc1 -finclude-default-header -triple spirv-pc-vulkan1.3-compute -verify %s +// RUN: %clang_cc1 -finclude-default-header -triple dxil-pc-shadermodel6.8-compute -verify %s + +#ifndef __spirv__ +// expected-warning@+2{{'constant_id' attribute ignored}} +#endif +[[vk::constant_id(0)]] +const bool sc0 = true; + +#ifdef __spirv__ +[[vk::constant_id(1)]] +// expected-error@+1{{variable with 'vk::constant_id' attribute cannot have an initializer that is not a constexpr}} +const bool sc1 = sc0; // error + +[[vk::constant_id(2)]] +// expected-error@+1{{variable with 'vk::constant_id' attribute must be externally visible}} +static const bool sc2 = false; // error + +[[vk::constant_id(3)]] +// expected-error@+1{{variable with 'vk::constant_id' attribute must have an initializer}} +const bool sc3; // error + +[[vk::constant_id(4)]] +// expected-error@+1{{variable with 'vk::constant_id' attribute must be const}} +bool sc4 = false; // error + +[[vk::constant_id(5)]] +// expected-error@+1{{variable with 'vk::constant_id' attribute must be an enum, bool, integer, or floating point value}} +const int2 sc5 = {0,0}; // error +#endif + +[numthreads(1,1,1)] +void main() { + #ifdef __spirv__ + [[vk::constant_id(6)]] + // expected-error@+1{{variable with 'vk::constant_id' attribute must be externally visible}} + const bool sc6 = false; // error + #endif +} _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits