From a983170e889f14289a9714366f1380550d41d9f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= Date: Tue, 16 Sep 2025 16:54:27 +0200 Subject: [PATCH 1/4] [HLSL][DXIL][SPIRV] Add user semantics to Clang This commit adds initial support for user semantics. Those are generic cpp idenfier which translate differently than system semantics. Supporting user semantics will allows testing another side of semantics: shadowing and implicit indices. When a semantic is applied on a parent element (parameter or type definition), the semantics on its children declarations are ignored. Also, the semantic index is used to determine the index of the first element, but increases with each array element or struct field. See https://github.com/llvm/wg-hlsl/blob/main/proposals/0031-semantics.md --- clang/include/clang/Basic/Attr.td | 4 + .../clang/Basic/DiagnosticSemaKinds.td | 1 + clang/include/clang/Sema/SemaHLSL.h | 4 + clang/lib/CodeGen/CGHLSLRuntime.cpp | 76 ++++++++++++++++++ clang/lib/CodeGen/CGHLSLRuntime.h | 16 ++++ clang/lib/Sema/SemaHLSL.cpp | 35 ++++++++- .../semantics/DispatchThreadID.hlsl | 1 - .../semantics/semantic.arbitrary.hlsl | 32 ++++++++ .../CodeGenHLSL/semantics/semantic.array.hlsl | 37 +++++++++ .../semantics/semantic.struct.hlsl | 77 +++++++++++++++++++ clang/test/ParserHLSL/semantic_parsing.hlsl | 21 ++--- .../SemaHLSL/Semantics/semantics-invalid.hlsl | 17 ++++ .../SemaHLSL/Semantics/semantics-valid.hlsl | 33 ++++++++ llvm/include/llvm/IR/IntrinsicsDirectX.td | 6 ++ 14 files changed, 349 insertions(+), 11 deletions(-) create mode 100644 clang/test/CodeGenHLSL/semantics/semantic.arbitrary.hlsl create mode 100644 clang/test/CodeGenHLSL/semantics/semantic.array.hlsl create mode 100644 clang/test/CodeGenHLSL/semantics/semantic.struct.hlsl create mode 100644 clang/test/SemaHLSL/Semantics/semantics-invalid.hlsl create mode 100644 clang/test/SemaHLSL/Semantics/semantics-valid.hlsl diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td index 749f531ec9ab1..1013bfc575747 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -5017,6 +5017,10 @@ def HLSLUnparsedSemantic : HLSLAnnotationAttr { let Documentation = [InternalOnly]; } +def HLSLUserSemantic : HLSLSemanticAttr { + let Documentation = [InternalOnly]; +} + def HLSLSV_Position : HLSLSemanticAttr { let Documentation = [HLSLSV_PositionDocs]; } diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index fa509536bf021..f43707e513351 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -13184,6 +13184,7 @@ def err_hlsl_semantic_indexing_not_supported : Error<"semantic %0 does not allow indexing">; def err_hlsl_init_priority_unsupported : Error< "initializer priorities are not supported in HLSL">; +def err_hlsl_semantic_index_overlap : Error<"semantic index overlap %0">; def warn_hlsl_user_defined_type_missing_member: Warning<"binding type '%select{t|u|b|s|c}0' only applies to types containing %select{SRV resources|UAV resources|constant buffer resources|sampler state|numeric types}0">, InGroup; def err_hlsl_binding_type_mismatch: Error<"binding type '%select{t|u|b|s|c}0' only applies to %select{SRV resources|UAV resources|constant buffer resources|sampler state|numeric variables in the global scope}0">; diff --git a/clang/include/clang/Sema/SemaHLSL.h b/clang/include/clang/Sema/SemaHLSL.h index 8c3b6ae176389..1c268dcd29768 100644 --- a/clang/include/clang/Sema/SemaHLSL.h +++ b/clang/include/clang/Sema/SemaHLSL.h @@ -20,7 +20,9 @@ #include "clang/Basic/DiagnosticSema.h" #include "clang/Basic/SourceLocation.h" #include "clang/Sema/SemaBase.h" +#include "llvm/ADT/DenseMap.h" #include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringSet.h" #include "llvm/TargetParser/Triple.h" #include @@ -244,6 +246,8 @@ class SemaHLSL : public SemaBase { IdentifierInfo *RootSigOverrideIdent = nullptr; + llvm::DenseMap> ActiveInputSemantics; + struct SemanticInfo { HLSLSemanticAttr *Semantic; std::optional Index; diff --git a/clang/lib/CodeGen/CGHLSLRuntime.cpp b/clang/lib/CodeGen/CGHLSLRuntime.cpp index 945f9e2451bc1..895c6f13d475f 100644 --- a/clang/lib/CodeGen/CGHLSLRuntime.cpp +++ b/clang/lib/CodeGen/CGHLSLRuntime.cpp @@ -549,6 +549,16 @@ static void addSPIRVBuiltinDecoration(llvm::GlobalVariable *GV, GV->addMetadata("spirv.Decorations", *Decoration); } +static void addLocationDecoration(llvm::GlobalVariable *GV, unsigned Location) { + LLVMContext &Ctx = GV->getContext(); + IRBuilder<> B(GV->getContext()); + MDNode *Operands = + MDNode::get(Ctx, {ConstantAsMetadata::get(B.getInt32(/* Location */ 30)), + ConstantAsMetadata::get(B.getInt32(Location))}); + MDNode *Decoration = MDNode::get(Ctx, {Operands}); + GV->addMetadata("spirv.Decorations", *Decoration); +} + static llvm::Value *createSPIRVBuiltinLoad(IRBuilder<> &B, llvm::Module &M, llvm::Type *Ty, const Twine &Name, unsigned BuiltInID) { @@ -562,6 +572,69 @@ static llvm::Value *createSPIRVBuiltinLoad(IRBuilder<> &B, llvm::Module &M, return B.CreateLoad(Ty, GV); } +static llvm::Value *createSPIRVLocationLoad(IRBuilder<> &B, llvm::Module &M, + llvm::Type *Ty, unsigned Location, + StringRef Name = "") { + auto *GV = new llvm::GlobalVariable( + M, Ty, /* isConstant= */ true, llvm::GlobalValue::ExternalLinkage, + /* Initializer= */ nullptr, /* Name= */ Name, /* insertBefore= */ nullptr, + llvm::GlobalVariable::GeneralDynamicTLSModel, + /* AddressSpace */ 7, /* isExternallyInitialized= */ true); + GV->setVisibility(llvm::GlobalValue::HiddenVisibility); + addLocationDecoration(GV, Location); + return B.CreateLoad(Ty, GV); +} + +llvm::Value * +CGHLSLRuntime::emitSPIRVUserSemanticLoad(llvm::IRBuilder<> &B, llvm::Type *Type, + HLSLSemanticAttr *Semantic, + std::optional Index) { + Twine BaseName = Twine(Semantic->getAttrName()->getName()); + Twine VariableName = BaseName.concat(Twine(Index.value_or(0))); + + unsigned Location = SPIRVLastAssignedInputSemanticLocation; + + // DXC completely ignores the semantic/index pair. Location are assigned from + // the first semantic to the last. + llvm::ArrayType *AT = dyn_cast(Type); + unsigned ElementCount = AT ? AT->getNumElements() : 1; + SPIRVLastAssignedInputSemanticLocation += ElementCount; + return createSPIRVLocationLoad(B, CGM.getModule(), Type, Location, + VariableName.str()); +} + +llvm::Value * +CGHLSLRuntime::emitDXILUserSemanticLoad(llvm::IRBuilder<> &B, llvm::Type *Type, + HLSLSemanticAttr *Semantic, + std::optional Index) { + Twine BaseName = Twine(Semantic->getAttrName()->getName()); + Twine VariableName = BaseName.concat(Twine(Index.value_or(0))); + + // DXIL packing rules etc shall be handled here. + // FIXME: generate proper sigpoint, index, col, row values. + // FIXME: also DXIL loads vectors element by element. + SmallVector Args{B.getInt32(4), B.getInt32(0), B.getInt32(0), + B.getInt8(0), + llvm::PoisonValue::get(B.getInt32Ty())}; + + llvm::Intrinsic::ID IntrinsicID = llvm::Intrinsic::dx_load_input; + llvm::Value *Value = B.CreateIntrinsic(/*ReturnType=*/Type, IntrinsicID, Args, + nullptr, VariableName); + return Value; +} + +llvm::Value *CGHLSLRuntime::emitUserSemanticLoad( + IRBuilder<> &B, llvm::Type *Type, const clang::DeclaratorDecl *Decl, + HLSLSemanticAttr *Semantic, std::optional Index) { + if (CGM.getTarget().getTriple().isSPIRV()) + return emitSPIRVUserSemanticLoad(B, Type, Semantic, Index); + + if (CGM.getTarget().getTriple().isDXIL()) + return emitDXILUserSemanticLoad(B, Type, Semantic, Index); + + llvm_unreachable("Unsupported target for user-semantic load."); +} + llvm::Value *CGHLSLRuntime::emitSystemSemanticLoad( IRBuilder<> &B, llvm::Type *Type, const clang::DeclaratorDecl *Decl, Attr *Semantic, std::optional Index) { @@ -626,6 +699,9 @@ CGHLSLRuntime::handleScalarSemanticLoad(IRBuilder<> &B, const FunctionDecl *FD, std::optional Index = std::nullopt; if (Semantic->isSemanticIndexExplicit()) Index = Semantic->getSemanticIndex(); + + if (isa(Semantic)) + return emitUserSemanticLoad(B, Type, Decl, Semantic, Index); return emitSystemSemanticLoad(B, Type, Decl, Semantic, Index); } diff --git a/clang/lib/CodeGen/CGHLSLRuntime.h b/clang/lib/CodeGen/CGHLSLRuntime.h index d35df524fdc84..9d31714ab8606 100644 --- a/clang/lib/CodeGen/CGHLSLRuntime.h +++ b/clang/lib/CodeGen/CGHLSLRuntime.h @@ -200,9 +200,25 @@ class CGHLSLRuntime { llvm::GlobalVariable *BufGV); void initializeBufferFromBinding(const HLSLBufferDecl *BufDecl, llvm::GlobalVariable *GV); + void initializeBufferFromBinding(const HLSLBufferDecl *BufDecl, + llvm::GlobalVariable *GV, + HLSLResourceBindingAttr *RBA); + + llvm::Value *emitSPIRVUserSemanticLoad(llvm::IRBuilder<> &B, llvm::Type *Type, + HLSLSemanticAttr *Semantic, + std::optional Index); + llvm::Value *emitDXILUserSemanticLoad(llvm::IRBuilder<> &B, llvm::Type *Type, + HLSLSemanticAttr *Semantic, + std::optional Index); + llvm::Value *emitUserSemanticLoad(llvm::IRBuilder<> &B, llvm::Type *Type, + const clang::DeclaratorDecl *Decl, + HLSLSemanticAttr *Semantic, + std::optional Index); + llvm::Triple::ArchType getArch(); llvm::DenseMap LayoutTypes; + unsigned SPIRVLastAssignedInputSemanticLocation = 0; }; } // namespace CodeGen diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp index b9707f0036765..f337d9b71700a 100644 --- a/clang/lib/Sema/SemaHLSL.cpp +++ b/clang/lib/Sema/SemaHLSL.cpp @@ -775,6 +775,10 @@ HLSLSemanticAttr *SemaHLSL::createSemantic(const SemanticInfo &Info, DeclaratorDecl *TargetDecl) { std::string SemanticName = Info.Semantic->getAttrName()->getName().upper(); + if (dyn_cast(Info.Semantic)) + return createSemanticAttr(*Info.Semantic, TargetDecl, + Info.Index); + if (SemanticName == "SV_DISPATCHTHREADID") { return createSemanticAttr( *Info.Semantic, TargetDecl, Info.Index); @@ -818,6 +822,33 @@ bool SemaHLSL::determineActiveSemanticOnScalar(FunctionDecl *FD, checkSemanticAnnotation(FD, D, A); FD->addAttr(A); + + unsigned Location = ActiveSemantic.Index.value_or(0); + + const ConstantArrayType *AT = dyn_cast(D->getType()); + unsigned ElementCount = AT ? AT->getZExtSize() : 1; + ActiveSemantic.Index = Location + ElementCount; + + Twine BaseName = Twine(ActiveSemantic.Semantic->getAttrName()->getName()); + for (unsigned I = 0; I < ElementCount; ++I) { + Twine VariableName = BaseName.concat(Twine(Location + I)); + + auto It = ActiveInputSemantics.find(FD); + if (It == ActiveInputSemantics.end()) { + llvm::StringSet<> Set({VariableName.str()}); + auto Item = std::make_pair(FD, std::move(Set)); + ActiveInputSemantics.insert(std::move(Item)); + continue; + } + + auto [_, Inserted] = ActiveInputSemantics[FD].insert(VariableName.str()); + if (!Inserted) { + Diag(D->getLocation(), diag::err_hlsl_semantic_index_overlap) + << VariableName.str(); + return false; + } + } + return true; } @@ -947,6 +978,8 @@ void SemaHLSL::checkSemanticAnnotation(FunctionDecl *EntryPoint, return; DiagnoseAttrStageMismatch(SemanticAttr, ST, {llvm::Triple::Pixel}); break; + case attr::HLSLUserSemantic: + return; default: llvm_unreachable("Unknown SemanticAttr"); } @@ -1766,7 +1799,7 @@ void SemaHLSL::handleSemanticAttr(Decl *D, const ParsedAttr &AL) { if (AL.getAttrName()->getName().starts_with_insensitive("SV_")) diagnoseSystemSemanticAttr(D, AL, Index); else - Diag(AL.getLoc(), diag::err_hlsl_unknown_semantic) << AL; + D->addAttr(createSemanticAttr(AL, nullptr, Index)); } void SemaHLSL::handlePackOffsetAttr(Decl *D, const ParsedAttr &AL) { diff --git a/clang/test/CodeGenHLSL/semantics/DispatchThreadID.hlsl b/clang/test/CodeGenHLSL/semantics/DispatchThreadID.hlsl index 7aeb877072d87..b0abaeddff422 100644 --- a/clang/test/CodeGenHLSL/semantics/DispatchThreadID.hlsl +++ b/clang/test/CodeGenHLSL/semantics/DispatchThreadID.hlsl @@ -24,4 +24,3 @@ void foo(uint Idx : SV_DispatchThreadID) {} [shader("compute")] [numthreads(8,8,1)] void bar(uint2 Idx : SV_DispatchThreadID) {} - diff --git a/clang/test/CodeGenHLSL/semantics/semantic.arbitrary.hlsl b/clang/test/CodeGenHLSL/semantics/semantic.arbitrary.hlsl new file mode 100644 index 0000000000000..220e56b69febc --- /dev/null +++ b/clang/test/CodeGenHLSL/semantics/semantic.arbitrary.hlsl @@ -0,0 +1,32 @@ +// RUN: %clang_cc1 -triple spirv-unknown-vulkan-vertex -x hlsl -emit-llvm -finclude-default-header -disable-llvm-passes -o - %s | FileCheck %s --check-prefixes=CHECK,CHECK-SPIRV -DTARGET=spv +// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-vertex -x hlsl -emit-llvm -finclude-default-header -disable-llvm-passes -o - %s | FileCheck %s --check-prefixes=CHECK,CHECK-DXIL -DTARGET=dx + +// CHECK-SPIRV-DAG: @AAA0 = external hidden thread_local addrspace(7) externally_initialized constant float, !spirv.Decorations ![[#METADATA_0:]] +// CHECK-SPIRV-DAG: @B0 = external hidden thread_local addrspace(7) externally_initialized constant i32, !spirv.Decorations ![[#METADATA_2:]] +// CHECK-SPIRV-DAG: @CC0 = external hidden thread_local addrspace(7) externally_initialized constant <2 x float>, !spirv.Decorations ![[#METADATA_4:]] + + +// FIXME: replace `float2 c` with a matrix when available. +void main(float a : AAA, int b : B, float2 c : CC) { + float tmp = a + b + c.x + c.y; +} +// CHECK-SPIRV: define internal spir_func void @_Z4mainfiDv2_f(float noundef nofpclass(nan inf) %a, i32 noundef %b, <2 x float> noundef nofpclass(nan inf) %c) #0 { + +// CHECK: define void @main() + +// CHECK-DXIL: %AAA0 = call float @llvm.dx.load.input.f32(i32 4, i32 0, i32 0, i8 0, i32 poison) +// CHECK-DXIL: %B0 = call i32 @llvm.dx.load.input.i32(i32 4, i32 0, i32 0, i8 0, i32 poison) +// CHECK-DXIL %CC0 = call <2 x float> @llvm.dx.load.input.v2f32(i32 4, i32 0, i32 0, i8 0, i32 poison) +// CHECK-DXIL: call void @_Z4mainfiDv2_f(float %AAA0, i32 %B0, <2 x float> %CC0) + +// CHECK-SPIRV: %[[#AAA0:]] = load float, ptr addrspace(7) @AAA0, align 4 +// CHECK-SPIRV: %[[#B0:]] = load i32, ptr addrspace(7) @B0, align 4 +// CHECK-SPIRV: %[[#CC0:]] = load <2 x float>, ptr addrspace(7) @CC0, align 8 +// CHECK-SPIRV: call spir_func void @_Z4mainfiDv2_f(float %[[#AAA0]], i32 %[[#B0]], <2 x float> %[[#CC0]]) [ "convergencectrl"(token %0) ] + +// CHECK-SPIRV-DAG: ![[#METADATA_0]] = !{![[#METADATA_1:]]} +// CHECK-SPIRV-DAG: ![[#METADATA_1]] = !{i32 30, i32 0} +// CHECK-SPIRV-DAG: ![[#METADATA_2]] = !{![[#METADATA_3:]]} +// CHECK-SPIRV-DAG: ![[#METADATA_3]] = !{i32 30, i32 1} +// CHECK-SPIRV-DAG: ![[#METADATA_4]] = !{![[#METADATA_5:]]} +// CHECK-SPIRV-DAG: ![[#METADATA_5]] = !{i32 30, i32 2} diff --git a/clang/test/CodeGenHLSL/semantics/semantic.array.hlsl b/clang/test/CodeGenHLSL/semantics/semantic.array.hlsl new file mode 100644 index 0000000000000..b2cb3dad9f0ce --- /dev/null +++ b/clang/test/CodeGenHLSL/semantics/semantic.array.hlsl @@ -0,0 +1,37 @@ +// RUN: %clang_cc1 -triple spirv-linux-vulkan-library -x hlsl -emit-llvm -finclude-default-header -disable-llvm-passes -o - %s | FileCheck %s --check-prefixes=CHECK,CHECK-SPIRV -DTARGET=spv +// RUN: %clang_cc1 -triple dxil-px-shadermodel6.3-library -x hlsl -emit-llvm -finclude-default-header -disable-llvm-passes -o - %s | FileCheck %s --check-prefixes=CHECK,CHECK-DXIL -DTARGET=dx + +struct S0 { + float4 position[2]; + float4 color; +}; + +// CHECK: %struct.S0 = type { [2 x <4 x float>], <4 x float> } + +// CHECK-SPIRV: @A0 = external hidden thread_local addrspace(7) externally_initialized constant [2 x <4 x float>], !spirv.Decorations ![[#MD_0:]] +// CHECK-SPIRV: @A2 = external hidden thread_local addrspace(7) externally_initialized constant <4 x float>, !spirv.Decorations ![[#MD_2:]] + +// CHECK: define void @main0() +// CHECK-DXIL: %A0 = call [2 x <4 x float>] @llvm.dx.load.input.a2v4f32(i32 4, i32 0, i32 0, i8 0, i32 poison) +// CHECK-DXIL: %[[#TMP0:]] = insertvalue %struct.S0 poison, [2 x <4 x float>] %A0, 0 +// CHECK-DXIL: %A2 = call <4 x float> @llvm.dx.load.input.v4f32(i32 4, i32 0, i32 0, i8 0, i32 poison) +// CHECK-DXIL: %[[#TMP1:]] = insertvalue %struct.S0 %[[#TMP0]], <4 x float> %A2, 1 + +// CHECK-SPIRV: %[[#A0:]] = load [2 x <4 x float>], ptr addrspace(7) @A0, align 16 +// CHECK-SPIRV: %[[#TMP0:]] = insertvalue %struct.S0 poison, [2 x <4 x float>] %[[#A0]], 0 +// CHECK-SPIRV: %[[#A2:]] = load <4 x float>, ptr addrspace(7) @A2, align 16 +// CHECK-SPIRV: %[[#TMP1:]] = insertvalue %struct.S0 %[[#TMP0]], <4 x float> %[[#A2]], 1 + +// CHECK: %[[#ARG:]] = alloca %struct.S0, align 16 +// CHECK: store %struct.S0 %[[#TMP1]], ptr %[[#ARG]], align 16 +// CHECK-DXIL: call void @{{.*}}main0{{.*}}(ptr %[[#ARG]]) +// CHECK-SPIRV: call spir_func void @{{.*}}main0{{.*}}(ptr %[[#ARG]]) +[shader("pixel")] +void main0(S0 p : A) { + float tmp = p.position[0] + p.position[1] + p.color; +} + +// CHECK-SPIRV: ![[#MD_0]] = !{![[#MD_1:]]} +// CHECK-SPIRV: ![[#MD_1]] = !{i32 30, i32 0} +// CHECK-SPIRV: ![[#MD_2]] = !{![[#MD_3:]]} +// CHECK-SPIRV: ![[#MD_3]] = !{i32 30, i32 2} diff --git a/clang/test/CodeGenHLSL/semantics/semantic.struct.hlsl b/clang/test/CodeGenHLSL/semantics/semantic.struct.hlsl new file mode 100644 index 0000000000000..733cf3a1a7b9d --- /dev/null +++ b/clang/test/CodeGenHLSL/semantics/semantic.struct.hlsl @@ -0,0 +1,77 @@ +// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-library -x hlsl -emit-llvm -finclude-default-header -disable-llvm-passes -o - %s | FileCheck %s --check-prefixes=CHECK,CHECK-DXIL -DTARGET=dx +// RUN: %clang_cc1 -triple spirv-linux-vulkan-library -x hlsl -emit-llvm -finclude-default-header -disable-llvm-passes -o - %s | FileCheck %s --check-prefixes=CHECK,CHECK-SPIRV -DTARGET=spv + +struct S0 { + uint Idx : SV_DispatchThreadID; +}; + +// CHECK: define void @main0() +// CHECK-DXIL: %[[#ID:]] = call i32 @llvm.[[TARGET]].thread.id(i32 0) +// CHECK-SPIRV: %[[#ID:]] = call i32 @llvm.[[TARGET]].thread.id.i32(i32 0) +// CHECK: %[[#TMP:]] = insertvalue %struct.S0 poison, i32 %[[#ID:]], 0 +// CHECK: %[[#ARG:]] = alloca %struct.S0, align 8 +// CHECK: store %struct.S0 %[[#TMP]], ptr %[[#ARG]], align 4 +// CHECK-DXIL: call void @{{.*}}main0{{.*}}(ptr %[[#ARG]]) +// CHECK-SPIRV: call spir_func void @{{.*}}main0{{.*}}(ptr %[[#ARG]]) +[shader("compute")] +[numthreads(8,8,1)] +void main0(S0 p) {} + +struct S1 { + uint2 a : SV_DispatchThreadID; + uint2 b : SV_GroupThreadID; +}; + +// CHECK: define void @main1() +// CHECK-DXIL: %[[#ID:]] = call i32 @llvm.[[TARGET]].thread.id(i32 0) +// CHECK-SPIRV: %[[#ID:]] = call i32 @llvm.[[TARGET]].thread.id.i32(i32 0) +// CHECK: %[[#AX_:]] = insertelement <2 x i32> poison, i32 %[[#ID]], i64 0 +// CHECK-DXIL: %[[#ID:]] = call i32 @llvm.[[TARGET]].thread.id(i32 1) +// CHECK-SPIRV: %[[#ID:]] = call i32 @llvm.[[TARGET]].thread.id.i32(i32 1) +// CHECK: %[[#AXY:]] = insertelement <2 x i32> %[[#AX_]], i32 %[[#ID]], i64 1 +// CHECK: %[[#S1A_:]] = insertvalue %struct.S1 poison, <2 x i32> %[[#AXY]], 0 +// CHECK-DXIL: %[[#ID_X:]] = call i32 @llvm.[[TARGET]].thread.id.in.group(i32 0) +// CHECK-SPIRV: %[[#ID_X:]] = call i32 @llvm.[[TARGET]].thread.id.in.group.i32(i32 0) +// CHECK: %[[#ID_X_:]] = insertelement <2 x i32> poison, i32 %[[#ID_X]], i64 0 +// CHECK-DXIL: %[[#ID_Y:]] = call i32 @llvm.[[TARGET]].thread.id.in.group(i32 1) +// CHECK-SPIRV: %[[#ID_Y:]] = call i32 @llvm.[[TARGET]].thread.id.in.group.i32(i32 1) +// CHECK: %[[#ID_XY:]] = insertelement <2 x i32> %[[#ID_X_]], i32 %[[#ID_Y]], i64 1 +// CHECK: %[[#S1AB:]] = insertvalue %struct.S1 %[[#S1A_]], <2 x i32> %[[#ID_XYZ:]], 1 +// CHECK: %[[#ARG:]] = alloca %struct.S1, align 8 +// CHECK: store %struct.S1 %[[#S1AB]], ptr %[[#ARG]], align 8 +// CHECK-DXIL: call void @{{.*}}main1{{.*}}(ptr %[[#ARG]]) +// CHECK-SPIRV: call spir_func void @{{.*}}main1{{.*}}(ptr %[[#ARG]]) +[shader("compute")] +[numthreads(8,8,1)] +void main1(S1 p) {} + +struct S2C { + uint2 b : SV_GroupThreadID; +}; + +struct S2 { + uint a : SV_DispatchThreadID; + S2C child; +}; + +// CHECK: define void @main2() +// CHECK-DXIL: %[[#ID:]] = call i32 @llvm.[[TARGET]].thread.id(i32 0) +// CHECK-SPIRV: %[[#ID:]] = call i32 @llvm.[[TARGET]].thread.id.i32(i32 0) +// CHECK: %[[#S2A_:]] = insertvalue %struct.S2 poison, i32 %[[#ID:]], 0 + +// CHECK-DXIL: %[[#ID_X:]] = call i32 @llvm.[[TARGET]].thread.id.in.group(i32 0) +// CHECK-SPIRV: %[[#ID_X:]] = call i32 @llvm.[[TARGET]].thread.id.in.group.i32(i32 0) +// CHECK: %[[#ID_X_:]] = insertelement <2 x i32> poison, i32 %[[#ID_X]], i64 0 +// CHECK-DXIL: %[[#ID_Y:]] = call i32 @llvm.[[TARGET]].thread.id.in.group(i32 1) +// CHECK-SPIRV: %[[#ID_Y:]] = call i32 @llvm.[[TARGET]].thread.id.in.group.i32(i32 1) +// CHECK: %[[#ID_XY:]] = insertelement <2 x i32> %[[#ID_X_]], i32 %[[#ID_Y]], i64 1 +// CHECK: %[[#S2C:]] = insertvalue %struct.S2C poison, <2 x i32> %[[#ID_XY:]], 0 + +// CHECK: %[[#S2AB:]] = insertvalue %struct.S2 %[[#S2A_]], %struct.S2C %[[#S2V:]], 1 +// CHECK: %[[#ARG:]] = alloca %struct.S2, align 8 +// CHECK: store %struct.S2 %[[#S2AB]], ptr %[[#ARG]], align 1 +// CHECK-DXIL: call void @{{.*}}main2{{.*}}(ptr %[[#ARG]]) +// CHECK-SPIRV: call spir_func void @{{.*}}main2{{.*}}(ptr %[[#ARG]]) +[shader("compute")] +[numthreads(8,8,1)] +void main2(S2 p) {} diff --git a/clang/test/ParserHLSL/semantic_parsing.hlsl b/clang/test/ParserHLSL/semantic_parsing.hlsl index 726deadb7c44c..9d8a60d175268 100644 --- a/clang/test/ParserHLSL/semantic_parsing.hlsl +++ b/clang/test/ParserHLSL/semantic_parsing.hlsl @@ -12,30 +12,33 @@ void Pony(int GI : SV_IWantAPony) { } // expected-note@+1 {{to match this '('}} void SuperPony(int GI : 0) { } -// expected-error@+1 {{unknown HLSL semantic '_'}} +// '_' is a valid CPP identifier. void MegaPony(int GI : _) { } -// expected-error@+1 {{unknown HLSL semantic 'A0A'}} +void GarguantuanPony(int GI : _1) { } + void CoolPony(int GI : A0A0) { } -// expected-error@+1 {{unknown HLSL semantic 'A_'}} void NicePony(int GI : A_0) { } -// expected-error@+1 {{unknown HLSL semantic 'A'}} void CutePony(int GI : A00) { } -// expected-error@+3 {{unknown HLSL semantic 'A'}} // expected-error@+2 {{expected ')'}} // expected-note@+1 {{to match this '('}} void DoublePony(int GI : A00 B) { } -// expected-error@+1 {{unknown HLSL semantic 'é'}} -void BigPony(int GI : é) { } +// Unicode can be used: +// https://timsong-cpp.github.io/cppwp/n3337/charname.allowed +void FrenchPony(int GI : garçon_de_café) { } +void UnicodePony(int GI : ℮) { } + +// Since P1949 seems Emojis are not allowed, even if in the range +// mentionned in N3337. +// https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p1949r7.html // expected-error@+2 {{unexpected character }} // expected-error@+1 {{expected HLSL Semantic identifier}} void UTFPony(int GI : 😊) { } -// expected-error@+2 {{character not allowed in an identifier}} -// expected-error@+1 {{unknown HLSL semantic 'PonyWithA😊'}} +// expected-error@+1 {{character not allowed in an identifier}} void SmilingPony(int GI : PonyWithA😊) { } diff --git a/clang/test/SemaHLSL/Semantics/semantics-invalid.hlsl b/clang/test/SemaHLSL/Semantics/semantics-invalid.hlsl new file mode 100644 index 0000000000000..fdba6f624d289 --- /dev/null +++ b/clang/test/SemaHLSL/Semantics/semantics-invalid.hlsl @@ -0,0 +1,17 @@ +// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-compute -x hlsl -fsyntax-only -hlsl-entry main -verify %s + +typedef float t_f : SEMANTIC; // expected-warning{{'SEMANTIC' attribute only applies to parameters, non-static data members, and functions}} + +struct semantic_on_struct : SEMANTIC { // expected-error{{expected class name}} + float a; +}; + +struct s_fields_multiple_semantics { + float a : semantic_a : semantic_c; // expected-error{{use of undeclared identifier 'semantic_c'}} + float b : semantic_b; +}; + +[numthreads(1, 1, 1)] +void main() { + float a : SEM_A; // expected-warning{{'SEM_A' attribute only applies to parameters, non-static data members, and functions}} +} diff --git a/clang/test/SemaHLSL/Semantics/semantics-valid.hlsl b/clang/test/SemaHLSL/Semantics/semantics-valid.hlsl new file mode 100644 index 0000000000000..1e6bae4fcbca5 --- /dev/null +++ b/clang/test/SemaHLSL/Semantics/semantics-valid.hlsl @@ -0,0 +1,33 @@ +// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-compute -hlsl-entry CSMain -x hlsl -finclude-default-header -ast-dump -o - %s | FileCheck %s + +struct s_fields { + float a : semantic_a; + float b : semantic_b; +// CHECK: |-CXXRecordDecl 0x{{[0-9a-fA-F]+}} <{{.*}}> line:[[@LINE-3]]:8 struct s_fields definition +// CHECK: | |-FieldDecl 0x{{[0-9a-fA-F]+}} <{{.*}}> col:9 a 'float' +// CHECK: | | `-HLSLUserSemanticAttr 0x{{[0-9a-fA-F]+}} +// CHECK: | `-FieldDecl 0x{{[0-9a-fA-F]+}} <{{.*}}> col:9 b 'float' +// CHECK: | `-HLSLUserSemanticAttr 0x{{[0-9a-fA-F]+}} +}; + +float fn_foo1(float a : a, float b : b) : sem_ret { return 1.0f; } +// CHECK: |-FunctionDecl {{.*}} <{{.*}}> col:7 fn_foo1 'float (float, float)' +// CHECK-NEXT: | |-ParmVarDecl {{.*}} <{{.*}}> col:21 a 'float' +// CHECK-NEXT: | | `-HLSLUserSemanticAttr {{.*}} <{{.*}}> +// CHECK-NEXT: | |-ParmVarDecl {{.*}} <{{.*}}> col:34 b 'float' +// CHECK-NEXT: | | `-HLSLUserSemanticAttr {{.*}} <{{.*}}> +// CHECK-NEXT: | |-CompoundStmt {{.*}} <{{.*}}> +// CHECK-NEXT: | | `-ReturnStmt {{.*}} <{{.*}}> +// CHECK-NEXT: | | `-FloatingLiteral {{.*}} <{{.*}}> 'float' 1.000000e+00 +// CHECK-NEXT: | `-HLSLUserSemanticAttr {{.*}} <{{.*}}> +float fn_foo2(float a : a, float b : b) : sem_ret : also_ret { return 1.0f; } +// CHECK: `-FunctionDecl {{.*}} <{{.*}}> col:7 fn_foo2 'float (float, float)' +// CHECK-NEXT: |-ParmVarDecl {{.*}} <{{.*}}> col:21 a 'float' +// CHECK-NEXT: | `-HLSLUserSemanticAttr {{.*}} <{{.*}}> +// CHECK-NEXT: |-ParmVarDecl {{.*}} <{{.*}}> col:34 b 'float' +// CHECK-NEXT: | `-HLSLUserSemanticAttr {{.*}} <{{.*}}> +// CHECK-NEXT: |-CompoundStmt {{.*}} <{{.*}}> +// CHECK-NEXT: | `-ReturnStmt {{.*}} <{{.*}}> +// CHECK-NEXT: | `-FloatingLiteral {{.*}} <{{.*}}> 'float' 1.000000e+00 +// CHECK-NEXT: |-HLSLUserSemanticAttr {{.*}} <{{.*}}> +// CHECK-NEXT: `-HLSLUserSemanticAttr {{.*}} <{{.*}}> diff --git a/llvm/include/llvm/IR/IntrinsicsDirectX.td b/llvm/include/llvm/IR/IntrinsicsDirectX.td index 9924b905aee63..d7db935ee07f1 100644 --- a/llvm/include/llvm/IR/IntrinsicsDirectX.td +++ b/llvm/include/llvm/IR/IntrinsicsDirectX.td @@ -176,4 +176,10 @@ def int_dx_firstbitlow : DefaultAttrsIntrinsic<[LLVMScalarOrSameVectorWidth<0, l def int_dx_group_memory_barrier_with_group_sync : DefaultAttrsIntrinsic<[], [], [IntrConvergent]>; + +def int_dx_load_input + : DefaultAttrsIntrinsic<[llvm_any_ty], + [llvm_i32_ty, llvm_i32_ty, llvm_i32_ty, llvm_i8_ty, + llvm_i32_ty], + [IntrConvergent]>; } From 610290c71e75ac0a548f0b17dc3243ff72b6d60d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= Date: Thu, 30 Oct 2025 11:24:23 +0100 Subject: [PATCH 2/4] remove default value --- clang/lib/CodeGen/CGHLSLRuntime.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/lib/CodeGen/CGHLSLRuntime.cpp b/clang/lib/CodeGen/CGHLSLRuntime.cpp index 895c6f13d475f..e392a12044a39 100644 --- a/clang/lib/CodeGen/CGHLSLRuntime.cpp +++ b/clang/lib/CodeGen/CGHLSLRuntime.cpp @@ -574,7 +574,7 @@ static llvm::Value *createSPIRVBuiltinLoad(IRBuilder<> &B, llvm::Module &M, static llvm::Value *createSPIRVLocationLoad(IRBuilder<> &B, llvm::Module &M, llvm::Type *Ty, unsigned Location, - StringRef Name = "") { + StringRef Name) { auto *GV = new llvm::GlobalVariable( M, Ty, /* isConstant= */ true, llvm::GlobalValue::ExternalLinkage, /* Initializer= */ nullptr, /* Name= */ Name, /* insertBefore= */ nullptr, From b18199fbe1cb2a71531ea4fb2e40508238c18fe4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= Date: Thu, 30 Oct 2025 16:36:28 +0100 Subject: [PATCH 3/4] pr-feedback --- clang/test/CodeGenHLSL/semantics/semantic.arbitrary.hlsl | 8 ++++++-- clang/test/ParserHLSL/semantic_parsing.hlsl | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/clang/test/CodeGenHLSL/semantics/semantic.arbitrary.hlsl b/clang/test/CodeGenHLSL/semantics/semantic.arbitrary.hlsl index 220e56b69febc..96d5b995fa74a 100644 --- a/clang/test/CodeGenHLSL/semantics/semantic.arbitrary.hlsl +++ b/clang/test/CodeGenHLSL/semantics/semantic.arbitrary.hlsl @@ -24,9 +24,13 @@ void main(float a : AAA, int b : B, float2 c : CC) { // CHECK-SPIRV: %[[#CC0:]] = load <2 x float>, ptr addrspace(7) @CC0, align 8 // CHECK-SPIRV: call spir_func void @_Z4mainfiDv2_f(float %[[#AAA0]], i32 %[[#B0]], <2 x float> %[[#CC0]]) [ "convergencectrl"(token %0) ] + // CHECK-SPIRV-DAG: ![[#METADATA_0]] = !{![[#METADATA_1:]]} -// CHECK-SPIRV-DAG: ![[#METADATA_1]] = !{i32 30, i32 0} // CHECK-SPIRV-DAG: ![[#METADATA_2]] = !{![[#METADATA_3:]]} -// CHECK-SPIRV-DAG: ![[#METADATA_3]] = !{i32 30, i32 1} // CHECK-SPIRV-DAG: ![[#METADATA_4]] = !{![[#METADATA_5:]]} + +// CHECK-SPIRV-DAG: ![[#METADATA_1]] = !{i32 30, i32 0} +// CHECK-SPIRV-DAG: ![[#METADATA_3]] = !{i32 30, i32 1} // CHECK-SPIRV-DAG: ![[#METADATA_5]] = !{i32 30, i32 2} +// | `- Location index +// `-> Decoration "Location" diff --git a/clang/test/ParserHLSL/semantic_parsing.hlsl b/clang/test/ParserHLSL/semantic_parsing.hlsl index 9d8a60d175268..bff7bd03189e7 100644 --- a/clang/test/ParserHLSL/semantic_parsing.hlsl +++ b/clang/test/ParserHLSL/semantic_parsing.hlsl @@ -33,7 +33,7 @@ void FrenchPony(int GI : garçon_de_café) { } void UnicodePony(int GI : ℮) { } // Since P1949 seems Emojis are not allowed, even if in the range -// mentionned in N3337. +// mentioned in N3337. // https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p1949r7.html // expected-error@+2 {{unexpected character }} From b95c89aeb338213ffe0a9e2bcf81e53ef4a9eb93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= Date: Fri, 31 Oct 2025 16:09:56 +0100 Subject: [PATCH 4/4] remove the need for ActiveInputSemantics attribute Each function is independent, no need to keep this across calls. --- clang/include/clang/Sema/SemaHLSL.h | 8 ++++---- clang/lib/Sema/SemaHLSL.cpp | 31 +++++++++++++---------------- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/clang/include/clang/Sema/SemaHLSL.h b/clang/include/clang/Sema/SemaHLSL.h index 1c268dcd29768..28b03ac4c4676 100644 --- a/clang/include/clang/Sema/SemaHLSL.h +++ b/clang/include/clang/Sema/SemaHLSL.h @@ -246,8 +246,6 @@ class SemaHLSL : public SemaBase { IdentifierInfo *RootSigOverrideIdent = nullptr; - llvm::DenseMap> ActiveInputSemantics; - struct SemanticInfo { HLSLSemanticAttr *Semantic; std::optional Index; @@ -263,9 +261,11 @@ class SemaHLSL : public SemaBase { HLSLSemanticAttr *createSemantic(const SemanticInfo &Semantic, DeclaratorDecl *TargetDecl); bool determineActiveSemanticOnScalar(FunctionDecl *FD, DeclaratorDecl *D, - SemanticInfo &ActiveSemantic); + SemanticInfo &ActiveSemantic, + llvm::StringSet<> &ActiveInputSemantics); bool determineActiveSemantic(FunctionDecl *FD, DeclaratorDecl *D, - SemanticInfo &ActiveSemantic); + SemanticInfo &ActiveSemantic, + llvm::StringSet<> &ActiveInputSemantics); void processExplicitBindingsOnDecl(VarDecl *D); diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp index f337d9b71700a..a06c57b15c585 100644 --- a/clang/lib/Sema/SemaHLSL.cpp +++ b/clang/lib/Sema/SemaHLSL.cpp @@ -801,9 +801,10 @@ HLSLSemanticAttr *SemaHLSL::createSemantic(const SemanticInfo &Info, return nullptr; } -bool SemaHLSL::determineActiveSemanticOnScalar(FunctionDecl *FD, - DeclaratorDecl *D, - SemanticInfo &ActiveSemantic) { +bool SemaHLSL::determineActiveSemanticOnScalar( + FunctionDecl *FD, DeclaratorDecl *D, SemanticInfo &ActiveSemantic, + llvm::StringSet<> &ActiveInputSemantics) { + if (ActiveSemantic.Semantic == nullptr) { ActiveSemantic.Semantic = D->getAttr(); if (ActiveSemantic.Semantic && @@ -833,15 +834,7 @@ bool SemaHLSL::determineActiveSemanticOnScalar(FunctionDecl *FD, for (unsigned I = 0; I < ElementCount; ++I) { Twine VariableName = BaseName.concat(Twine(Location + I)); - auto It = ActiveInputSemantics.find(FD); - if (It == ActiveInputSemantics.end()) { - llvm::StringSet<> Set({VariableName.str()}); - auto Item = std::make_pair(FD, std::move(Set)); - ActiveInputSemantics.insert(std::move(Item)); - continue; - } - - auto [_, Inserted] = ActiveInputSemantics[FD].insert(VariableName.str()); + auto [_, Inserted] = ActiveInputSemantics.insert(VariableName.str()); if (!Inserted) { Diag(D->getLocation(), diag::err_hlsl_semantic_index_overlap) << VariableName.str(); @@ -852,8 +845,9 @@ bool SemaHLSL::determineActiveSemanticOnScalar(FunctionDecl *FD, return true; } -bool SemaHLSL::determineActiveSemantic(FunctionDecl *FD, DeclaratorDecl *D, - SemanticInfo &ActiveSemantic) { +bool SemaHLSL::determineActiveSemantic( + FunctionDecl *FD, DeclaratorDecl *D, SemanticInfo &ActiveSemantic, + llvm::StringSet<> &ActiveInputSemantics) { if (ActiveSemantic.Semantic == nullptr) { ActiveSemantic.Semantic = D->getAttr(); if (ActiveSemantic.Semantic && @@ -864,12 +858,13 @@ bool SemaHLSL::determineActiveSemantic(FunctionDecl *FD, DeclaratorDecl *D, const Type *T = D->getType()->getUnqualifiedDesugaredType(); const RecordType *RT = dyn_cast(T); if (!RT) - return determineActiveSemanticOnScalar(FD, D, ActiveSemantic); + return determineActiveSemanticOnScalar(FD, D, ActiveSemantic, + ActiveInputSemantics); const RecordDecl *RD = RT->getDecl(); for (FieldDecl *Field : RD->fields()) { SemanticInfo Info = ActiveSemantic; - if (!determineActiveSemantic(FD, Field, Info)) { + if (!determineActiveSemantic(FD, Field, Info, ActiveInputSemantics)) { Diag(Field->getLocation(), diag::note_hlsl_semantic_used_here) << Field; return false; } @@ -942,12 +937,14 @@ void SemaHLSL::CheckEntryPoint(FunctionDecl *FD) { llvm_unreachable("Unhandled environment in triple"); } + llvm::StringSet<> ActiveInputSemantics; for (ParmVarDecl *Param : FD->parameters()) { SemanticInfo ActiveSemantic; ActiveSemantic.Semantic = nullptr; ActiveSemantic.Index = std::nullopt; - if (!determineActiveSemantic(FD, Param, ActiveSemantic)) { + if (!determineActiveSemantic(FD, Param, ActiveSemantic, + ActiveInputSemantics)) { Diag(Param->getLocation(), diag::note_previous_decl) << Param; FD->setInvalidDecl(); }