https://github.com/Keenuts updated 
https://github.com/llvm/llvm-project/pull/169479

From 8305fe297e15890e661efe87576de797e1e603fb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= <[email protected]>
Date: Wed, 19 Nov 2025 16:51:00 +0100
Subject: [PATCH 1/7] [HLSL][SPIR-V] Add support for SV_Target semantic

This PR adds the support for the SV_Target semantic and improved the
diagnostics when the stage is correct, but the direction is disallowed.
---
 .../clang/Basic/DiagnosticSemaKinds.td        |  2 +
 clang/include/clang/Sema/SemaHLSL.h           | 22 ++++++-
 clang/lib/CodeGen/CGHLSLRuntime.cpp           |  3 +
 clang/lib/Sema/SemaHLSL.cpp                   | 63 ++++++++++++++++---
 .../CodeGenHLSL/semantics/SV_Target.ps.hlsl   | 19 ++++++
 .../test/SemaHLSL/Semantics/position.ps.hlsl  |  2 +-
 .../SemaHLSL/Semantics/target.ps.input.hlsl   |  7 +++
 .../SemaHLSL/Semantics/target.vs.input.hlsl   |  8 +++
 .../SemaHLSL/Semantics/target.vs.output.hlsl  |  7 +++
 .../test/CodeGen/SPIRV/semantics/target.ps.ll | 33 ++++++++++
 10 files changed, 154 insertions(+), 12 deletions(-)
 create mode 100644 clang/test/CodeGenHLSL/semantics/SV_Target.ps.hlsl
 create mode 100644 clang/test/SemaHLSL/Semantics/target.ps.input.hlsl
 create mode 100644 clang/test/SemaHLSL/Semantics/target.vs.input.hlsl
 create mode 100644 clang/test/SemaHLSL/Semantics/target.vs.output.hlsl
 create mode 100644 llvm/test/CodeGen/SPIRV/semantics/target.ps.ll

diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td 
b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 53aa86a7dabde..1e024258b7c06 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -13231,6 +13231,8 @@ def err_hlsl_semantic_indexing_not_supported
 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 err_hlsl_semantic_unsupported_direction_for_stage
+    : Error<"semantic %0 is unsupported as %1 for stage %2">;
 
 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<LegacyConstantRegisterBinding>;
 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 15edb7e77a22b..2ac88f7e82700 100644
