[clang] [llvm] [HLSL][SPIRV] Add vk::constant_id attribute. (PR #143180)
Steven Perron via llvm-commits
llvm-commits at lists.llvm.org
Mon Jun 9 06:42:40 PDT 2025
https://github.com/s-perron updated https://github.com/llvm/llvm-project/pull/143180
>From de57697e0f9b0f9d4bb5be7ca6be631fe2502484 Mon Sep 17 00:00:00 2001
From: Steven Perron <stevenperron at 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).
The strategy is to to modify the initializer to get the value of a
specialize constant, and make the variable itself static.
---
clang/include/clang/Basic/Attr.td | 8 +
clang/include/clang/Basic/AttrDocs.td | 15 ++
clang/include/clang/Basic/Builtins.td | 13 ++
.../clang/Basic/DiagnosticSemaKinds.td | 12 +
clang/include/clang/Sema/SemaHLSL.h | 5 +-
clang/lib/Basic/Attributes.cpp | 3 +-
clang/lib/CodeGen/CGHLSLBuiltins.cpp | 17 ++
clang/lib/Sema/SemaDecl.cpp | 14 ++
clang/lib/Sema/SemaDeclAttr.cpp | 3 +
clang/lib/Sema/SemaHLSL.cpp | 120 +++++++++-
.../test/AST/HLSL/vk.spec-constant.usage.hlsl | 130 +++++++++++
.../SpirvType.alignment.hlsl | 0
.../SpirvType.hlsl | 0
.../vk-features/vk.spec-constant.hlsl | 210 ++++++++++++++++++
.../test/SemaHLSL/vk.spec-constant.error.hlsl | 37 +++
llvm/include/llvm/IR/IntrinsicsSPIRV.td | 4 +
16 files changed, 588 insertions(+), 3 deletions(-)
create mode 100644 clang/test/AST/HLSL/vk.spec-constant.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.error.hlsl
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index f889e41c8699f..d3f39de6a3e85 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -4993,6 +4993,14 @@ def HLSLVkExtBuiltinInput : InheritableAttr {
let Documentation = [HLSLVkExtBuiltinInputDocs];
}
+def HLSLVkConstantId : InheritableAttr {
+ let Spellings = [CXX11<"vk", "constant_id">];
+ let Args = [IntArgument<"Id">];
+ let Subjects = SubjectList<[ExternalGlobalVar]>;
+ 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 a16218f038518..d5a617c4769fd 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -8247,6 +8247,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/Builtins.td b/clang/include/clang/Basic/Builtins.td
index b15cde05410ab..4db6ca3b36ff9 100644
--- a/clang/include/clang/Basic/Builtins.td
+++ b/clang/include/clang/Basic/Builtins.td
@@ -5059,6 +5059,19 @@ def HLSLGroupMemoryBarrierWithGroupSync: LangBuiltin<"HLSL_LANG"> {
let Prototype = "void()";
}
+class HLSLScalarTemplate
+ : Template<["bool", "char", "short", "int", "long long int",
+ "unsigned short", "unsigned int", "unsigned long long int",
+ "__fp16", "float", "double"],
+ ["_bool", "_char", "_short", "_int", "_longlong", "_ushort",
+ "_uint", "_ulonglong", "_half", "_float", "_double"]>;
+
+def HLSLGetSpirvSpecConstant : LangBuiltin<"HLSL_LANG">, HLSLScalarTemplate {
+ let Spellings = ["__builtin_get_spirv_spec_constant"];
+ let Attributes = [NoThrow, Const, Pure];
+ let Prototype = "T(unsigned int, T)";
+}
+
// Builtins for XRay.
def XRayCustomEvent : Builtin {
let Spellings = ["__xray_customevent"];
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 8ba3bb099d741..982ae39b48f47 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -12904,6 +12904,18 @@ 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_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 66d09f49680be..099d9c35684e8 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);
@@ -156,7 +159,7 @@ class SemaHLSL : public SemaBase {
QualType getInoutParameterType(QualType Ty);
bool transformInitList(const InitializedEntity &Entity, InitListExpr *Init);
-
+ bool handleInitialization(VarDecl *VDecl, Expr *&Init);
void deduceAddressSpace(VarDecl *Decl);
private:
diff --git a/clang/lib/Basic/Attributes.cpp b/clang/lib/Basic/Attributes.cpp
index 905046685934b..5f74365f0ed00 100644
--- a/clang/lib/Basic/Attributes.cpp
+++ b/clang/lib/Basic/Attributes.cpp
@@ -213,7 +213,8 @@ getScopeFromNormalizedScopeName(StringRef ScopeName) {
.Case("vk", AttributeCommonInfo::Scope::VK)
.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/CGHLSLBuiltins.cpp b/clang/lib/CodeGen/CGHLSLBuiltins.cpp
index 10dd9fd04eb9e..3f199e1a3895f 100644
--- a/clang/lib/CodeGen/CGHLSLBuiltins.cpp
+++ b/clang/lib/CodeGen/CGHLSLBuiltins.cpp
@@ -769,6 +769,23 @@ Value *CodeGenFunction::EmitHLSLBuiltinExpr(unsigned BuiltinID,
return EmitRuntimeCall(
Intrinsic::getOrInsertDeclaration(&CGM.getModule(), ID));
}
+ case Builtin::BI__builtin_get_spirv_spec_constant_bool:
+ case Builtin::BI__builtin_get_spirv_spec_constant_short:
+ case Builtin::BI__builtin_get_spirv_spec_constant_ushort:
+ case Builtin::BI__builtin_get_spirv_spec_constant_int:
+ case Builtin::BI__builtin_get_spirv_spec_constant_uint:
+ case Builtin::BI__builtin_get_spirv_spec_constant_longlong:
+ case Builtin::BI__builtin_get_spirv_spec_constant_ulonglong:
+ case Builtin::BI__builtin_get_spirv_spec_constant_half:
+ case Builtin::BI__builtin_get_spirv_spec_constant_float:
+ case Builtin::BI__builtin_get_spirv_spec_constant_double: {
+ assert(CGM.getTarget().getTriple().isSPIRV() && "SPIR-V only");
+ Intrinsic::ID ID = Intrinsic::spv_get_specialization_constant;
+ llvm::Type *T = CGM.getTypes().ConvertType(E->getType());
+ auto F = Intrinsic::getOrInsertDeclaration(&CGM.getModule(), ID, {T});
+ return EmitRuntimeCall(
+ F, {EmitScalarExpr(E->getArg(0)), EmitScalarExpr(E->getArg(1))});
+ }
}
return nullptr;
}
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 60e911b9fecc0..c6ef680e77fa1 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))
@@ -13755,6 +13757,10 @@ void Sema::AddInitializerToDecl(Decl *RealDecl, Expr *Init, bool DirectInit) {
return;
}
+ if (getLangOpts().HLSL)
+ if (!HLSL().handleInitialization(VDecl, Init))
+ return;
+
// Get the decls type and save a reference for later, since
// CheckInitializerTypes may change it.
QualType DclT = VDecl->getType(), SavT = DclT;
@@ -14215,6 +14221,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 da0e3265767d8..e49bdda1a402b 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -7560,6 +7560,9 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
case ParsedAttr::AT_HLSLVkExtBuiltinInput:
S.HLSL().handleVkExtBuiltinInputAttr(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 9065cc5a1d4a5..5764507f35882 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -119,6 +119,40 @@ static ResourceClass getResourceClass(RegisterType RT) {
llvm_unreachable("unexpected RegisterType value");
}
+static Builtin::ID getSpecConstBuiltinId(QualType Type) {
+ const auto *BT = dyn_cast<BuiltinType>(Type);
+ if (!BT) {
+ if (!Type->isEnumeralType())
+ return Builtin::NotBuiltin;
+ return Builtin::BI__builtin_get_spirv_spec_constant_int;
+ }
+
+ switch (BT->getKind()) {
+ case BuiltinType::Bool:
+ return Builtin::BI__builtin_get_spirv_spec_constant_bool;
+ case BuiltinType::Short:
+ return Builtin::BI__builtin_get_spirv_spec_constant_short;
+ case BuiltinType::Int:
+ return Builtin::BI__builtin_get_spirv_spec_constant_int;
+ case BuiltinType::LongLong:
+ return Builtin::BI__builtin_get_spirv_spec_constant_longlong;
+ case BuiltinType::UShort:
+ return Builtin::BI__builtin_get_spirv_spec_constant_ushort;
+ case BuiltinType::UInt:
+ return Builtin::BI__builtin_get_spirv_spec_constant_uint;
+ case BuiltinType::ULongLong:
+ return Builtin::BI__builtin_get_spirv_spec_constant_ulonglong;
+ case BuiltinType::Half:
+ return Builtin::BI__builtin_get_spirv_spec_constant_half;
+ case BuiltinType::Float:
+ return Builtin::BI__builtin_get_spirv_spec_constant_float;
+ case BuiltinType::Double:
+ return Builtin::BI__builtin_get_spirv_spec_constant_double;
+ default:
+ return Builtin::NotBuiltin;
+ }
+}
+
DeclBindingInfo *ResourceBindings::addDeclBindingInfo(const VarDecl *VD,
ResourceClass ResClass) {
assert(getDeclBindingInfo(VD, ResClass) == nullptr &&
@@ -607,6 +641,41 @@ 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 (getSpecConstBuiltinId(VD->getType()) == Builtin::NotBuiltin) {
+ Diag(VD->getLocation(), diag::err_specialization_const_is_not_int_or_float);
+ 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) {
@@ -1125,6 +1194,15 @@ void SemaHLSL::handleVkExtBuiltinInputAttr(Decl *D, const ParsedAttr &AL) {
HLSLVkExtBuiltinInputAttr(getASTContext(), AL, ID));
}
+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>();
@@ -3154,6 +3232,7 @@ static bool IsDefaultBufferConstantDecl(VarDecl *VD) {
return VD->getDeclContext()->isTranslationUnit() &&
QT.getAddressSpace() == LangAS::Default &&
VD->getStorageClass() != SC_Static &&
+ !VD->hasAttr<HLSLVkConstantIdAttr>() &&
!isInvalidConstantBufferLeafElementType(QT.getTypePtr());
}
@@ -3221,7 +3300,8 @@ void SemaHLSL::ActOnVariableDeclarator(VarDecl *VD) {
const Type *VarType = VD->getType().getTypePtr();
while (VarType->isArrayType())
VarType = VarType->getArrayElementTypeNoTypeQual();
- if (VarType->isHLSLResourceRecord()) {
+ if (VarType->isHLSLResourceRecord() ||
+ VD->hasAttr<HLSLVkConstantIdAttr>()) {
// Make the variable for resources static. The global externally visible
// storage is accessed through the handle, which is a member. The variable
// itself is not externally visible.
@@ -3644,3 +3724,41 @@ bool SemaHLSL::transformInitList(const InitializedEntity &Entity,
Init->updateInit(Ctx, I, NewInit->getInit(I));
return true;
}
+
+bool SemaHLSL::handleInitialization(VarDecl *VDecl, Expr *&Init) {
+ const HLSLVkConstantIdAttr *ConstIdAttr =
+ VDecl->getAttr<HLSLVkConstantIdAttr>();
+ if (!ConstIdAttr)
+ return true;
+
+ ASTContext &Context = SemaRef.getASTContext();
+
+ APValue InitValue;
+ if (!Init->isCXX11ConstantExpr(Context, &InitValue)) {
+ Diag(VDecl->getLocation(), diag::err_specialization_const_lit_init);
+ VDecl->setInvalidDecl();
+ return false;
+ }
+
+ Builtin::ID BID = getSpecConstBuiltinId(VDecl->getType());
+
+ // Argument 1: The ID from the attribute
+ int ConstantID = ConstIdAttr->getId();
+ llvm::APInt IDVal(Context.getIntWidth(Context.IntTy), ConstantID);
+ Expr *IdExpr = IntegerLiteral::Create(Context, IDVal, Context.IntTy,
+ ConstIdAttr->getLocation());
+
+ SmallVector<Expr *, 2> Args = {IdExpr, Init};
+ Expr *C = SemaRef.BuildBuiltinCallExpr(Init->getExprLoc(), BID, Args);
+ if (C->getType()->getCanonicalTypeUnqualified() !=
+ VDecl->getType()->getCanonicalTypeUnqualified()) {
+ C = SemaRef
+ .BuildCStyleCastExpr(SourceLocation(),
+ Context.getTrivialTypeSourceInfo(
+ Init->getType(), Init->getExprLoc()),
+ SourceLocation(), C)
+ .get();
+ }
+ Init = C;
+ return true;
+}
diff --git a/clang/test/AST/HLSL/vk.spec-constant.usage.hlsl b/clang/test/AST/HLSL/vk.spec-constant.usage.hlsl
new file mode 100644
index 0000000000000..c0955c1ea7b43
--- /dev/null
+++ b/clang/test/AST/HLSL/vk.spec-constant.usage.hlsl
@@ -0,0 +1,130 @@
+// RUN: %clang_cc1 -finclude-default-header -triple spirv-unknown-vulkan-compute -x hlsl -ast-dump -o - %s | FileCheck %s
+
+// CHECK: VarDecl {{.*}} bool_const 'const hlsl_private bool' static cinit
+// CHECK-NEXT: CallExpr {{.*}} 'bool'
+// CHECK-NEXT: ImplicitCastExpr {{.*}} 'bool (*)(unsigned int, bool) noexcept' <FunctionToPointerDecay>
+// CHECK-NEXT: DeclRefExpr {{.*}} 'bool (unsigned int, bool) noexcept' lvalue Function {{.*}} '__builtin_get_spirv_spec_constant_bool' 'bool (unsigned int, bool) noexcept'
+// CHECK-NEXT: ImplicitCastExpr {{.*}} 'unsigned int' <IntegralCast>
+// CHECK-NEXT: IntegerLiteral {{.*}} 'int' 1
+// CHECK-NEXT: CXXBoolLiteralExpr {{.*}} 'bool' true
+[[vk::constant_id(1)]]
+const bool bool_const = true;
+
+// CHECK: VarDecl {{.*}} short_const 'const hlsl_private short' static cinit
+// CHECK-NEXT: CallExpr {{.*}} 'short'
+// CHECK-NEXT: ImplicitCastExpr {{.*}} 'short (*)(unsigned int, short) noexcept' <FunctionToPointerDecay>
+// CHECK-NEXT: DeclRefExpr {{.*}} 'short (unsigned int, short) noexcept' lvalue Function {{.*}} '__builtin_get_spirv_spec_constant_short' 'short (unsigned int, short) noexcept'
+// CHECK-NEXT: ImplicitCastExpr {{.*}} 'unsigned int' <IntegralCast>
+// CHECK-NEXT: IntegerLiteral {{.*}} 'int' 2
+// CHECK-NEXT: ImplicitCastExpr {{.*}} 'short' <IntegralCast>
+// CHECK-NEXT: IntegerLiteral {{.*}} 'int' 4
+[[vk::constant_id(2)]]
+const short short_const = 4;
+
+// CHECK: VarDecl {{.*}} int_const 'const hlsl_private int' static cinit
+// CHECK-NEXT: CallExpr {{.*}} 'int'
+// CHECK-NEXT: ImplicitCastExpr {{.*}} 'int (*)(unsigned int, int) noexcept' <FunctionToPointerDecay>
+// CHECK-NEXT: DeclRefExpr {{.*}} 'int (unsigned int, int) noexcept' lvalue Function {{.*}} '__builtin_get_spirv_spec_constant_int' 'int (unsigned int, int) noexcept'
+// CHECK-NEXT: ImplicitCastExpr {{.*}} 'unsigned int' <IntegralCast>
+// CHECK-NEXT: IntegerLiteral {{.*}} 'int' 3
+// CHECK-NEXT: IntegerLiteral {{.*}} 'int' 5
+[[vk::constant_id(3)]]
+const int int_const = 5;
+
+// CHECK: VarDecl {{.*}} long_const 'const hlsl_private long long' static cinit
+// CHECK-NEXT: CallExpr {{.*}} 'long long'
+// CHECK-NEXT: ImplicitCastExpr {{.*}} 'long long (*)(unsigned int, long long) noexcept' <FunctionToPointerDecay>
+// CHECK-NEXT: DeclRefExpr {{.*}} 'long long (unsigned int, long long) noexcept' lvalue Function {{.*}} '__builtin_get_spirv_spec_constant_longlong' 'long long (unsigned int, long long) noexcept'
+// CHECK-NEXT: ImplicitCastExpr {{.*}} 'unsigned int' <IntegralCast>
+// CHECK-NEXT: IntegerLiteral {{.*}} 'int' 4
+// CHECK-NEXT: ImplicitCastExpr {{.*}} 'long long' <IntegralCast>
+// CHECK-NEXT: IntegerLiteral {{.*}} 'int' 8
+[[vk::constant_id(4)]]
+const long long long_const = 8;
+
+// CHECK: VarDecl {{.*}} ushort_const 'const hlsl_private unsigned short' static cinit
+// CHECK-NEXT: CallExpr {{.*}} 'unsigned short'
+// CHECK-NEXT: ImplicitCastExpr {{.*}} 'unsigned short (*)(unsigned int, unsigned short) noexcept' <FunctionToPointerDecay>
+// CHECK-NEXT: DeclRefExpr {{.*}} 'unsigned short (unsigned int, unsigned short) noexcept' lvalue Function {{.*}} '__builtin_get_spirv_spec_constant_ushort' 'unsigned short (unsigned int, unsigned short) noexcept'
+// CHECK-NEXT: ImplicitCastExpr {{.*}} 'unsigned int' <IntegralCast>
+// CHECK-NEXT: IntegerLiteral {{.*}} 'int' 5
+// CHECK-NEXT: ImplicitCastExpr {{.*}} 'unsigned short' <IntegralCast>
+// CHECK-NEXT: IntegerLiteral {{.*}} 'int' 10
+[[vk::constant_id(5)]]
+const unsigned short ushort_const = 10;
+
+// CHECK: VarDecl {{.*}} uint_const 'const hlsl_private unsigned int' static cinit
+// CHECK-NEXT: CallExpr {{.*}} 'unsigned int'
+// CHECK-NEXT: ImplicitCastExpr {{.*}} 'unsigned int (*)(unsigned int, unsigned int) noexcept' <FunctionToPointerDecay>
+// CHECK-NEXT: DeclRefExpr {{.*}} 'unsigned int (unsigned int, unsigned int) noexcept' lvalue Function {{.*}} '__builtin_get_spirv_spec_constant_uint' 'unsigned int (unsigned int, unsigned int) noexcept'
+// CHECK-NEXT: ImplicitCastExpr {{.*}} 'unsigned int' <IntegralCast>
+// CHECK-NEXT: IntegerLiteral {{.*}} 'int' 6
+// CHECK-NEXT: ImplicitCastExpr {{.*}} 'unsigned int' <IntegralCast>
+// CHECK-NEXT: IntegerLiteral {{.*}} 'int' 12
+[[vk::constant_id(6)]]
+const unsigned int uint_const = 12;
+
+
+// CHECK: VarDecl {{.*}} ulong_const 'const hlsl_private unsigned long long' static cinit
+// CHECK-NEXT: CallExpr {{.*}} 'unsigned long long'
+// CHECK-NEXT: ImplicitCastExpr {{.*}} 'unsigned long long (*)(unsigned int, unsigned long long) noexcept' <FunctionToPointerDecay>
+// CHECK-NEXT: DeclRefExpr {{.*}} 'unsigned long long (unsigned int, unsigned long long) noexcept' lvalue Function {{.*}} '__builtin_get_spirv_spec_constant_ulonglong' 'unsigned long long (unsigned int, unsigned long long) noexcept'
+// CHECK-NEXT: ImplicitCastExpr {{.*}} 'unsigned int' <IntegralCast>
+// CHECK-NEXT: IntegerLiteral {{.*}} 'int' 7
+// CHECK-NEXT: ImplicitCastExpr {{.*}} 'unsigned long long' <IntegralCast>
+// CHECK-NEXT: IntegerLiteral {{.*}} 'int' 25
+[[vk::constant_id(7)]]
+const unsigned long long ulong_const = 25;
+
+// CHECK: VarDecl {{.*}} half_const 'const hlsl_private half' static cinit
+// CHECK-NEXT: CallExpr {{.*}} 'half'
+// CHECK-NEXT: ImplicitCastExpr {{.*}} 'half (*)(unsigned int, half) noexcept' <FunctionToPointerDecay>
+// CHECK-NEXT: DeclRefExpr {{.*}} 'half (unsigned int, half) noexcept' lvalue Function {{.*}} '__builtin_get_spirv_spec_constant_half' 'half (unsigned int, half) noexcept'
+// CHECK-NEXT: ImplicitCastExpr {{.*}} 'unsigned int' <IntegralCast>
+// CHECK-NEXT: IntegerLiteral {{.*}} 'int' 8
+// CHECK-NEXT: ImplicitCastExpr {{.*}} 'half' <FloatingCast>
+// CHECK-NEXT: FloatingLiteral {{.*}} 'float' 4.040000e+01
+[[vk::constant_id(8)]]
+const half half_const = 40.4;
+
+// CHECK: VarDecl {{.*}} float_const 'const hlsl_private float' static cinit
+// CHECK-NEXT: CallExpr {{.*}} 'float'
+// CHECK-NEXT: ImplicitCastExpr {{.*}} 'float (*)(unsigned int, float) noexcept' <FunctionToPointerDecay>
+// CHECK-NEXT: DeclRefExpr {{.*}} 'float (unsigned int, float) noexcept' lvalue Function {{.*}} '__builtin_get_spirv_spec_constant_float' 'float (unsigned int, float) noexcept'
+// CHECK-NEXT: ImplicitCastExpr {{.*}} 'unsigned int' <IntegralCast>
+// CHECK-NEXT: IntegerLiteral {{.*}} 'int' 8
+// CHECK-NEXT: ImplicitCastExpr {{.*}} 'float' <IntegralToFloating>
+// CHECK-NEXT: IntegerLiteral {{.*}} 'int' 50
+[[vk::constant_id(8)]]
+const float float_const = 50;
+
+// CHECK: VarDecl {{.*}} double_const 'const hlsl_private double' static cinit
+// CHECK-NEXT: CallExpr {{.*}} 'double'
+// CHECK-NEXT: ImplicitCastExpr {{.*}} 'double (*)(unsigned int, double) noexcept' <FunctionToPointerDecay>
+// CHECK-NEXT: DeclRefExpr {{.*}} 'double (unsigned int, double) noexcept' lvalue Function {{.*}} '__builtin_get_spirv_spec_constant_double' 'double (unsigned int, double) noexcept'
+// CHECK-NEXT: ImplicitCastExpr {{.*}} 'unsigned int' <IntegralCast>
+// CHECK-NEXT: IntegerLiteral {{.*}} 'int' 9
+// CHECK-NEXT: ImplicitCastExpr {{.*}} 'double' <IntegralToFloating>
+// CHECK-NEXT: IntegerLiteral {{.*}} 'int' 100
+[[vk::constant_id(9)]]
+const double double_const = 100;
+
+// CHECK: VarDecl {{.*}} enum_const 'const hlsl_private E' static cinit
+// CHECK-NEXT: CStyleCastExpr {{.*}} 'E' <IntegralCast>
+// CHECK-NEXT: CallExpr {{.*}} 'int'
+// CHECK-NEXT: ImplicitCastExpr {{.*}} 'int (*)(unsigned int, int) noexcept' <FunctionToPointerDecay>
+// CHECK-NEXT: DeclRefExpr {{.*}} 'int (unsigned int, int) noexcept' lvalue Function {{.*}} '__builtin_get_spirv_spec_constant_int' 'int (unsigned int, int) noexcept'
+// CHECK-NEXT: ImplicitCastExpr {{.*}} 'unsigned int' <IntegralCast>
+// CHECK-NEXT: IntegerLiteral {{.*}} 'int' 10
+// CHECK-NEXT: ImplicitCastExpr {{.*}} 'int' <IntegralCast>
+// CHECK-NEXT: DeclRefExpr {{.*}} 'E' EnumConstant {{.*}} 'e2' 'E'
+enum E {
+ e0 = 10,
+ e1 = 20,
+ e2 = 30
+};
+
+[[vk::constant_id(10)]]
+const E enum_const = e2;
+
+// CHECK-NOT: CXXRecordDecl {{.*}} implicit struct __cblayout_$Globals definition
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..95a3968083f62
--- /dev/null
+++ b/clang/test/CodeGenHLSL/vk-features/vk.spec-constant.hlsl
@@ -0,0 +1,210 @@
+// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --check-globals all --include-generated-funcs --version 5
+// RUN: %clang_cc1 -finclude-default-header -x hlsl -triple \
+// RUN: spirv-unknown-vulkan-compute %s -emit-llvm -disable-llvm-passes \
+// RUN: -o - | FileCheck %s
+
+[[vk::constant_id(1)]]
+const bool bool_const = true;
+
+[[vk::constant_id(2)]]
+const short short_const = 4;
+
+[[vk::constant_id(3)]]
+const int int_const = 5;
+
+[[vk::constant_id(4)]]
+const long long long_const = 8;
+
+[[vk::constant_id(5)]]
+const unsigned short ushort_const = 10;
+
+[[vk::constant_id(6)]]
+const unsigned int uint_const = 12;
+
+[[vk::constant_id(7)]]
+const unsigned long long ulong_const = 25;
+
+[[vk::constant_id(8)]]
+const half half_const = 40.4;
+
+[[vk::constant_id(8)]]
+const float float_const = 50.5;
+
+[[vk::constant_id(9)]]
+const double double_const = 100.2;
+
+enum E {
+ e0 = 10,
+ e1 = 20,
+ e2 = 30
+};
+
+[[vk::constant_id(10)]]
+const E enum_const = e2;
+
+[numthreads(1,1,1)]
+void main() {
+ bool b = bool_const;
+ short s = short_const;
+ int i = int_const;
+ long long l = long_const;
+ unsigned short us = ushort_const;
+ unsigned int ui = uint_const;
+ unsigned long long ul = ulong_const;
+ half h = half_const;
+ float f = float_const;
+ double d = double_const;
+ E e = enum_const;
+}
+//.
+// CHECK: @_ZL10bool_const = internal addrspace(10) global i32 0, align 4
+// CHECK: @_ZL11short_const = internal addrspace(10) global i16 0, align 2
+// CHECK: @_ZL9int_const = internal addrspace(10) global i32 0, align 4
+// CHECK: @_ZL10long_const = internal addrspace(10) global i64 0, align 8
+// CHECK: @_ZL12ushort_const = internal addrspace(10) global i16 0, align 2
+// CHECK: @_ZL10uint_const = internal addrspace(10) global i32 0, align 4
+// CHECK: @_ZL11ulong_const = internal addrspace(10) global i64 0, align 8
+// CHECK: @_ZL10half_const = internal addrspace(10) global float 0.000000e+00, align 4
+// CHECK: @_ZL11float_const = internal addrspace(10) global float 0.000000e+00, align 4
+// CHECK: @_ZL12double_const = internal addrspace(10) global double 0.000000e+00, align 8
+// CHECK: @_ZL10enum_const = internal addrspace(10) global i32 0, align 4
+//.
+// CHECK-LABEL: define internal spir_func void @_Z4mainv(
+// CHECK-SAME: ) #[[ATTR0:[0-9]+]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[TMP0:%.*]] = call token @llvm.experimental.convergence.entry()
+// CHECK-NEXT: [[B:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[S:%.*]] = alloca i16, align 2
+// CHECK-NEXT: [[I:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[L:%.*]] = alloca i64, align 8
+// CHECK-NEXT: [[US:%.*]] = alloca i16, align 2
+// CHECK-NEXT: [[UI:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[UL:%.*]] = alloca i64, align 8
+// CHECK-NEXT: [[H:%.*]] = alloca float, align 4
+// CHECK-NEXT: [[F:%.*]] = alloca float, align 4
+// CHECK-NEXT: [[D:%.*]] = alloca double, align 8
+// CHECK-NEXT: [[E:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[TMP1:%.*]] = load i32, ptr addrspace(10) @_ZL10bool_const, align 4
+// CHECK-NEXT: [[LOADEDV:%.*]] = trunc i32 [[TMP1]] to i1
+// CHECK-NEXT: [[STOREDV:%.*]] = zext i1 [[LOADEDV]] to i32
+// CHECK-NEXT: store i32 [[STOREDV]], ptr [[B]], align 4
+// CHECK-NEXT: [[TMP2:%.*]] = load i16, ptr addrspace(10) @_ZL11short_const, align 2
+// CHECK-NEXT: store i16 [[TMP2]], ptr [[S]], align 2
+// CHECK-NEXT: [[TMP3:%.*]] = load i32, ptr addrspace(10) @_ZL9int_const, align 4
+// CHECK-NEXT: store i32 [[TMP3]], ptr [[I]], align 4
+// CHECK-NEXT: [[TMP4:%.*]] = load i64, ptr addrspace(10) @_ZL10long_const, align 8
+// CHECK-NEXT: store i64 [[TMP4]], ptr [[L]], align 8
+// CHECK-NEXT: [[TMP5:%.*]] = load i16, ptr addrspace(10) @_ZL12ushort_const, align 2
+// CHECK-NEXT: store i16 [[TMP5]], ptr [[US]], align 2
+// CHECK-NEXT: [[TMP6:%.*]] = load i32, ptr addrspace(10) @_ZL10uint_const, align 4
+// CHECK-NEXT: store i32 [[TMP6]], ptr [[UI]], align 4
+// CHECK-NEXT: [[TMP7:%.*]] = load i64, ptr addrspace(10) @_ZL11ulong_const, align 8
+// CHECK-NEXT: store i64 [[TMP7]], ptr [[UL]], align 8
+// CHECK-NEXT: [[TMP8:%.*]] = load float, ptr addrspace(10) @_ZL10half_const, align 4
+// CHECK-NEXT: store float [[TMP8]], ptr [[H]], align 4
+// CHECK-NEXT: [[TMP9:%.*]] = load float, ptr addrspace(10) @_ZL11float_const, align 4
+// CHECK-NEXT: store float [[TMP9]], ptr [[F]], align 4
+// CHECK-NEXT: [[TMP10:%.*]] = load double, ptr addrspace(10) @_ZL12double_const, align 8
+// CHECK-NEXT: store double [[TMP10]], ptr [[D]], align 8
+// CHECK-NEXT: [[TMP11:%.*]] = load i32, ptr addrspace(10) @_ZL10enum_const, align 4
+// CHECK-NEXT: store i32 [[TMP11]], ptr [[E]], align 4
+// CHECK-NEXT: ret void
+//
+// CHECK-LABEL: define internal spir_func void @__cxx_global_var_init(
+// CHECK-SAME: ) #[[ATTR3:[0-9]+]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[TMP0:%.*]] = call token @llvm.experimental.convergence.entry()
+// CHECK-NEXT: [[TMP1:%.*]] = call spir_func i1 @llvm.spv.get.specialization.constant.i1(i32 1, i1 true)
+// CHECK-NEXT: [[STOREDV:%.*]] = zext i1 [[TMP1]] to i32
+// CHECK-NEXT: store i32 [[STOREDV]], ptr addrspace(10) @_ZL10bool_const, align 4
+// CHECK-NEXT: ret void
+//
+//
+// CHECK-LABEL: define internal spir_func void @__cxx_global_var_init.1(
+// CHECK-SAME: ) #[[ATTR3]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[TMP0:%.*]] = call token @llvm.experimental.convergence.entry()
+// CHECK-NEXT: [[TMP1:%.*]] = call spir_func i16 @llvm.spv.get.specialization.constant.i16(i32 2, i16 4)
+// CHECK-NEXT: store i16 [[TMP1]], ptr addrspace(10) @_ZL11short_const, align 2
+// CHECK-NEXT: ret void
+//
+//
+// CHECK-LABEL: define internal spir_func void @__cxx_global_var_init.2(
+// CHECK-SAME: ) #[[ATTR3]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[TMP0:%.*]] = call token @llvm.experimental.convergence.entry()
+// CHECK-NEXT: [[TMP1:%.*]] = call spir_func i32 @llvm.spv.get.specialization.constant.i32(i32 3, i32 5)
+// CHECK-NEXT: store i32 [[TMP1]], ptr addrspace(10) @_ZL9int_const, align 4
+// CHECK-NEXT: ret void
+//
+//
+// CHECK-LABEL: define internal spir_func void @__cxx_global_var_init.3(
+// CHECK-SAME: ) #[[ATTR3]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[TMP0:%.*]] = call token @llvm.experimental.convergence.entry()
+// CHECK-NEXT: [[TMP1:%.*]] = call spir_func i64 @llvm.spv.get.specialization.constant.i64(i32 4, i64 8)
+// CHECK-NEXT: store i64 [[TMP1]], ptr addrspace(10) @_ZL10long_const, align 8
+// CHECK-NEXT: ret void
+//
+//
+// CHECK-LABEL: define internal spir_func void @__cxx_global_var_init.4(
+// CHECK-SAME: ) #[[ATTR3]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[TMP0:%.*]] = call token @llvm.experimental.convergence.entry()
+// CHECK-NEXT: [[TMP1:%.*]] = call spir_func i16 @llvm.spv.get.specialization.constant.i16(i32 5, i16 10)
+// CHECK-NEXT: store i16 [[TMP1]], ptr addrspace(10) @_ZL12ushort_const, align 2
+// CHECK-NEXT: ret void
+//
+//
+// CHECK-LABEL: define internal spir_func void @__cxx_global_var_init.5(
+// CHECK-SAME: ) #[[ATTR3]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[TMP0:%.*]] = call token @llvm.experimental.convergence.entry()
+// CHECK-NEXT: [[TMP1:%.*]] = call spir_func i32 @llvm.spv.get.specialization.constant.i32(i32 6, i32 12)
+// CHECK-NEXT: store i32 [[TMP1]], ptr addrspace(10) @_ZL10uint_const, align 4
+// CHECK-NEXT: ret void
+//
+//
+// CHECK-LABEL: define internal spir_func void @__cxx_global_var_init.6(
+// CHECK-SAME: ) #[[ATTR3]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[TMP0:%.*]] = call token @llvm.experimental.convergence.entry()
+// CHECK-NEXT: [[TMP1:%.*]] = call spir_func i64 @llvm.spv.get.specialization.constant.i64(i32 7, i64 25)
+// CHECK-NEXT: store i64 [[TMP1]], ptr addrspace(10) @_ZL11ulong_const, align 8
+// CHECK-NEXT: ret void
+//
+//
+// CHECK-LABEL: define internal spir_func void @__cxx_global_var_init.7(
+// CHECK-SAME: ) #[[ATTR3]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[TMP0:%.*]] = call token @llvm.experimental.convergence.entry()
+// CHECK-NEXT: [[TMP1:%.*]] = call reassoc nnan ninf nsz arcp afn spir_func float @llvm.spv.get.specialization.constant.f32(i32 8, float 0x4044333340000000)
+// CHECK-NEXT: store float [[TMP1]], ptr addrspace(10) @_ZL10half_const, align 4
+// CHECK-NEXT: ret void
+//
+//
+// CHECK-LABEL: define internal spir_func void @__cxx_global_var_init.8(
+// CHECK-SAME: ) #[[ATTR3]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[TMP0:%.*]] = call token @llvm.experimental.convergence.entry()
+// CHECK-NEXT: [[TMP1:%.*]] = call reassoc nnan ninf nsz arcp afn spir_func float @llvm.spv.get.specialization.constant.f32(i32 8, float 5.050000e+01)
+// CHECK-NEXT: store float [[TMP1]], ptr addrspace(10) @_ZL11float_const, align 4
+// CHECK-NEXT: ret void
+//
+//
+// CHECK-LABEL: define internal spir_func void @__cxx_global_var_init.9(
+// CHECK-SAME: ) #[[ATTR3]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[TMP0:%.*]] = call token @llvm.experimental.convergence.entry()
+// CHECK-NEXT: [[TMP1:%.*]] = call reassoc nnan ninf nsz arcp afn spir_func double @llvm.spv.get.specialization.constant.f64(i32 9, double 0x40590CCCC0000000)
+// CHECK-NEXT: store double [[TMP1]], ptr addrspace(10) @_ZL12double_const, align 8
+// CHECK-NEXT: ret void
+//
+//
+// CHECK-LABEL: define internal spir_func void @__cxx_global_var_init.10(
+// CHECK-SAME: ) #[[ATTR3]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[TMP0:%.*]] = call token @llvm.experimental.convergence.entry()
+// CHECK-NEXT: [[TMP1:%.*]] = call spir_func i32 @llvm.spv.get.specialization.constant.i32(i32 10, i32 30)
+// CHECK-NEXT: store i32 [[TMP1]], ptr addrspace(10) @_ZL10enum_const, align 4
+// CHECK-NEXT: ret void
diff --git a/clang/test/SemaHLSL/vk.spec-constant.error.hlsl b/clang/test/SemaHLSL/vk.spec-constant.error.hlsl
new file mode 100644
index 0000000000000..68b9f60f24833
--- /dev/null
+++ b/clang/test/SemaHLSL/vk.spec-constant.error.hlsl
@@ -0,0 +1,37 @@
+// 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 at +2{{'constant_id' attribute ignored}}
+#endif
+[[vk::constant_id(0)]]
+const bool sc0 = true;
+
+#ifdef __spirv__
+// expected-error at +2{{variable with 'vk::constant_id' attribute cannot have an initializer that is not a constexpr}}
+[[vk::constant_id(1)]]
+const bool sc1 = sc0; // error
+
+// expected-warning at +1{{'constant_id' attribute only applies to external global variables}}
+[[vk::constant_id(2)]]
+static const bool sc2 = false; // error
+
+// expected-error at +2{{variable with 'vk::constant_id' attribute must have an initializer}}
+[[vk::constant_id(3)]]
+const bool sc3; // error
+
+// expected-error at +2{{variable with 'vk::constant_id' attribute must be const}}
+[[vk::constant_id(4)]]
+bool sc4 = false; // error
+
+// expected-error at +2{{variable with 'vk::constant_id' attribute must be an enum, bool, integer, or floating point value}}
+[[vk::constant_id(5)]]
+const int2 sc5 = {0,0}; // error
+
+[numthreads(1,1,1)]
+void main() {
+ // expected-warning at +1{{'constant_id' attribute only applies to external global variables}}
+ [[vk::constant_id(6)]]
+ const bool sc6 = false; // error
+}
+#endif
diff --git a/llvm/include/llvm/IR/IntrinsicsSPIRV.td b/llvm/include/llvm/IR/IntrinsicsSPIRV.td
index 8d984d6ce58df..01f335a2ad2b9 100644
--- a/llvm/include/llvm/IR/IntrinsicsSPIRV.td
+++ b/llvm/include/llvm/IR/IntrinsicsSPIRV.td
@@ -137,6 +137,10 @@ let TargetPrefix = "spv" in {
: DefaultAttrsIntrinsic<[llvm_anyptr_ty], [llvm_any_ty, llvm_i32_ty],
[IntrNoMem]>;
+ def int_spv_get_specialization_constant
+ : DefaultAttrsIntrinsic<[llvm_any_ty], [llvm_i32_ty, LLVMMatchType<0>],
+ [IntrNoMem, IntrWillReturn]>;
+
// Read a value from the image buffer. It does not translate directly to a
// single OpImageRead because the result type is not necessarily a 4 element
// vector.
More information about the llvm-commits
mailing list