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

Reply via email to