--- a/clang/include/clang/Sema/SemaHLSL.h
+++ b/clang/include/clang/Sema/SemaHLSL.h
@@ -134,9 +134,6 @@ class SemaHLSL : public SemaBase {
   void CheckEntryPoint(FunctionDecl *FD);
   bool CheckResourceBinOp(BinaryOperatorKind Opc, Expr *LHSExpr, Expr *RHSExpr,
                           SourceLocation Loc);
-  void DiagnoseAttrStageMismatch(
-      const Attr *A, llvm::Triple::EnvironmentType Stage,
-      std::initializer_list<llvm::Triple::EnvironmentType> AllowedStages);
 
   QualType handleVectorBinOpConversion(ExprResult &LHS, ExprResult &RHS,
                                        QualType LHSType, QualType RHSType,
@@ -244,6 +241,17 @@ class SemaHLSL : public SemaBase {
     std::optional<uint32_t> Index;
   };
 
+  enum IOType {
+    In = 0b01,
+    Out = 0b10,
+    InOut = 0b11,
+  };
+
+  struct SemanticStageInfo {
+    llvm::Triple::EnvironmentType Stage;
+    IOType Direction;
+  };
+
 private:
   void collectResourceBindingsOnVarDecl(VarDecl *D);
   void collectResourceBindingsOnUserRecordDecl(const VarDecl *VD,
@@ -269,6 +277,14 @@ class SemaHLSL : public SemaBase {
 
   void diagnoseAvailabilityViolations(TranslationUnitDecl *TU);
 
+  void diagnoseAttrStageMismatch(
+      const Attr *A, llvm::Triple::EnvironmentType Stage,
+      std::initializer_list<llvm::Triple::EnvironmentType> AllowedStages);
+
+  void diagnoseSemanticStageMismatch(
+      const Attr *A, llvm::Triple::EnvironmentType Stage, bool IsInput,
+      std::initializer_list<SemanticStageInfo> AllowedStages);
+
   uint32_t getNextImplicitBindingOrderID() {
     return ImplicitBindingNextOrderID++;
   }
diff --git a/clang/lib/CodeGen/CGHLSLRuntime.cpp 
b/clang/lib/CodeGen/CGHLSLRuntime.cpp
index f5c07fe2e33ff..e40a2296cbe5c 100644
--- a/clang/lib/CodeGen/CGHLSLRuntime.cpp
+++ b/clang/lib/CodeGen/CGHLSLRuntime.cpp
@@ -783,6 +783,9 @@ void CGHLSLRuntime::emitSystemSemanticStore(IRBuilder<> &B, 
llvm::Value *Source,
     }
   }
 
+  if (SemanticName == "SV_TARGET")
+    emitUserSemanticStore(B, Source, Decl, Semantic, Index);
+
   llvm_unreachable(
       "Store hasn't been implemented yet for this system semantic. FIXME");
 }
diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index ecab3946b58c7..40ba26ab54a7d 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -873,14 +873,14 @@ void SemaHLSL::CheckEntryPoint(FunctionDecl *FD) {
   case llvm::Triple::Miss:
   case llvm::Triple::Callable:
     if (const auto *NT = FD->getAttr<HLSLNumThreadsAttr>()) {
-      DiagnoseAttrStageMismatch(NT, ST,
+      diagnoseAttrStageMismatch(NT, ST,
                                 {llvm::Triple::Compute,
                                  llvm::Triple::Amplification,
                                  llvm::Triple::Mesh});
       FD->setInvalidDecl();
     }
     if (const auto *WS = FD->getAttr<HLSLWaveSizeAttr>()) {
-      DiagnoseAttrStageMismatch(WS, ST,
+      diagnoseAttrStageMismatch(WS, ST,
                                 {llvm::Triple::Compute,
                                  llvm::Triple::Amplification,
                                  llvm::Triple::Mesh});
@@ -954,7 +954,8 @@ void SemaHLSL::checkSemanticAnnotation(
       SemanticName == "SV_GROUPID") {
 
     if (ST != llvm::Triple::Compute)
-      DiagnoseAttrStageMismatch(SemanticAttr, ST, {llvm::Triple::Compute});
+      diagnoseSemanticStageMismatch(SemanticAttr, ST, IsInput,
+                                    {{llvm::Triple::Compute, IOType::In}});
 
     if (SemanticAttr->getSemanticIndex() != 0) {
       std::string PrettyName =
@@ -969,10 +970,15 @@ void SemaHLSL::checkSemanticAnnotation(
   if (SemanticName == "SV_POSITION") {
     // SV_Position can be an input or output in vertex shaders,
     // but only an input in pixel shaders.
-    if (ST == llvm::Triple::Vertex || (ST == llvm::Triple::Pixel && IsInput))
-      return;
-    DiagnoseAttrStageMismatch(SemanticAttr, ST,
-                              {llvm::Triple::Pixel, llvm::Triple::Vertex});
+    diagnoseSemanticStageMismatch(SemanticAttr, ST, IsInput,
+                                  {{llvm::Triple::Vertex, IOType::InOut},
+                                   {llvm::Triple::Pixel, IOType::In}});
+    return;
+  }
+
+  if (SemanticName == "SV_TARGET") {
+    diagnoseSemanticStageMismatch(SemanticAttr, ST, IsInput,
+                                  {{llvm::Triple::Pixel, IOType::Out}});
     return;
   }
 
@@ -982,7 +988,7 @@ void SemaHLSL::checkSemanticAnnotation(
     llvm_unreachable("Unknown SemanticAttr");
 }
 
-void SemaHLSL::DiagnoseAttrStageMismatch(
+void SemaHLSL::diagnoseAttrStageMismatch(
     const Attr *A, llvm::Triple::EnvironmentType Stage,
     std::initializer_list<llvm::Triple::EnvironmentType> AllowedStages) {
   SmallVector<StringRef, 8> StageStrings;
@@ -996,6 +1002,37 @@ void SemaHLSL::DiagnoseAttrStageMismatch(
       << (AllowedStages.size() != 1) << join(StageStrings, ", ");
 }
 
+void SemaHLSL::diagnoseSemanticStageMismatch(
+    const Attr *A, llvm::Triple::EnvironmentType Stage, bool IsInput,
+    std::initializer_list<SemanticStageInfo> Allowed) {
+
+  for (auto &Case : Allowed) {
+    if (Case.Stage != Stage)
+      continue;
+
+    if (IsInput && Case.Direction & IOType::In)
+      return;
+    if (!IsInput && Case.Direction & IOType::Out)
+      return;
+
+    Diag(A->getLoc(), diag::err_hlsl_semantic_unsupported_direction_for_stage)
+        << A->getAttrName() << (IsInput ? "input" : "output")
+        << llvm::Triple::getEnvironmentTypeName(Case.Stage);
+    return;
+  }
+
+  SmallVector<StringRef, 8> StageStrings;
+  llvm::transform(
+      Allowed, std::back_inserter(StageStrings), [](SemanticStageInfo Case) {
+        return StringRef(
+            HLSLShaderAttr::ConvertEnvironmentTypeToStr(Case.Stage));
+      });
+
+  Diag(A->getLoc(), diag::err_hlsl_attr_unsupported_in_stage)
+      << A->getAttrName() << llvm::Triple::getEnvironmentTypeName(Stage)
+      << (Allowed.size() != 1) << join(StageStrings, ", ");
+}
+
 template <CastKind Kind>
 static void castVector(Sema &S, ExprResult &E, QualType &Ty, unsigned Sz) {
   if (const auto *VTy = Ty->getAs<VectorType>())
@@ -1797,6 +1834,16 @@ void SemaHLSL::diagnoseSystemSemanticAttr(Decl *D, const 
ParsedAttr &AL,
     return;
   }
 
+  if (SemanticName == "SV_TARGET") {
+    const auto *VT = ValueType->getAs<VectorType>();
+    if (!ValueType->hasFloatingRepresentation() ||
+        (VT && VT->getNumElements() > 4))
+      Diag(AL.getLoc(), diag::err_hlsl_attr_invalid_type)
+          << AL << "float/float1/float2/float3/float4";
+    D->addAttr(createSemanticAttr<HLSLParsedSemanticAttr>(AL, Index));
+    return;
+  }
+
   Diag(AL.getLoc(), diag::err_hlsl_unknown_semantic) << AL;
 }
 
diff --git a/clang/test/CodeGenHLSL/semantics/SV_Target.ps.hlsl 
b/clang/test/CodeGenHLSL/semantics/SV_Target.ps.hlsl
new file mode 100644
index 0000000000000..4dc622a1eb6bb
--- /dev/null
+++ b/clang/test/CodeGenHLSL/semantics/SV_Target.ps.hlsl
@@ -0,0 +1,19 @@
+// 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
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-pixel -x hlsl -emit-llvm 
-finclude-default-header -disable-llvm-passes -o - %s | FileCheck %s 
--check-prefix=CHECK-DXIL
+
+// CHECK-SPIRV: @SV_Target0 = external hidden thread_local addrspace(8) global 
<4 x float>, !spirv.Decorations ![[#MD_2:]]
+
+// CHECK: define void @main() {{.*}} {
+float4 main(float4 p : SV_Position) : SV_Target {
+  // CHECK-SPIRV: %[[#R:]] = call spir_func <4 x float> @_Z4mainDv4_f(<4 x 
float> %[[#]])
+  // CHECK-SPIRV:            store <4 x float> %[[#R]], ptr addrspace(8) 
@SV_Target0, align 16
+
+  // CHECK-DXIL:    %[[#TMP:]] = call <4 x float> @_Z4mainDv4_f(<4 x float> 
%SV_Position0)
+  // CHECK-DXIL:                 call void @llvm.dx.store.output.v4f32(i32 4, 
i32 0, i32 0, i8 0, i32 poison, <4 x float> %[[#TMP]])
+  return p;
+}
+
+// CHECK-SPIRV-DAG: ![[#MD_2]] = !{![[#MD_3:]]}
+// CHECK-SPIRV-DAG: ![[#MD_3]] = !{i32 30, i32 0}
+//                                      |       `-> Location index
+//                                      `-> SPIR-V decoration 'Location'
diff --git a/clang/test/SemaHLSL/Semantics/position.ps.hlsl 
b/clang/test/SemaHLSL/Semantics/position.ps.hlsl
index 47d07887911d6..b96aa121d6aef 100644
--- a/clang/test/SemaHLSL/Semantics/position.ps.hlsl
+++ b/clang/test/SemaHLSL/Semantics/position.ps.hlsl
@@ -2,6 +2,6 @@
 // RUN: %clang_cc1 -triple spirv-pc-vulkan1.3-pixel -finclude-default-header 
-x hlsl -verify -o - %s
 
 float4 main(float4 a : A) : SV_Position {
-// expected-error@-1 {{attribute 'SV_Position' is unsupported in 'pixel' 
shaders, requires one of the following: pixel, vertex}}
+// expected-error@-1 {{semantic 'SV_Position' is unsupported as output for 
stage pixel}}
   return a;
 }
diff --git a/clang/test/SemaHLSL/Semantics/target.ps.input.hlsl 
b/clang/test/SemaHLSL/Semantics/target.ps.input.hlsl
new file mode 100644
index 0000000000000..6aabff60fac39
--- /dev/null
+++ b/clang/test/SemaHLSL/Semantics/target.ps.input.hlsl
@@ -0,0 +1,7 @@
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-pixel 
-finclude-default-header -x hlsl -verify -o - %s
+// RUN: %clang_cc1 -triple spirv-pc-vulkan1.3-pixel -finclude-default-header 
-x hlsl -verify -o - %s
+
+float4 main(float4 a : SV_Target) : A {
+// expected-error@-1 {{semantic 'SV_Target' is unsupported as input for stage 
pixel}}
+  return a;
+}
diff --git a/clang/test/SemaHLSL/Semantics/target.vs.input.hlsl 
b/clang/test/SemaHLSL/Semantics/target.vs.input.hlsl
new file mode 100644
index 0000000000000..add24732fc05a
--- /dev/null
+++ b/clang/test/SemaHLSL/Semantics/target.vs.input.hlsl
@@ -0,0 +1,8 @@
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-vertex 
-finclude-default-header -x hlsl -verify -o - %s
+// RUN: %clang_cc1 -triple spirv-pc-vulkan1.3-vertex -finclude-default-header 
-x hlsl -verify -o - %s
+
+float4 main(float4 a : SV_Target) : A {
+// expected-error@-1 {{attribute 'SV_Target' is unsupported in 'vertex' 
shaders, requires pixel}}
+  return a;
+}
+
diff --git a/clang/test/SemaHLSL/Semantics/target.vs.output.hlsl 
b/clang/test/SemaHLSL/Semantics/target.vs.output.hlsl
new file mode 100644
index 0000000000000..0481bcdad0177
--- /dev/null
+++ b/clang/test/SemaHLSL/Semantics/target.vs.output.hlsl
@@ -0,0 +1,7 @@
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-vertex 
-finclude-default-header -x hlsl -verify -o - %s
+// RUN: %clang_cc1 -triple spirv-pc-vulkan1.3-vertex -finclude-default-header 
-x hlsl -verify -o - %s
+
+float4 main(float4 a : SV_Position) : SV_Target {
+// expected-error@-1 {{attribute 'SV_Target' is unsupported in 'vertex' 
shaders, requires pixel}}
+  return a;
+}
diff --git a/llvm/test/CodeGen/SPIRV/semantics/target.ps.ll 
b/llvm/test/CodeGen/SPIRV/semantics/target.ps.ll
new file mode 100644
index 0000000000000..249ffc078f158
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/semantics/target.ps.ll
@@ -0,0 +1,33 @@
+; RUN: llc -O0 -verify-machineinstrs -mtriple=spirv-vulkan-unknown %s -o - | 
FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv-vulkan-unknown %s -o - 
-filetype=obj | spirv-val %}
+
+; CHECK-DAG:        OpDecorate %[[#INPUT:]] BuiltIn FragCoord
+; CHECK-DAG:        OpDecorate %[[#OUTPUT:]] Location 0
+
+; CHECK-DAG:   %[[#float:]] = OpTypeFloat 32
+; CHECK-DAG:      %[[#v4:]] = OpTypeVector %[[#float]] 4
+; CHECK-DAG:   %[[#ptr_i:]] = OpTypePointer Input %[[#v4]]
+; CHECK-DAG:   %[[#ptr_o:]] = OpTypePointer Output %[[#v4]]
+
+; CHECK-DAG:      %[[#INPUT]] = OpVariable %[[#ptr_i]] Input
+; CHECK-DAG:      %[[#OUTPUT]] = OpVariable %[[#ptr_o]] Output
+
+@SV_Position = external hidden thread_local addrspace(7) 
externally_initialized constant <4 x float>, !spirv.Decorations !0
+@SV_Target0 = external hidden thread_local addrspace(8) global <4 x float>, 
!spirv.Decorations !2
+
+define void @main() #1 {
+entry:
+  %0 = load <4 x float>, ptr addrspace(7) @SV_Position, align 16
+  store <4 x float> %0, ptr addrspace(8) @SV_Target0, align 16
+  ret void
+
+; CHECK: %[[#TMP:]] = OpLoad %[[#v4]] %[[#INPUT]] Aligned 16
+; CHECK:              OpStore %[[#OUTPUT]] %[[#TMP]] Aligned 16
+}
+
+!0 = !{!1}
+!1 = !{i32 11, i32 15}
+!2 = !{!3}
+!3 = !{i32 30, i32 0}
+
+

From 2ebbc2773e585708db5ebb062081e308ffba92e3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= <[email protected]>
Date: Mon, 24 Nov 2025 13:20:36 +0100
Subject: [PATCH 2/7] pr-feedback

---
 .../clang/Basic/DiagnosticSemaKinds.td        |  5 +++--
 clang/include/clang/Sema/SemaHLSL.h           |  2 +-
 clang/lib/CodeGen/CGHLSLRuntime.cpp           |  4 +++-
 clang/lib/Sema/SemaHLSL.cpp                   | 20 +++++++++++++++----
 .../test/SemaHLSL/Semantics/position.ps.hlsl  |  2 +-
 .../SemaHLSL/Semantics/target.ps.input.hlsl   |  2 +-
 6 files changed, 25 insertions(+), 10 deletions(-)

diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td 
b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 1e024258b7c06..48a17df95c1e6 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -13231,8 +13231,9 @@ def err_hlsl_semantic_indexing_not_supported
 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 err_hlsl_semantic_unsupported_direction_for_stage
-    : Error<"semantic %0 is unsupported as %1 for stage %2">;
+def err_hlsl_semantic_unsupported_iotype_for_stage
+    : Error<"semantic %0 is unsupported in %2 shaders as %1, requires one of "
+            "the following: %3">;
 
 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<LegacyConstantRegisterBinding>;
 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 2ac88f7e82700..fb18817f2177b 100644
--- a/clang/include/clang/Sema/SemaHLSL.h
+++ b/clang/include/clang/Sema/SemaHLSL.h
@@ -249,7 +249,7 @@ class SemaHLSL : public SemaBase {
 
   struct SemanticStageInfo {
     llvm::Triple::EnvironmentType Stage;
-    IOType Direction;
+    IOType AllowedIOTypesMask;
   };
 
 private:
diff --git a/clang/lib/CodeGen/CGHLSLRuntime.cpp 
b/clang/lib/CodeGen/CGHLSLRuntime.cpp
index e40a2296cbe5c..58a523b853647 100644
--- a/clang/lib/CodeGen/CGHLSLRuntime.cpp
+++ b/clang/lib/CodeGen/CGHLSLRuntime.cpp
@@ -783,8 +783,10 @@ void CGHLSLRuntime::emitSystemSemanticStore(IRBuilder<> 
&B, llvm::Value *Source,
     }
   }
 
-  if (SemanticName == "SV_TARGET")
+  if (SemanticName == "SV_TARGET") {
     emitUserSemanticStore(B, Source, Decl, Semantic, Index);
+    return;
+  }
 
   llvm_unreachable(
       "Store hasn't been implemented yet for this system semantic. FIXME");
diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index 40ba26ab54a7d..62153b8e8794a 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -1010,14 +1010,26 @@ void SemaHLSL::diagnoseSemanticStageMismatch(
     if (Case.Stage != Stage)
       continue;
 
-    if (IsInput && Case.Direction & IOType::In)
+    if (IsInput && Case.AllowedIOTypesMask & IOType::In)
       return;
-    if (!IsInput && Case.Direction & IOType::Out)
+    if (!IsInput && Case.AllowedIOTypesMask & IOType::Out)
       return;
 
-    Diag(A->getLoc(), diag::err_hlsl_semantic_unsupported_direction_for_stage)
+    SmallVector<std::string, 8> ValidCases;
+    llvm::transform(
+        Allowed, std::back_inserter(ValidCases), [](SemanticStageInfo Case) {
+          std::string Type =
+              Case.AllowedIOTypesMask == IOType::InOut
+                  ? " inout"
+                  : (Case.AllowedIOTypesMask & IOType::In ? " in" : " out");
+          return std::string(
+                     HLSLShaderAttr::ConvertEnvironmentTypeToStr(Case.Stage)) +
+                 Type;
+        });
+    Diag(A->getLoc(), diag::err_hlsl_semantic_unsupported_iotype_for_stage)
         << A->getAttrName() << (IsInput ? "input" : "output")
-        << llvm::Triple::getEnvironmentTypeName(Case.Stage);
+        << llvm::Triple::getEnvironmentTypeName(Case.Stage)
+        << join(ValidCases, ", ");
     return;
   }
 
diff --git a/clang/test/SemaHLSL/Semantics/position.ps.hlsl 
b/clang/test/SemaHLSL/Semantics/position.ps.hlsl
index b96aa121d6aef..af237d8d69ef0 100644
--- a/clang/test/SemaHLSL/Semantics/position.ps.hlsl
+++ b/clang/test/SemaHLSL/Semantics/position.ps.hlsl
@@ -2,6 +2,6 @@
 // RUN: %clang_cc1 -triple spirv-pc-vulkan1.3-pixel -finclude-default-header 
-x hlsl -verify -o - %s
 
 float4 main(float4 a : A) : SV_Position {
-// expected-error@-1 {{semantic 'SV_Position' is unsupported as output for 
stage pixel}}
+// expected-error@-1 {{semantic 'SV_Position' is unsupported in pixel shaders 
as output, requires one of the following: vertex inout, pixel in}}
   return a;
 }
diff --git a/clang/test/SemaHLSL/Semantics/target.ps.input.hlsl 
b/clang/test/SemaHLSL/Semantics/target.ps.input.hlsl
index 6aabff60fac39..a77b46c0e9f1a 100644
--- a/clang/test/SemaHLSL/Semantics/target.ps.input.hlsl
+++ b/clang/test/SemaHLSL/Semantics/target.ps.input.hlsl
@@ -2,6 +2,6 @@
 // RUN: %clang_cc1 -triple spirv-pc-vulkan1.3-pixel -finclude-default-header 
-x hlsl -verify -o - %s
 
 float4 main(float4 a : SV_Target) : A {
-// expected-error@-1 {{semantic 'SV_Target' is unsupported as input for stage 
pixel}}
+// expected-error@-1 {{semantic 'SV_Target' is unsupported in pixel shaders as 
input, requires one of the following: pixel out}}
   return a;
 }

From 9c4c26fc1ab1fb1a1a8767a25a03aea21a4ebc82 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= <[email protected]>
Date: Mon, 24 Nov 2025 15:27:22 +0100
Subject: [PATCH 3/7] change language

---
 clang/lib/Sema/SemaHLSL.cpp                    | 11 ++++++-----
 clang/test/SemaHLSL/Semantics/position.ps.hlsl |  2 +-
 2 files changed, 7 insertions(+), 6 deletions(-)

diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index 62153b8e8794a..0cea7af6024d4 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -1018,13 +1018,14 @@ void SemaHLSL::diagnoseSemanticStageMismatch(
     SmallVector<std::string, 8> ValidCases;
     llvm::transform(
         Allowed, std::back_inserter(ValidCases), [](SemanticStageInfo Case) {
-          std::string Type =
-              Case.AllowedIOTypesMask == IOType::InOut
-                  ? " inout"
-                  : (Case.AllowedIOTypesMask & IOType::In ? " in" : " out");
+          SmallVector<std::string, 2> ValidType;
+          if (Case.AllowedIOTypesMask & IOType::In)
+            ValidType.push_back("input");
+          if (Case.AllowedIOTypesMask & IOType::Out)
+            ValidType.push_back("output");
           return std::string(
                      HLSLShaderAttr::ConvertEnvironmentTypeToStr(Case.Stage)) +
-                 Type;
+                 " " + join(ValidType, "/");
         });
     Diag(A->getLoc(), diag::err_hlsl_semantic_unsupported_iotype_for_stage)
         << A->getAttrName() << (IsInput ? "input" : "output")
diff --git a/clang/test/SemaHLSL/Semantics/position.ps.hlsl 
b/clang/test/SemaHLSL/Semantics/position.ps.hlsl
index af237d8d69ef0..d0fe19d1a5407 100644
--- a/clang/test/SemaHLSL/Semantics/position.ps.hlsl
+++ b/clang/test/SemaHLSL/Semantics/position.ps.hlsl
@@ -2,6 +2,6 @@
 // RUN: %clang_cc1 -triple spirv-pc-vulkan1.3-pixel -finclude-default-header 
-x hlsl -verify -o - %s
 
 float4 main(float4 a : A) : SV_Position {
-// expected-error@-1 {{semantic 'SV_Position' is unsupported in pixel shaders 
as output, requires one of the following: vertex inout, pixel in}}
+// expected-error@-1 {{semantic 'SV_Position' is unsupported in pixel shaders 
as output, requires one of the following: vertex input/output, pixel input}}
   return a;
 }

From 24b9a4ebaa14dc6a4d06f4cda8810f2223651fda Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= <[email protected]>
Date: Wed, 12 Nov 2025 16:58:45 +0100
Subject: [PATCH 4/7] [HLSL][SPIR-V] Implement vk::location for inputs

This commit adds the support for vk::location attribute, focusing
on input semantics.
---
 clang/include/clang/Basic/Attr.td             |  8 ++++
 clang/include/clang/Basic/AttrDocs.td         | 12 ++++++
 .../clang/Basic/DiagnosticSemaKinds.td        |  3 ++
 clang/include/clang/Sema/SemaHLSL.h           |  3 ++
 clang/lib/CodeGen/CGHLSLRuntime.cpp           | 20 +++++----
 clang/lib/CodeGen/CGHLSLRuntime.h             |  2 +
 clang/lib/Sema/SemaDeclAttr.cpp               |  3 ++
 clang/lib/Sema/SemaHLSL.cpp                   | 41 +++++++++++++++++++
 ...antic.explicit-location-output-struct.hlsl | 37 +++++++++++++++++
 .../semantics/semantic.explicit-location.hlsl | 19 +++++++++
 .../semantic.explicit-mix-builtin.hlsl        | 39 ++++++++++++++++++
 ...a-attribute-supported-attributes-list.test |  1 +
 .../semantic.explicit-mix-builtin-vs.hlsl     | 16 ++++++++
 .../semantic.explicit-mix-location-2.hlsl     | 15 +++++++
 .../semantic.explicit-mix-location.hlsl       | 15 +++++++
 15 files changed, 227 insertions(+), 7 deletions(-)
 create mode 100644 
clang/test/CodeGenHLSL/semantics/semantic.explicit-location-output-struct.hlsl
 create mode 100644 
clang/test/CodeGenHLSL/semantics/semantic.explicit-location.hlsl
 create mode 100644 
clang/test/CodeGenHLSL/semantics/semantic.explicit-mix-builtin.hlsl
 create mode 100644 
clang/test/SemaHLSL/Semantics/semantic.explicit-mix-builtin-vs.hlsl
 create mode 100644 
clang/test/SemaHLSL/Semantics/semantic.explicit-mix-location-2.hlsl
 create mode 100644 
clang/test/SemaHLSL/Semantics/semantic.explicit-mix-location.hlsl

diff --git a/clang/include/clang/Basic/Attr.td 
b/clang/include/clang/Basic/Attr.td
index 8e5f7ef0bb82d..9e7dc200bc6cf 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -5172,6 +5172,14 @@ def HLSLVkConstantId : InheritableAttr {
   let Documentation = [VkConstantIdDocs];
 }
 
+def HLSLVkLocation : HLSLAnnotationAttr {
+  let Spellings = [CXX11<"vk", "location">];
+  let Args = [IntArgument<"Location">];
+  let Subjects = SubjectList<[ParmVar, Field, Function], ErrorDiag>;
+  let LangOpts = [HLSL];
+  let Documentation = [HLSLVkLocationDocs];
+}
+
 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 c1b1510f363d4..fa365da3ed9aa 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -8981,6 +8981,18 @@ The descriptor set is optional and defaults to 0 if not 
provided.
   }];
 }
 
+def HLSLVkLocationDocs : Documentation {
+  let Category = DocCatVariable;
+  let Content = [{
+Attribute used for specifying the location number for the stage input/output
+variables. Allowed on function parameters, function returns, and struct
+fields. This parameter has no effect when used outside of an entrypoint
+parameter/parameter field/return value.
+
+This attribute maps to the 'Location' SPIR-V decoration.
+  }];
+}
+
 def WebAssemblyFuncrefDocs : Documentation {
   let Category = DocCatType;
   let Content = [{
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td 
b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 48a17df95c1e6..04812bd78d9b4 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -13234,6 +13234,9 @@ def err_hlsl_semantic_index_overlap : Error<"semantic 
index overlap %0">;
 def err_hlsl_semantic_unsupported_iotype_for_stage
     : Error<"semantic %0 is unsupported in %2 shaders as %1, requires one of "
             "the following: %3">;
+def err_hlsl_semantic_partial_explicit_indexing
+    : Error<"partial explicit stage input location assignment via "
+            "vk::location(X) unsupported">;
 
 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<LegacyConstantRegisterBinding>;
 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 fb18817f2177b..cac89f6e51cae 100644
--- a/clang/include/clang/Sema/SemaHLSL.h
+++ b/clang/include/clang/Sema/SemaHLSL.h
@@ -168,6 +168,7 @@ class SemaHLSL : public SemaBase {
   void handleWaveSizeAttr(Decl *D, const ParsedAttr &AL);
   void handleVkConstantIdAttr(Decl *D, const ParsedAttr &AL);
   void handleVkBindingAttr(Decl *D, const ParsedAttr &AL);
+  void handleVkLocationAttr(Decl *D, const ParsedAttr &AL);
   void handlePackOffsetAttr(Decl *D, const ParsedAttr &AL);
   void handleShaderAttr(Decl *D, const ParsedAttr &AL);
   void handleResourceBindingAttr(Decl *D, const ParsedAttr &AL);
@@ -240,6 +241,8 @@ class SemaHLSL : public SemaBase {
     HLSLParsedSemanticAttr *Semantic;
     std::optional<uint32_t> Index;
   };
+  std::optional<bool> InputUsesExplicitVkLocations = std::nullopt;
+  std::optional<bool> OutputUsesExplicitVkLocations = std::nullopt;
 
   enum IOType {
     In = 0b01,
diff --git a/clang/lib/CodeGen/CGHLSLRuntime.cpp 
b/clang/lib/CodeGen/CGHLSLRuntime.cpp
index 58a523b853647..913d80b7e5c25 100644
--- a/clang/lib/CodeGen/CGHLSLRuntime.cpp
+++ b/clang/lib/CodeGen/CGHLSLRuntime.cpp
@@ -582,20 +582,22 @@ static llvm::Value *createSPIRVLocationLoad(IRBuilder<> 
&B, llvm::Module &M,
   return B.CreateLoad(Ty, GV);
 }
 
-llvm::Value *
-CGHLSLRuntime::emitSPIRVUserSemanticLoad(llvm::IRBuilder<> &B, llvm::Type 
*Type,
-                                         HLSLAppliedSemanticAttr *Semantic,
-                                         std::optional<unsigned> Index) {
+llvm::Value *CGHLSLRuntime::emitSPIRVUserSemanticLoad(
+    llvm::IRBuilder<> &B, 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)));
 
   unsigned Location = SPIRVLastAssignedInputSemanticLocation;
+  if (auto *L = Decl->getAttr<HLSLVkLocationAttr>())
+    Location = L->getLocation();
 
   // DXC completely ignores the semantic/index pair. Location are assigned from
   // the first semantic to the last.
   llvm::ArrayType *AT = dyn_cast<llvm::ArrayType>(Type);
   unsigned ElementCount = AT ? AT->getNumElements() : 1;
   SPIRVLastAssignedInputSemanticLocation += ElementCount;
+
   return createSPIRVLocationLoad(B, CGM.getModule(), Type, Location,
                                  VariableName.str());
 }
@@ -616,10 +618,14 @@ static void createSPIRVLocationStore(IRBuilder<> &B, 
llvm::Module &M,
 
 void CGHLSLRuntime::emitSPIRVUserSemanticStore(
     llvm::IRBuilder<> &B, llvm::Value *Source,
-    HLSLAppliedSemanticAttr *Semantic, std::optional<unsigned> Index) {
+    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)));
+
   unsigned Location = SPIRVLastAssignedOutputSemanticLocation;
+  if (auto *L = Decl->getAttr<HLSLVkLocationAttr>())
+    Location = L->getLocation();
 
   // DXC completely ignores the semantic/index pair. Location are assigned from
   // the first semantic to the last.
@@ -671,7 +677,7 @@ llvm::Value *CGHLSLRuntime::emitUserSemanticLoad(
     IRBuilder<> &B, llvm::Type *Type, const clang::DeclaratorDecl *Decl,
     HLSLAppliedSemanticAttr *Semantic, std::optional<unsigned> Index) {
   if (CGM.getTarget().getTriple().isSPIRV())
-    return emitSPIRVUserSemanticLoad(B, Type, Semantic, Index);
+    return emitSPIRVUserSemanticLoad(B, Type, Decl, Semantic, Index);
 
   if (CGM.getTarget().getTriple().isDXIL())
     return emitDXILUserSemanticLoad(B, Type, Semantic, Index);
@@ -684,7 +690,7 @@ void CGHLSLRuntime::emitUserSemanticStore(IRBuilder<> &B, 
llvm::Value *Source,
                                           HLSLAppliedSemanticAttr *Semantic,
                                           std::optional<unsigned> Index) {
   if (CGM.getTarget().getTriple().isSPIRV())
-    return emitSPIRVUserSemanticStore(B, Source, Semantic, Index);
+    return emitSPIRVUserSemanticStore(B, Source, Decl, Semantic, Index);
 
   if (CGM.getTarget().getTriple().isDXIL())
     return emitDXILUserSemanticStore(B, Source, Semantic, Index);
diff --git a/clang/lib/CodeGen/CGHLSLRuntime.h 
b/clang/lib/CodeGen/CGHLSLRuntime.h
index c883282a8d9c8..d057add2db222 100644
--- a/clang/lib/CodeGen/CGHLSLRuntime.h
+++ b/clang/lib/CodeGen/CGHLSLRuntime.h
@@ -278,6 +278,7 @@ class CGHLSLRuntime {
                                    HLSLResourceBindingAttr *RBA);
 
   llvm::Value *emitSPIRVUserSemanticLoad(llvm::IRBuilder<> &B, llvm::Type 
*Type,
+                                         const clang::DeclaratorDecl *Decl,
                                          HLSLAppliedSemanticAttr *Semantic,
                                          std::optional<unsigned> Index);
   llvm::Value *emitDXILUserSemanticLoad(llvm::IRBuilder<> &B, llvm::Type *Type,
@@ -289,6 +290,7 @@ class CGHLSLRuntime {
                                     std::optional<unsigned> Index);
 
   void emitSPIRVUserSemanticStore(llvm::IRBuilder<> &B, llvm::Value *Source,
+                                  const clang::DeclaratorDecl *Decl,
                                   HLSLAppliedSemanticAttr *Semantic,
                                   std::optional<unsigned> Index);
   void emitDXILUserSemanticStore(llvm::IRBuilder<> &B, llvm::Value *Source,
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index e3af5023c74d0..c9d1ee76a2e52 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -7703,6 +7703,9 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, 
const ParsedAttr &AL,
   case ParsedAttr::AT_HLSLUnparsedSemantic:
     S.HLSL().handleSemanticAttr(D, AL);
     break;
+  case ParsedAttr::AT_HLSLVkLocation:
+    S.HLSL().handleVkLocationAttr(D, AL);
+    break;
 
   case ParsedAttr::AT_AbiTag:
     handleAbiTagAttr(S, D, AL);
diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index 0cea7af6024d4..2a69703d0d1e9 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -771,6 +771,22 @@ void SemaHLSL::ActOnTopLevelFunction(FunctionDecl *FD) {
   }
 }
 
+static bool isPipelineBuiltin(const ASTContext &AstContext, FunctionDecl *FD,
+                              HLSLAppliedSemanticAttr *Semantic) {
+  if (AstContext.getTargetInfo().getTriple().getOS() != llvm::Triple::Vulkan)
+    return false;
+
+  const auto *ShaderAttr = FD->getAttr<HLSLShaderAttr>();
+  assert(ShaderAttr && "Entry point has no shader attribute");
+  llvm::Triple::EnvironmentType ST = ShaderAttr->getType();
+  auto SemanticName = Semantic->getSemanticName().upper();
+
+  if (ST == llvm::Triple::Pixel && SemanticName == "SV_POSITION")
+    return true;
+
+  return false;
+}
+
 bool SemaHLSL::determineActiveSemanticOnScalar(FunctionDecl *FD,
                                                DeclaratorDecl *OutputDecl,
                                                DeclaratorDecl *D,
@@ -800,6 +816,22 @@ bool 
SemaHLSL::determineActiveSemanticOnScalar(FunctionDecl *FD,
 
   unsigned Location = ActiveSemantic.Index.value_or(0);
 
+  if (!isPipelineBuiltin(getASTContext(), FD, A)) {
+    bool HasVkLocation = false;
+    if (auto *A = D->getAttr<HLSLVkLocationAttr>()) {
+      HasVkLocation = true;
+      Location = A->getLocation();
+    }
+
+    auto &UsesExplicitVkLocations =
+        IsInput ? InputUsesExplicitVkLocations : OutputUsesExplicitVkLocations;
+    if (UsesExplicitVkLocations.value_or(HasVkLocation) != HasVkLocation) {
+      Diag(D->getLocation(), 
diag::err_hlsl_semantic_partial_explicit_indexing);
+      return false;
+    }
+    UsesExplicitVkLocations = HasVkLocation;
+  }
+
   const ConstantArrayType *AT = dyn_cast<ConstantArrayType>(D->getType());
   unsigned ElementCount = AT ? AT->getZExtSize() : 1;
   ActiveSemantic.Index = Location + ElementCount;
@@ -1757,6 +1789,15 @@ void SemaHLSL::handleVkBindingAttr(Decl *D, const 
ParsedAttr &AL) {
                  HLSLVkBindingAttr(getASTContext(), AL, Binding, Set));
 }
 
+void SemaHLSL::handleVkLocationAttr(Decl *D, const ParsedAttr &AL) {
+  uint32_t Location;
+  if (!SemaRef.checkUInt32Argument(AL, AL.getArgAsExpr(0), Location))
+    return;
+
+  D->addAttr(::new (getASTContext())
+                 HLSLVkLocationAttr(getASTContext(), AL, Location));
+}
+
 bool SemaHLSL::diagnoseInputIDType(QualType T, const ParsedAttr &AL) {
   const auto *VT = T->getAs<VectorType>();
 
diff --git 
a/clang/test/CodeGenHLSL/semantics/semantic.explicit-location-output-struct.hlsl
 
b/clang/test/CodeGenHLSL/semantics/semantic.explicit-location-output-struct.hlsl
new file mode 100644
index 0000000000000..c5d86637fb4ea
--- /dev/null
+++ 
b/clang/test/CodeGenHLSL/semantics/semantic.explicit-location-output-struct.hlsl
@@ -0,0 +1,37 @@
+// RUN: %clang_cc1 -triple spirv-pc-vulkan1.3-pixel -x hlsl -emit-llvm 
-finclude-default-header -disable-llvm-passes -o - %s | FileCheck %s 
--check-prefixes=CHECK,CHECK-SPIRV
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-pixel -x hlsl -emit-llvm 
-finclude-default-header -disable-llvm-passes -o - %s | FileCheck %s 
--check-prefixes=CHECK,CHECK-DXIL
+
+// CHECK-SPIRV: @SV_Position = external hidden thread_local addrspace(7) 
externally_initialized constant <4 x float>, !spirv.Decorations ![[#MD_0:]]
+// CHECK-SPIRV: @SV_Target0 = external hidden thread_local addrspace(8) global 
<4 x float>, !spirv.Decorations ![[#MD_2:]]
+
+struct Output {
+  [[vk::location(2)]] float4 field : SV_Target;
+};
+
+// CHECK: define void @main() {{.*}} {
+Output main(float4 p : SV_Position) {
+  // CHECK:   %[[#OUT:]] = alloca %struct.Output, align 16
+
+  // CHECK-SPIRV:    %[[#IN:]] = load <4 x float>, ptr addrspace(7) 
@SV_Position, align 16
+  // CHECK-SPIRV:                call spir_func void @_Z4mainDv4_f(ptr 
%[[#OUT]], <4 x float> %[[#IN]])
+
+  // CHECK-DXIL:                 call void @_Z4mainDv4_f(ptr %[[#OUT]], <4 x 
float> %SV_Position0)
+
+  // CHECK:   %[[#TMP:]] = load %struct.Output, ptr %[[#OUT]], align 16
+  // CHECK: %[[#FIELD:]] = extractvalue %struct.Output %[[#TMP]], 0
+
+  // CHECK-SPIRV:                store <4 x float> %[[#FIELD]], ptr 
addrspace(8) @SV_Target0, align 16
+  // CHECK-DXIL:                 call void @llvm.dx.store.output.v4f32(i32 4, 
i32 0, i32 0, i8 0, i32 poison, <4 x float> %[[#FIELD]])
+  Output o;
+  o.field = p;
+  return o;
+}
+
+// CHECK-SPIRV-DAG: ![[#MD_0]] = !{![[#MD_1:]]}
+// CHECK-SPIRV-DAG: ![[#MD_1]] = !{i32 11, i32 15}
+//                                      |       `-> BuiltIn 'FragCoord'
+//                                      `-> SPIR-V decoration 'BuiltIn'
+// CHECK-SPIRV-DAG: ![[#MD_2]] = !{![[#MD_3:]]}
+// CHECK-SPIRV-DAG: ![[#MD_3]] = !{i32 30, i32 2}
+//                                      |       `-> Location index
+//                                      `-> SPIR-V decoration 'Location'
diff --git a/clang/test/CodeGenHLSL/semantics/semantic.explicit-location.hlsl 
b/clang/test/CodeGenHLSL/semantics/semantic.explicit-location.hlsl
new file mode 100644
index 0000000000000..41e28bf1259d6
--- /dev/null
+++ b/clang/test/CodeGenHLSL/semantics/semantic.explicit-location.hlsl
@@ -0,0 +1,19 @@
+// 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
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-pixel -x hlsl -emit-llvm 
-finclude-default-header -disable-llvm-passes -o - %s | FileCheck %s 
--check-prefix=CHECK-DXIL
+
+// CHECK-SPIRV: @SV_Target0 = external hidden thread_local addrspace(8) global 
<4 x float>, !spirv.Decorations ![[#MD_2:]]
+
+// CHECK: define void @main() {{.*}} {
+[[vk::location(2)]] float4 main(float4 p : SV_Position) : SV_Target {
+  // CHECK-SPIRV: %[[#R:]] = call spir_func <4 x float> @_Z4mainDv4_f(<4 x 
float> %[[#]])
+  // CHECK-SPIRV:            store <4 x float> %[[#R]], ptr addrspace(8) 
@SV_Target0, align 16
+
+  // CHECK-DXIL:    %[[#TMP:]] = call <4 x float> @_Z4mainDv4_f(<4 x float> 
%SV_Position0)
+  // CHECK-DXIL:                 call void @llvm.dx.store.output.v4f32(i32 4, 
i32 0, i32 0, i8 0, i32 poison, <4 x float> %[[#TMP]])
+  return p;
+}
+
+// CHECK-SPIRV-DAG: ![[#MD_2]] = !{![[#MD_3:]]}
+// CHECK-SPIRV-DAG: ![[#MD_3]] = !{i32 30, i32 2}
+//                                      |       `-> Location index
+//                                      `-> SPIR-V decoration 'Location'
diff --git 
a/clang/test/CodeGenHLSL/semantics/semantic.explicit-mix-builtin.hlsl 
b/clang/test/CodeGenHLSL/semantics/semantic.explicit-mix-builtin.hlsl
new file mode 100644
index 0000000000000..bc2ecd926dd51
--- /dev/null
+++ b/clang/test/CodeGenHLSL/semantics/semantic.explicit-mix-builtin.hlsl
@@ -0,0 +1,39 @@
+// RUN: %clang_cc1 -triple spirv-linux-vulkan-pixel -x hlsl -emit-llvm 
-finclude-default-header -disable-llvm-passes -o - %s | FileCheck %s 
--check-prefixes=CHECK,CHECK-SPIRV
+
+// The following code is allowed because the `SV_Position` semantic is here
+// translated into a SPIR-V builtin. Meaning there is no implicit `Location`
+// assignment.
+
+struct S2 {
+  float4 a;
+  float4 b;
+};
+
+struct S1 {
+  float4 position : SV_Position;
+  [[vk::location(3)]] float4 color0 : COLOR0;
+};
+
+// CHECK-SPIRV: @SV_Position = external hidden thread_local addrspace(7) 
externally_initialized constant <4 x float>, !spirv.Decorations ![[#MD_0:]]
+// CHECK-SPIRV: @COLOR0 = external hidden thread_local addrspace(7) 
externally_initialized constant <4 x float>, !spirv.Decorations ![[#MD_2:]]
+// CHECK-SPIRV: @SV_Target0 = external hidden thread_local addrspace(8) global 
<4 x float>, !spirv.Decorations ![[#MD_4:]]
+
+[shader("pixel")]
+float4 main(S1 p) : SV_Target {
+  return p.position + p.color0;
+}
+// CHECK-SPIRV:    %[[#SV_POS:]] = load <4 x float>, ptr addrspace(7) 
@SV_Position, align 16
+// CHECK:            %[[#TMP1:]] = insertvalue %struct.S1 poison, <4 x float> 
%[[#SV_POS]], 0
+// CHECK-SPIRV:        %[[#A0:]] = load <4 x float>, ptr addrspace(7) @COLOR0, 
align 16
+// CHECK:            %[[#TMP2:]] = insertvalue %struct.S1 %[[#TMP1]], <4 x 
float> %[[#A0]], 1
+// CHECK:               %[[#P:]] = alloca %struct.S1, align 16
+// CHECK:                          store %struct.S1 %[[#TMP2]], ptr %[[#P]], 
align 16
+// CHECK-SPIRV:         %[[#R:]] = call spir_func <4 x float> @_Z4main2S1(ptr 
%[[#P]])
+// CHECK-SPIRV:                    store <4 x float> %[[#R]], ptr addrspace(8) 
@SV_Target0, align 16
+
+// CHECK-SPIRV: ![[#MD_0]] = !{![[#MD_1:]]}
+// CHECK-SPIRV: ![[#MD_1]] = !{i32 11, i32 15}
+// CHECK-SPIRV: ![[#MD_2]] = !{![[#MD_3:]]}
+// CHECK-SPIRV: ![[#MD_3]] = !{i32 30, i32 3}
+// CHECK-SPIRV: ![[#MD_4]] = !{![[#MD_5:]]}
+// CHECK-SPIRV: ![[#MD_5]] = !{i32 30, i32 0}
diff --git a/clang/test/Misc/pragma-attribute-supported-attributes-list.test 
b/clang/test/Misc/pragma-attribute-supported-attributes-list.test
index 747eb17446c87..1e1d4a356f515 100644
--- a/clang/test/Misc/pragma-attribute-supported-attributes-list.test
+++ b/clang/test/Misc/pragma-attribute-supported-attributes-list.test
@@ -89,6 +89,7 @@
 // CHECK-NEXT: FunctionReturnThunks (SubjectMatchRule_function)
 // CHECK-NEXT: GNUInline (SubjectMatchRule_function)
 // CHECK-NEXT: HIPManaged (SubjectMatchRule_variable)
+// CHECK-NEXT: HLSLVkLocation (SubjectMatchRule_variable_is_parameter, 
SubjectMatchRule_field, SubjectMatchRule_function)
 // CHECK-NEXT: Hot (SubjectMatchRule_function)
 // CHECK-NEXT: HybridPatchable (SubjectMatchRule_function)
 // CHECK-NEXT: IBAction (SubjectMatchRule_objc_method_is_instance)
diff --git 
a/clang/test/SemaHLSL/Semantics/semantic.explicit-mix-builtin-vs.hlsl 
b/clang/test/SemaHLSL/Semantics/semantic.explicit-mix-builtin-vs.hlsl
new file mode 100644
index 0000000000000..3abd1cb65ffc4
--- /dev/null
+++ b/clang/test/SemaHLSL/Semantics/semantic.explicit-mix-builtin-vs.hlsl
@@ -0,0 +1,16 @@
+// RUN: %clang_cc1 -triple spirv-linux-vulkan-vertex -x hlsl -emit-llvm 
-finclude-default-header -disable-llvm-passes -o - %s -verify 
-verify-ignore-unexpected=note
+
+// This is almost the same as semantic.explicit-mix-builtin.hlsl, except this
+// time we build a vertex shader. This means the SV_Position semantic is not
+// a BuiltIn anymore, but a Location decorated variable. This means we mix
+// implicit and explicit location assignment.
+struct S1 {
+  float4 position : SV_Position;
+  [[vk::location(3)]] float4 color : A;
+  // expected-error@-1 {{partial explicit stage input location assignment via 
vk::location(X) unsupported}}
+};
+
+[shader("vertex")]
+float4 main1(S1 p) : A {
+  return p.position + p.color;
+}
diff --git 
a/clang/test/SemaHLSL/Semantics/semantic.explicit-mix-location-2.hlsl 
b/clang/test/SemaHLSL/Semantics/semantic.explicit-mix-location-2.hlsl
new file mode 100644
index 0000000000000..3de85863883ad
--- /dev/null
+++ b/clang/test/SemaHLSL/Semantics/semantic.explicit-mix-location-2.hlsl
@@ -0,0 +1,15 @@
+// RUN: %clang --driver-mode=dxc %s -T ps_6_8 -E main1 -O3 -spirv -Xclang 
-verify -Xclang -verify-ignore-unexpected=note
+
+// The following code is not legal: both semantics A and B will be lowered
+// into a Location decoration. And mixing implicit and explicit Location
+// assignment is not supported.
+struct S1 {
+  [[vk::location(3)]] float4 color : B;
+  float4 position : A;
+  // expected-error@-1 {{partial explicit stage input location assignment via 
vk::location(X) unsupported}}
+};
+
+[shader("pixel")]
+float4 main1(S1 p) : SV_Target {
+  return p.position + p.color;
+}
diff --git a/clang/test/SemaHLSL/Semantics/semantic.explicit-mix-location.hlsl 
b/clang/test/SemaHLSL/Semantics/semantic.explicit-mix-location.hlsl
new file mode 100644
index 0000000000000..8f5b6e48b48e9
--- /dev/null
+++ b/clang/test/SemaHLSL/Semantics/semantic.explicit-mix-location.hlsl
@@ -0,0 +1,15 @@
+// RUN: %clang --driver-mode=dxc %s -T ps_6_8 -E main1 -O3 -spirv -Xclang 
-verify -Xclang -verify-ignore-unexpected=note
+
+// The following code is not legal: both semantics A and B will be lowered
+// into a Location decoration. And mixing implicit and explicit Location
+// assignment is not supported.
+struct S1 {
+  float4 position : A;
+  [[vk::location(3)]] float4 color : B;
+  // expected-error@-1 {{partial explicit stage input location assignment via 
vk::location(X) unsupported}}
+};
+
+[shader("pixel")]
+float4 main1(S1 p) : SV_Target {
+  return p.position + p.color;
+}

From d0cfafea13b3d6968ad7b2e98477b89481fe0e09 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= <[email protected]>
Date: Tue, 25 Nov 2025 17:47:56 +0100
Subject: [PATCH 5/7] pr-feedback

---
 clang/lib/Sema/SemaHLSL.cpp                   | 16 +++++++---
 .../semantic.explicit-mix-builtin.vs.hlsl     | 31 +++++++++++++++++++
 2 files changed, 42 insertions(+), 5 deletions(-)
 create mode 100644 
clang/test/CodeGenHLSL/semantics/semantic.explicit-mix-builtin.vs.hlsl

diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index 2a69703d0d1e9..1b55d6c1f2e55 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -771,8 +771,9 @@ void SemaHLSL::ActOnTopLevelFunction(FunctionDecl *FD) {
   }
 }
 
-static bool isPipelineBuiltin(const ASTContext &AstContext, FunctionDecl *FD,
-                              HLSLAppliedSemanticAttr *Semantic) {
+static bool isVkPipelineBuiltin(const ASTContext &AstContext, FunctionDecl *FD,
+                                HLSLAppliedSemanticAttr *Semantic,
+                                bool IsInput) {
   if (AstContext.getTargetInfo().getTriple().getOS() != llvm::Triple::Vulkan)
     return false;
 
@@ -781,8 +782,13 @@ static bool isPipelineBuiltin(const ASTContext 
&AstContext, FunctionDecl *FD,
   llvm::Triple::EnvironmentType ST = ShaderAttr->getType();
   auto SemanticName = Semantic->getSemanticName().upper();
 
-  if (ST == llvm::Triple::Pixel && SemanticName == "SV_POSITION")
-    return true;
+  // The SV_Position semantic is lowered to:
+  //  - Position built-in for vertex output.
+  //  - FragCoord built-in for fragment input.
+  if (SemanticName == "SV_POSITION") {
+    return (ST == llvm::Triple::Vertex && !IsInput) ||
+           (ST == llvm::Triple::Pixel && IsInput);
+  }
 
   return false;
 }
@@ -816,7 +822,7 @@ bool SemaHLSL::determineActiveSemanticOnScalar(FunctionDecl 
*FD,
 
   unsigned Location = ActiveSemantic.Index.value_or(0);
 
-  if (!isPipelineBuiltin(getASTContext(), FD, A)) {
+  if (!isVkPipelineBuiltin(getASTContext(), FD, A, IsInput)) {
     bool HasVkLocation = false;
     if (auto *A = D->getAttr<HLSLVkLocationAttr>()) {
       HasVkLocation = true;
diff --git 
a/clang/test/CodeGenHLSL/semantics/semantic.explicit-mix-builtin.vs.hlsl 
b/clang/test/CodeGenHLSL/semantics/semantic.explicit-mix-builtin.vs.hlsl
new file mode 100644
index 0000000000000..43dc30f089d9e
--- /dev/null
+++ b/clang/test/CodeGenHLSL/semantics/semantic.explicit-mix-builtin.vs.hlsl
@@ -0,0 +1,31 @@
+// RUN: %clang_cc1 -triple spirv-linux-vulkan-vertex -x hlsl -emit-llvm 
-finclude-default-header -disable-llvm-passes -o - %s | FileCheck %s
+
+// This is almost the same as semantic.explicit-mix-builtin.hlsl, except this
+// time we build a vertex shader. This means the SV_Position semantic output
+// is also a BuiltIn, This means we can mix implicit and explicit location
+// assignment.
+struct S1 {
+  float4 position : SV_Position;
+  [[vk::location(3)]] float4 color : A;
+};
+
+// CHECK: @SV_Position0 = external hidden thread_local addrspace(7) 
externally_initialized constant <4 x float>, !spirv.Decorations ![[#MD_0:]]
+// CHECK: @SV_Position = external hidden thread_local addrspace(8) global <4 x 
float>, !spirv.Decorations ![[#MD_2:]]
+// CHECK: @A0 = external hidden thread_local addrspace(8) global <4 x float>, 
!spirv.Decorations ![[#MD_0]]
+
+[shader("vertex")]
+S1 main1(float4 position : SV_Position) {
+  S1 output;
+  output.position = position;
+  output.color = position;
+  return output;
+}
+
+// CHECK: ![[#MD_0]] = !{![[#MD_1:]]}
+// CHECK: ![[#MD_1]] = !{i32 30, i32 0}
+//                            |       `-> Location index
+//                            `-> SPIR-V decoration 'Location'
+// CHECK: ![[#MD_2]] = !{![[#MD_3:]]}
+// CHECK: ![[#MD_3]] = !{i32 11, i32 0}
+//                            |       `-> BuiltIn 'Position'
+//                            `-> SPIR-V decoration 'BuiltIn'

From 97d25e5608df4c0706b4702d5b17ef54849282df Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= <[email protected]>
Date: Thu, 27 Nov 2025 10:34:21 +0100
Subject: [PATCH 6/7] move state to struct

---
 clang/include/clang/Sema/SemaHLSL.h | 30 +++++++++++-----
 clang/lib/Sema/SemaHLSL.cpp         | 53 +++++++++++++----------------
 2 files changed, 45 insertions(+), 38 deletions(-)

diff --git a/clang/include/clang/Sema/SemaHLSL.h 
b/clang/include/clang/Sema/SemaHLSL.h
index cac89f6e51cae..a2faa91d1e54d 100644
--- a/clang/include/clang/Sema/SemaHLSL.h
+++ b/clang/include/clang/Sema/SemaHLSL.h
@@ -237,19 +237,33 @@ class SemaHLSL : public SemaBase {
 
   IdentifierInfo *RootSigOverrideIdent = nullptr;
 
+  // Information about the current subtree being flattened.
   struct SemanticInfo {
     HLSLParsedSemanticAttr *Semantic;
-    std::optional<uint32_t> Index;
+    std::optional<uint32_t> Index = std::nullopt;
   };
-  std::optional<bool> InputUsesExplicitVkLocations = std::nullopt;
-  std::optional<bool> OutputUsesExplicitVkLocations = std::nullopt;
 
+  // Bitmask used to recall if the current semantic subtree is
+  // input, output or inout.
   enum IOType {
     In = 0b01,
     Out = 0b10,
     InOut = 0b11,
   };
 
+  // The context shared by all semantics with the same IOType during
+  // flattening.
+  struct SemanticContext {
+    // Present if any semantic sharing the same IO type has an explicit or
+    // implicit SPIR-V location index assigned.
+    std::optional<bool> UsesExplicitVkLocations = std::nullopt;
+    // The set of semantics found to be active during flattening. Used to 
detect
+    // index collisions.
+    llvm::StringSet<> ActiveSemantics = {};
+    // The IOType of this semantic set.
+    IOType CurrentIOType;
+  };
+
   struct SemanticStageInfo {
     llvm::Triple::EnvironmentType Stage;
     IOType AllowedIOTypesMask;
@@ -262,19 +276,17 @@ class SemaHLSL : public SemaBase {
 
   void checkSemanticAnnotation(FunctionDecl *EntryPoint, const Decl *Param,
                                const HLSLAppliedSemanticAttr *SemanticAttr,
-                               bool IsInput);
+                               const SemanticContext &SC);
 
   bool determineActiveSemanticOnScalar(FunctionDecl *FD,
                                        DeclaratorDecl *OutputDecl,
                                        DeclaratorDecl *D,
                                        SemanticInfo &ActiveSemantic,
-                                       llvm::StringSet<> &ActiveSemantics,
-                                       bool IsInput);
+                                       SemanticContext &SC);
 
   bool determineActiveSemantic(FunctionDecl *FD, DeclaratorDecl *OutputDecl,
                                DeclaratorDecl *D, SemanticInfo &ActiveSemantic,
-                               llvm::StringSet<> &ActiveSemantics,
-                               bool IsInput);
+                               SemanticContext &SC);
 
   void processExplicitBindingsOnDecl(VarDecl *D);
 
@@ -285,7 +297,7 @@ class SemaHLSL : public SemaBase {
       std::initializer_list<llvm::Triple::EnvironmentType> AllowedStages);
 
   void diagnoseSemanticStageMismatch(
-      const Attr *A, llvm::Triple::EnvironmentType Stage, bool IsInput,
+      const Attr *A, llvm::Triple::EnvironmentType Stage, IOType CurrentIOType,
       std::initializer_list<SemanticStageInfo> AllowedStages);
 
   uint32_t getNextImplicitBindingOrderID() {
diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index 1b55d6c1f2e55..89645e3b67db3 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -797,8 +797,7 @@ bool SemaHLSL::determineActiveSemanticOnScalar(FunctionDecl 
*FD,
                                                DeclaratorDecl *OutputDecl,
                                                DeclaratorDecl *D,
                                                SemanticInfo &ActiveSemantic,
-                                               llvm::StringSet<> 
&UsedSemantics,
-                                               bool IsInput) {
+                                               SemaHLSL::SemanticContext &SC) {
   if (ActiveSemantic.Semantic == nullptr) {
     ActiveSemantic.Semantic = D->getAttr<HLSLParsedSemanticAttr>();
     if (ActiveSemantic.Semantic)
@@ -817,25 +816,24 @@ bool 
SemaHLSL::determineActiveSemanticOnScalar(FunctionDecl *FD,
   if (!A)
     return false;
 
-  checkSemanticAnnotation(FD, D, A, IsInput);
+  checkSemanticAnnotation(FD, D, A, SC);
   OutputDecl->addAttr(A);
 
   unsigned Location = ActiveSemantic.Index.value_or(0);
 
-  if (!isVkPipelineBuiltin(getASTContext(), FD, A, IsInput)) {
+  if (!isVkPipelineBuiltin(getASTContext(), FD, A,
+                           SC.CurrentIOType & IOType::In)) {
     bool HasVkLocation = false;
     if (auto *A = D->getAttr<HLSLVkLocationAttr>()) {
       HasVkLocation = true;
       Location = A->getLocation();
     }
 
-    auto &UsesExplicitVkLocations =
-        IsInput ? InputUsesExplicitVkLocations : OutputUsesExplicitVkLocations;
-    if (UsesExplicitVkLocations.value_or(HasVkLocation) != HasVkLocation) {
+    if (SC.UsesExplicitVkLocations.value_or(HasVkLocation) != HasVkLocation) {
       Diag(D->getLocation(), 
diag::err_hlsl_semantic_partial_explicit_indexing);
       return false;
     }
-    UsesExplicitVkLocations = HasVkLocation;
+    SC.UsesExplicitVkLocations = HasVkLocation;
   }
 
   const ConstantArrayType *AT = dyn_cast<ConstantArrayType>(D->getType());
@@ -846,7 +844,7 @@ bool SemaHLSL::determineActiveSemanticOnScalar(FunctionDecl 
*FD,
   for (unsigned I = 0; I < ElementCount; ++I) {
     Twine VariableName = BaseName.concat(Twine(Location + I));
 
-    auto [_, Inserted] = UsedSemantics.insert(VariableName.str());
+    auto [_, Inserted] = SC.ActiveSemantics.insert(VariableName.str());
     if (!Inserted) {
       Diag(D->getLocation(), diag::err_hlsl_semantic_index_overlap)
           << VariableName.str();
@@ -861,8 +859,7 @@ bool SemaHLSL::determineActiveSemantic(FunctionDecl *FD,
                                        DeclaratorDecl *OutputDecl,
                                        DeclaratorDecl *D,
                                        SemanticInfo &ActiveSemantic,
-                                       llvm::StringSet<> &UsedSemantics,
-                                       bool IsInput) {
+                                       SemaHLSL::SemanticContext &SC) {
   if (ActiveSemantic.Semantic == nullptr) {
     ActiveSemantic.Semantic = D->getAttr<HLSLParsedSemanticAttr>();
     if (ActiveSemantic.Semantic)
@@ -875,13 +872,12 @@ bool SemaHLSL::determineActiveSemantic(FunctionDecl *FD,
   const RecordType *RT = dyn_cast<RecordType>(T);
   if (!RT)
     return determineActiveSemanticOnScalar(FD, OutputDecl, D, ActiveSemantic,
-                                           UsedSemantics, IsInput);
+                                           SC);
 
   const RecordDecl *RD = RT->getDecl();
   for (FieldDecl *Field : RD->fields()) {
     SemanticInfo Info = ActiveSemantic;
-    if (!determineActiveSemantic(FD, OutputDecl, Field, Info, UsedSemantics,
-                                 IsInput)) {
+    if (!determineActiveSemantic(FD, OutputDecl, Field, Info, SC)) {
       Diag(Field->getLocation(), diag::note_hlsl_semantic_used_here) << Field;
       return false;
     }
@@ -954,7 +950,9 @@ void SemaHLSL::CheckEntryPoint(FunctionDecl *FD) {
     llvm_unreachable("Unhandled environment in triple");
   }
 
-  llvm::StringSet<> ActiveInputSemantics;
+  SemaHLSL::SemanticContext InputSC = {};
+  InputSC.CurrentIOType = IOType::In;
+
   for (ParmVarDecl *Param : FD->parameters()) {
     SemanticInfo ActiveSemantic;
     ActiveSemantic.Semantic = Param->getAttr<HLSLParsedSemanticAttr>();
@@ -962,26 +960,25 @@ void SemaHLSL::CheckEntryPoint(FunctionDecl *FD) {
       ActiveSemantic.Index = ActiveSemantic.Semantic->getSemanticIndex();
 
     // FIXME: Verify output semantics in parameters.
-    if (!determineActiveSemantic(FD, Param, Param, ActiveSemantic,
-                                 ActiveInputSemantics, /* IsInput= */ true)) {
+    if (!determineActiveSemantic(FD, Param, Param, ActiveSemantic, InputSC)) {
       Diag(Param->getLocation(), diag::note_previous_decl) << Param;
       FD->setInvalidDecl();
     }
   }
 
   SemanticInfo ActiveSemantic;
-  llvm::StringSet<> ActiveOutputSemantics;
+  SemaHLSL::SemanticContext OutputSC = {};
+  OutputSC.CurrentIOType = IOType::Out;
   ActiveSemantic.Semantic = FD->getAttr<HLSLParsedSemanticAttr>();
   if (ActiveSemantic.Semantic)
     ActiveSemantic.Index = ActiveSemantic.Semantic->getSemanticIndex();
   if (!FD->getReturnType()->isVoidType())
-    determineActiveSemantic(FD, FD, FD, ActiveSemantic, ActiveOutputSemantics,
-                            /* IsInput= */ false);
+    determineActiveSemantic(FD, FD, FD, ActiveSemantic, OutputSC);
 }
 
 void SemaHLSL::checkSemanticAnnotation(
     FunctionDecl *EntryPoint, const Decl *Param,
-    const HLSLAppliedSemanticAttr *SemanticAttr, bool IsInput) {
+    const HLSLAppliedSemanticAttr *SemanticAttr, const SemanticContext &SC) {
   auto *ShaderAttr = EntryPoint->getAttr<HLSLShaderAttr>();
   assert(ShaderAttr && "Entry point has no shader attribute");
   llvm::Triple::EnvironmentType ST = ShaderAttr->getType();
@@ -992,7 +989,7 @@ void SemaHLSL::checkSemanticAnnotation(
       SemanticName == "SV_GROUPID") {
 
     if (ST != llvm::Triple::Compute)
-      diagnoseSemanticStageMismatch(SemanticAttr, ST, IsInput,
+      diagnoseSemanticStageMismatch(SemanticAttr, ST, SC.CurrentIOType,
                                     {{llvm::Triple::Compute, IOType::In}});
 
     if (SemanticAttr->getSemanticIndex() != 0) {
@@ -1008,14 +1005,14 @@ void SemaHLSL::checkSemanticAnnotation(
   if (SemanticName == "SV_POSITION") {
     // SV_Position can be an input or output in vertex shaders,
     // but only an input in pixel shaders.
-    diagnoseSemanticStageMismatch(SemanticAttr, ST, IsInput,
+    diagnoseSemanticStageMismatch(SemanticAttr, ST, SC.CurrentIOType,
                                   {{llvm::Triple::Vertex, IOType::InOut},
                                    {llvm::Triple::Pixel, IOType::In}});
     return;
   }
 
   if (SemanticName == "SV_TARGET") {
-    diagnoseSemanticStageMismatch(SemanticAttr, ST, IsInput,
+    diagnoseSemanticStageMismatch(SemanticAttr, ST, SC.CurrentIOType,
                                   {{llvm::Triple::Pixel, IOType::Out}});
     return;
   }
@@ -1041,16 +1038,14 @@ void SemaHLSL::diagnoseAttrStageMismatch(
 }
 
 void SemaHLSL::diagnoseSemanticStageMismatch(
-    const Attr *A, llvm::Triple::EnvironmentType Stage, bool IsInput,
+    const Attr *A, llvm::Triple::EnvironmentType Stage, IOType CurrentIOType,
     std::initializer_list<SemanticStageInfo> Allowed) {
 
   for (auto &Case : Allowed) {
     if (Case.Stage != Stage)
       continue;
 
-    if (IsInput && Case.AllowedIOTypesMask & IOType::In)
-      return;
-    if (!IsInput && Case.AllowedIOTypesMask & IOType::Out)
+    if (CurrentIOType & Case.AllowedIOTypesMask)
       return;
 
     SmallVector<std::string, 8> ValidCases;
@@ -1066,7 +1061,7 @@ void SemaHLSL::diagnoseSemanticStageMismatch(
                  " " + join(ValidType, "/");
         });
     Diag(A->getLoc(), diag::err_hlsl_semantic_unsupported_iotype_for_stage)
-        << A->getAttrName() << (IsInput ? "input" : "output")
+        << A->getAttrName() << (CurrentIOType & IOType::In ? "input" : 
"output")
         << llvm::Triple::getEnvironmentTypeName(Case.Stage)
         << join(ValidCases, ", ");
     return;

From ddf2c2134bff846d2733fe03fe0698e51c8ea77e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= <[email protected]>
Date: Thu, 27 Nov 2025 11:01:48 +0100
Subject: [PATCH 7/7] fix shader kind in CG

---
 clang/lib/CodeGen/CGHLSLRuntime.cpp           | 15 ++++---
 clang/lib/CodeGen/CGHLSLRuntime.h             |  3 +-
 .../semantics/semantic.explicit-mix.lib.hlsl  | 40 +++++++++++++++++++
 3 files changed, 52 insertions(+), 6 deletions(-)
 create mode 100644 
clang/test/CodeGenHLSL/semantics/semantic.explicit-mix.lib.hlsl

diff --git a/clang/lib/CodeGen/CGHLSLRuntime.cpp 
b/clang/lib/CodeGen/CGHLSLRuntime.cpp
index 913d80b7e5c25..30a74a2571247 100644
--- a/clang/lib/CodeGen/CGHLSLRuntime.cpp
+++ b/clang/lib/CodeGen/CGHLSLRuntime.cpp
@@ -699,8 +699,9 @@ void CGHLSLRuntime::emitUserSemanticStore(IRBuilder<> &B, 
llvm::Value *Source,
 }
 
 llvm::Value *CGHLSLRuntime::emitSystemSemanticLoad(
-    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) {
 
   std::string SemanticName = Semantic->getAttrName()->getName().upper();
   if (SemanticName == "SV_GROUPINDEX") {
@@ -736,8 +737,12 @@ llvm::Value *CGHLSLRuntime::emitSystemSemanticLoad(
     return buildVectorInput(B, GroupIDIntrinsic, Type);
   }
 
+  const auto *ShaderAttr = FD->getAttr<HLSLShaderAttr>();
+  assert(ShaderAttr && "Entry point has no shader attribute");
+  llvm::Triple::EnvironmentType ST = ShaderAttr->getType();
+
   if (SemanticName == "SV_POSITION") {
-    if (CGM.getTriple().getEnvironment() == Triple::EnvironmentType::Pixel) {
+    if (ST == Triple::EnvironmentType::Pixel) {
       if (CGM.getTarget().getTriple().isSPIRV())
         return createSPIRVBuiltinLoad(B, CGM.getModule(), Type,
                                       Semantic->getAttrName()->getName(),
@@ -746,7 +751,7 @@ llvm::Value *CGHLSLRuntime::emitSystemSemanticLoad(
         return emitDXILUserSemanticLoad(B, Type, Semantic, Index);
     }
 
-    if (CGM.getTriple().getEnvironment() == Triple::EnvironmentType::Vertex) {
+    if (ST == Triple::EnvironmentType::Vertex) {
       return emitUserSemanticLoad(B, Type, Decl, Semantic, Index);
     }
   }
@@ -804,7 +809,7 @@ llvm::Value *CGHLSLRuntime::handleScalarSemanticLoad(
 
   std::optional<unsigned> Index = Semantic->getSemanticIndex();
   if (Semantic->getAttrName()->getName().starts_with_insensitive("SV_"))
-    return emitSystemSemanticLoad(B, Type, Decl, Semantic, Index);
+    return emitSystemSemanticLoad(B, FD, Type, Decl, Semantic, Index);
   return emitUserSemanticLoad(B, Type, Decl, Semantic, Index);
 }
 
diff --git a/clang/lib/CodeGen/CGHLSLRuntime.h 
b/clang/lib/CodeGen/CGHLSLRuntime.h
index d057add2db222..77f43e8766745 100644
--- a/clang/lib/CodeGen/CGHLSLRuntime.h
+++ b/clang/lib/CodeGen/CGHLSLRuntime.h
@@ -179,7 +179,8 @@ class CGHLSLRuntime {
 protected:
   CodeGenModule &CGM;
 
-  llvm::Value *emitSystemSemanticLoad(llvm::IRBuilder<> &B, llvm::Type *Type,
+  llvm::Value *emitSystemSemanticLoad(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/semantic.explicit-mix.lib.hlsl 
b/clang/test/CodeGenHLSL/semantics/semantic.explicit-mix.lib.hlsl
new file mode 100644
index 0000000000000..456c9bf9aee05
--- /dev/null
+++ b/clang/test/CodeGenHLSL/semantics/semantic.explicit-mix.lib.hlsl
@@ -0,0 +1,40 @@
+// 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
+
+// The followiong file contains both implicit and explicit vk::location, but
+// because each entrypoint has only one kind, this is allowed.
+
+[shader("vertex")]
+float4 vs_main(float4 p : SV_Position) : A {
+  return p;
+}
+
+[shader("pixel")]
+float4 ps_main([[vk::location(0)]] float4 p : A) : SV_Target {
+  return p;
+}
+
+// The following function is not marked as being a shader entrypoint, this
+// means the semantics and [[vk::location]] attributes are ignored.
+// Otherwise, the partial explicit location assignment would be illegal.
+float4 not_an_entry([[vk::location(0)]] float4 a : A, float4 b : B) : C {
+  return a + b;
+}
+
+// CHECK: @SV_Position0 = external hidden thread_local addrspace(7) 
externally_initialized constant <4 x float>, !spirv.Decorations ![[#MD_0:]]
+// CHECK: @A0 = external hidden thread_local addrspace(8) global <4 x float>, 
!spirv.Decorations ![[#MD_0:]]
+// CHECK: @A0.1 = external hidden thread_local addrspace(7) 
externally_initialized constant <4 x float>, !spirv.Decorations ![[#MD_0:]]
+// CHECK: @SV_Target0 = external hidden thread_local addrspace(8) global <4 x 
float>, !spirv.Decorations ![[#MD_2:]]
+
+
+// CHECK: define void @vs_main()
+// CHECK: %[[#]] = load <4 x float>, ptr addrspace(7) @SV_Position0, align 16
+// CHECK: store <4 x float> %[[#]], ptr addrspace(8) @A0, align 16
+
+// CHECK: define void @ps_main()
+// CHECK: %[[#]] = load <4 x float>, ptr addrspace(7) @A0.1, align 16
+// CHECK: store <4 x float> %[[#]], ptr addrspace(8) @SV_Target0, align 16
+
+// CHECK: ![[#MD_0]] = !{![[#MD_1:]]}
+// CHECK: ![[#MD_1]] = !{i32 30, i32 0}
+// CHECK: ![[#MD_2]] = !{![[#MD_3:]]}
+// CHECK: ![[#MD_3]] = !{i32 30, i32 1}

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to