https://github.com/farzonl updated https://github.com/llvm/llvm-project/pull/210116
>From a5ea01283f44aefec49f2337cf7ddbc1e9640580 Mon Sep 17 00:00:00 2001 From: Farzon Lotfi <[email protected]> Date: Thu, 16 Jul 2026 13:11:16 -0400 Subject: [PATCH 1/2] [Clang][HLSL][SPIRV] Support Flat and Location decorators in the frontend fixes #194293 fixes #194432 In HLSL we need both Location and and Flat must be emitted as a single `spirv.Decorations` node. Also we need to apply Flat decoration to pixe/fragment shades with inputs of integer or double because only 32 bit floats can be interpolated. Assisted with Claude Opus 4.8 via Copilot --- clang/lib/CodeGen/CGHLSLRuntime.cpp | 62 ++++++++++++++++--- clang/lib/CodeGen/CGHLSLRuntime.h | 7 ++- .../semantics/flat-decoration.ps.hlsl | 29 +++++++++ llvm/test/CodeGen/SPIRV/semantics/flat.ps.ll | 51 +++++++++++++++ 4 files changed, 137 insertions(+), 12 deletions(-) create mode 100644 clang/test/CodeGenHLSL/semantics/flat-decoration.ps.hlsl create mode 100644 llvm/test/CodeGen/SPIRV/semantics/flat.ps.ll diff --git a/clang/lib/CodeGen/CGHLSLRuntime.cpp b/clang/lib/CodeGen/CGHLSLRuntime.cpp index 8794579166b6a..283bfeb73df11 100644 --- a/clang/lib/CodeGen/CGHLSLRuntime.cpp +++ b/clang/lib/CodeGen/CGHLSLRuntime.cpp @@ -1062,6 +1062,26 @@ static void addLocationDecoration(llvm::GlobalVariable *GV, unsigned Location) { GV->addMetadata("spirv.Decorations", *Decoration); } +// A fragment shader input interface variable whose base type is an integer or +// a 64-bit float (double) cannot be interpolated by the rasterizer. The Vulkan +// specification requires these variables to be decorated with Flat (see +// VUID-StandaloneSpirv-Flat-04744). Arrays and vectors are unwrapped to inspect +// their base scalar type. +static bool inputRequiresFlatDecoration(llvm::Type *Ty) { + while (true) { + if (auto *AT = dyn_cast<llvm::ArrayType>(Ty)) { + Ty = AT->getElementType(); + continue; + } + if (auto *VT = dyn_cast<llvm::FixedVectorType>(Ty)) { + Ty = VT->getElementType(); + continue; + } + break; + } + return Ty->isIntegerTy() || Ty->isDoubleTy(); +} + static llvm::Value *createSPIRVBuiltinLoad(IRBuilder<> &B, llvm::Module &M, llvm::Type *Ty, const Twine &Name, unsigned BuiltInID) { @@ -1077,20 +1097,35 @@ 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, bool NeedsFlat) { 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); + + // Emit all decorations as a single `spirv.Decorations` node. Attaching + // multiple `spirv.Decorations` metadata nodes to the same global is not + // supported by the SPIR-V backend and results in all but one being dropped. + LLVMContext &Ctx = GV->getContext(); + SmallVector<Metadata *, 2> Decorations; + Decorations.push_back( + MDNode::get(Ctx, {ConstantAsMetadata::get(B.getInt32(/* Location */ 30)), + ConstantAsMetadata::get(B.getInt32(Location))})); + if (NeedsFlat) + Decorations.push_back( + MDNode::get(Ctx, {ConstantAsMetadata::get( + B.getInt32(/* Spirv::Decoration::Flat */ 14))})); + GV->addMetadata("spirv.Decorations", *MDNode::get(Ctx, Decorations)); + return B.CreateLoad(Ty, GV); } llvm::Value *CGHLSLRuntime::emitSPIRVUserSemanticLoad( - llvm::IRBuilder<> &B, llvm::Type *Type, const clang::DeclaratorDecl *Decl, - HLSLAppliedSemanticAttr *Semantic, std::optional<unsigned> Index) { + llvm::IRBuilder<> &B, const FunctionDecl *FD, llvm::Type *Type, + const clang::DeclaratorDecl *Decl, HLSLAppliedSemanticAttr *Semantic, + std::optional<unsigned> Index) { Twine BaseName = Twine(Semantic->getAttrName()->getName()); Twine VariableName = BaseName.concat(Twine(Index.value_or(0))); @@ -1104,8 +1139,14 @@ llvm::Value *CGHLSLRuntime::emitSPIRVUserSemanticLoad( unsigned ElementCount = AT ? AT->getNumElements() : 1; SPIRVLastAssignedInputSemanticLocation += ElementCount; + const auto *ShaderAttr = FD->getAttr<HLSLShaderAttr>(); + bool NeedsFlat = + ShaderAttr && + ShaderAttr->getType() == llvm::Triple::EnvironmentType::Pixel && + inputRequiresFlatDecoration(Type); + return createSPIRVLocationLoad(B, CGM.getModule(), Type, Location, - VariableName.str()); + VariableName.str(), NeedsFlat); } static void createSPIRVLocationStore(IRBuilder<> &B, llvm::Module &M, @@ -1197,10 +1238,11 @@ void CGHLSLRuntime::emitDXILUserSemanticStore(llvm::IRBuilder<> &B, } llvm::Value *CGHLSLRuntime::emitUserSemanticLoad( - IRBuilder<> &B, llvm::Type *Type, const clang::DeclaratorDecl *Decl, - HLSLAppliedSemanticAttr *Semantic, std::optional<unsigned> Index) { + IRBuilder<> &B, const FunctionDecl *FD, llvm::Type *Type, + const clang::DeclaratorDecl *Decl, HLSLAppliedSemanticAttr *Semantic, + std::optional<unsigned> Index) { if (CGM.getTarget().getTriple().isSPIRV()) - return emitSPIRVUserSemanticLoad(B, Type, Decl, Semantic, Index); + return emitSPIRVUserSemanticLoad(B, FD, Type, Decl, Semantic, Index); if (CGM.getTarget().getTriple().isDXIL()) return emitDXILUserSemanticLoad(B, Type, Semantic, Index); @@ -1275,7 +1317,7 @@ llvm::Value *CGHLSLRuntime::emitSystemSemanticLoad( } if (ST == Triple::EnvironmentType::Vertex) { - return emitUserSemanticLoad(B, Type, Decl, Semantic, Index); + return emitUserSemanticLoad(B, FD, Type, Decl, Semantic, Index); } } @@ -1344,7 +1386,7 @@ llvm::Value *CGHLSLRuntime::handleScalarSemanticLoad( std::optional<unsigned> Index = Semantic->getSemanticIndex(); if (Semantic->getAttrName()->getName().starts_with_insensitive("SV_")) return emitSystemSemanticLoad(B, FD, Type, Decl, Semantic, Index); - return emitUserSemanticLoad(B, Type, Decl, Semantic, Index); + return emitUserSemanticLoad(B, FD, Type, Decl, Semantic, Index); } void CGHLSLRuntime::handleScalarSemanticStore( diff --git a/clang/lib/CodeGen/CGHLSLRuntime.h b/clang/lib/CodeGen/CGHLSLRuntime.h index cf47b1633fd3c..0664eef464f98 100644 --- a/clang/lib/CodeGen/CGHLSLRuntime.h +++ b/clang/lib/CodeGen/CGHLSLRuntime.h @@ -335,14 +335,17 @@ class CGHLSLRuntime { llvm::GlobalVariable *GV, HLSLResourceBindingAttr *RBA); - llvm::Value *emitSPIRVUserSemanticLoad(llvm::IRBuilder<> &B, llvm::Type *Type, + llvm::Value *emitSPIRVUserSemanticLoad(llvm::IRBuilder<> &B, + const FunctionDecl *FD, + llvm::Type *Type, const clang::DeclaratorDecl *Decl, HLSLAppliedSemanticAttr *Semantic, std::optional<unsigned> Index); llvm::Value *emitDXILUserSemanticLoad(llvm::IRBuilder<> &B, llvm::Type *Type, HLSLAppliedSemanticAttr *Semantic, std::optional<unsigned> Index); - llvm::Value *emitUserSemanticLoad(llvm::IRBuilder<> &B, llvm::Type *Type, + llvm::Value *emitUserSemanticLoad(llvm::IRBuilder<> &B, + const FunctionDecl *FD, llvm::Type *Type, const clang::DeclaratorDecl *Decl, HLSLAppliedSemanticAttr *Semantic, std::optional<unsigned> Index); diff --git a/clang/test/CodeGenHLSL/semantics/flat-decoration.ps.hlsl b/clang/test/CodeGenHLSL/semantics/flat-decoration.ps.hlsl new file mode 100644 index 0000000000000..5e97be23386bd --- /dev/null +++ b/clang/test/CodeGenHLSL/semantics/flat-decoration.ps.hlsl @@ -0,0 +1,29 @@ +// RUN: %clang_cc1 -triple spirv-pc-vulkan1.3-pixel -x hlsl -emit-llvm -finclude-default-header -disable-llvm-passes -o - %s | FileCheck %s --check-prefix=CHECK-SPIRV + +// CHECK-SPIRV-DAG: @A0 = external hidden thread_local addrspace(7) externally_initialized constant float, !spirv.Decorations ![[#FLOAT_MD:]] +// CHECK-SPIRV-DAG: @B0 = external hidden thread_local addrspace(7) externally_initialized constant i32, !spirv.Decorations ![[#INT_MD:]] +// CHECK-SPIRV-DAG: @C0 = external hidden thread_local addrspace(7) externally_initialized constant double, !spirv.Decorations ![[#DOUBLE_MD:]] +// CHECK-SPIRV-DAG: @D0 = external hidden thread_local addrspace(7) externally_initialized constant <2 x i32>, !spirv.Decorations ![[#IVEC_MD:]] + +float main(float a : A, int b : B, double c : C, int2 d : D) : SV_Target { + return a + b + (float)c + d.x + d.y; +} + +// The float input only carries a Location decoration (no Flat). +// CHECK-SPIRV-DAG: ![[#FLOAT_MD]] = !{![[#FLOAT_LOC:]]} +// CHECK-SPIRV-DAG: ![[#FLOAT_LOC]] = !{i32 30, i32 0} + +// The integer input carries both Location and Flat decorations in one node. +// CHECK-SPIRV-DAG: ![[#INT_MD]] = !{![[#INT_LOC:]], ![[#FLAT:]]} +// CHECK-SPIRV-DAG: ![[#INT_LOC]] = !{i32 30, i32 1} + +// The double input carries both Location and Flat decorations in one node. +// CHECK-SPIRV-DAG: ![[#DOUBLE_MD]] = !{![[#DOUBLE_LOC:]], ![[#FLAT]]} +// CHECK-SPIRV-DAG: ![[#DOUBLE_LOC]] = !{i32 30, i32 2} + +// The integer vector input carries both Location and Flat decorations in one node. +// CHECK-SPIRV-DAG: ![[#IVEC_MD]] = !{![[#IVEC_LOC:]], ![[#FLAT]]} +// CHECK-SPIRV-DAG: ![[#IVEC_LOC]] = !{i32 30, i32 3} + +// CHECK-SPIRV-DAG: ![[#FLAT]] = !{i32 14} +// `-> SPIR-V decoration 'Flat' diff --git a/llvm/test/CodeGen/SPIRV/semantics/flat.ps.ll b/llvm/test/CodeGen/SPIRV/semantics/flat.ps.ll new file mode 100644 index 0000000000000..82934a85f9949 --- /dev/null +++ b/llvm/test/CodeGen/SPIRV/semantics/flat.ps.ll @@ -0,0 +1,51 @@ +; RUN: llc -O0 -verify-machineinstrs -mtriple=spirv-unknown-vulkan1.3-pixel %s -o - | FileCheck %s +; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv-unknown-vulkan1.3-pixel %s -o - -filetype=obj | spirv-val --target-env vulkan1.3 %} + +; This test is the simplified llvm IR of flat-decoration.ps.hlsl. It exists +; because we can't do end to end testing but this gets us close & shows that +; the generated code will now pass the spirv validator by adding the Flat & +; Location decorators. + +; CHECK-DAG: OpEntryPoint Fragment %[[#entry:]] "main" +; CHECK-DAG: OpExecutionMode %[[#entry]] OriginUpperLeft + +; CHECK-DAG: OpDecorate %[[#INT_IN:]] Location 0 +; CHECK-DAG: OpDecorate %[[#INT_IN]] Flat +; CHECK-DAG: OpDecorate %[[#DOUBLE_IN:]] Location 1 +; CHECK-DAG: OpDecorate %[[#DOUBLE_IN]] Flat +; CHECK-DAG: OpDecorate %[[#FLOAT_IN:]] Location 2 +; CHECK-NOT: OpDecorate %[[#FLOAT_IN]] Flat + +@B0 = external hidden thread_local addrspace(7) externally_initialized constant i32, !spirv.Decorations !0 +@C0 = external hidden thread_local addrspace(7) externally_initialized constant double, !spirv.Decorations !3 +@A0 = external hidden thread_local addrspace(7) externally_initialized constant float, !spirv.Decorations !5 +@SV_Target = external hidden thread_local addrspace(8) global float, !spirv.Decorations !7 + +define void @main() #0 { +entry: + %0 = load i32, ptr addrspace(7) @B0, align 4 + %1 = load double, ptr addrspace(7) @C0, align 8 + %2 = load float, ptr addrspace(7) @A0, align 4 + %conv.i = sitofp i32 %0 to float + %conv.d = fptrunc double %1 to float + %add1 = fadd float %2, %conv.i + %add2 = fadd float %add1, %conv.d + store float %add2, ptr addrspace(8) @SV_Target, align 4 + ret void +} + +attributes #0 = { "hlsl.shader"="pixel" } + +; Integer input: Location 0 and Flat. +!0 = !{!1, !2} +!1 = !{i32 30, i32 0} +!2 = !{i32 14} +; Double input: Location 1 and Flat. +!3 = !{!4, !2} +!4 = !{i32 30, i32 1} +; Float input: Location 2 only (interpolated, no Flat). +!5 = !{!6} +!6 = !{i32 30, i32 2} +; SV_Target output: Location 0. +!7 = !{!8} +!8 = !{i32 30, i32 0} >From b37f670fc4a10b92b47313ee6c91033457e3fe58 Mon Sep 17 00:00:00 2001 From: Farzon Lotfi <[email protected]> Date: Fri, 17 Jul 2026 13:18:46 -0400 Subject: [PATCH 2/2] address pr concerns --- clang/lib/CodeGen/CGHLSLRuntime.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/clang/lib/CodeGen/CGHLSLRuntime.cpp b/clang/lib/CodeGen/CGHLSLRuntime.cpp index 283bfeb73df11..ba33ee27bb9b1 100644 --- a/clang/lib/CodeGen/CGHLSLRuntime.cpp +++ b/clang/lib/CodeGen/CGHLSLRuntime.cpp @@ -1111,12 +1111,13 @@ static llvm::Value *createSPIRVLocationLoad(IRBuilder<> &B, llvm::Module &M, LLVMContext &Ctx = GV->getContext(); SmallVector<Metadata *, 2> Decorations; Decorations.push_back( - MDNode::get(Ctx, {ConstantAsMetadata::get(B.getInt32(/* Location */ 30)), + MDNode::get(Ctx, {ConstantAsMetadata::get( + B.getInt32(/* SPIRV::Decoration::Location */ 30)), ConstantAsMetadata::get(B.getInt32(Location))})); if (NeedsFlat) Decorations.push_back( MDNode::get(Ctx, {ConstantAsMetadata::get( - B.getInt32(/* Spirv::Decoration::Flat */ 14))})); + B.getInt32(/* SPIRV::Decoration::Flat */ 14))})); GV->addMetadata("spirv.Decorations", *MDNode::get(Ctx, Decorations)); return B.CreateLoad(Ty, GV); _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
