https://github.com/dnovillo updated 
https://github.com/llvm/llvm-project/pull/196404

>From 547fe35d9882021b7767b4db07d585a20c990ab9 Mon Sep 17 00:00:00 2001
From: Diego Novillo <[email protected]>
Date: Wed, 6 May 2026 11:13:34 -0400
Subject: [PATCH 1/8] [clang][SPIR-V] Implement -fspv-preserve-interface

This flag, originally implemented in DXC, prevents GlobalDCE from
removing entry-point interface variables, even if they are unreferenced
after inlining.

This adds HLSLSpvPreserveInterface to LangOptions.def and Options.td. In
CGHLSLRuntime::finishCodeGen(), adds all addrspace(7) and addrspace(8)
globals to llvm.compiler.used.

In processGlobalValue(), it extends the condition that emits
spv_unref_global to fire for globals whose only uses come from
llvm.compiler.used or llvm.used.

Fixes https://github.com/llvm/llvm-project/issues/136936
---
 clang/include/clang/Basic/LangOptions.def     |  1 +
 clang/include/clang/Options/Options.td        |  9 +++
 clang/lib/CodeGen/CGHLSLRuntime.cpp           | 13 +++++
 .../CodeGenHLSL/preserve-interface-dce.hlsl   | 25 +++++++++
 .../test/CodeGenHLSL/preserve-interface.hlsl  | 24 ++++++++
 .../Driver/dxc_fspv_preserve_interface.hlsl   | 10 ++++
 llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp | 45 +++++++++++++--
 .../CodeGen/SPIRV/preserve-interface-dce.ll   | 51 +++++++++++++++++
 llvm/test/CodeGen/SPIRV/preserve-interface.ll | 55 +++++++++++++++++++
 9 files changed, 228 insertions(+), 5 deletions(-)
 create mode 100644 clang/test/CodeGenHLSL/preserve-interface-dce.hlsl
 create mode 100644 clang/test/CodeGenHLSL/preserve-interface.hlsl
 create mode 100644 clang/test/Driver/dxc_fspv_preserve_interface.hlsl
 create mode 100644 llvm/test/CodeGen/SPIRV/preserve-interface-dce.ll
 create mode 100644 llvm/test/CodeGen/SPIRV/preserve-interface.ll

diff --git a/clang/include/clang/Basic/LangOptions.def 
b/clang/include/clang/Basic/LangOptions.def
index 4a3e3b7c04822..87a60fdd40475 100644
--- a/clang/include/clang/Basic/LangOptions.def
+++ b/clang/include/clang/Basic/LangOptions.def
@@ -248,6 +248,7 @@ LANGOPT(HLSLStrictAvailability, 1, 0, NotCompatible,
         "Strict availability diagnostic mode for HLSL built-in functions.")
 LANGOPT(HLSLSpvUseUnknownImageFormat, 1, 0, NotCompatible, "For storage images 
and texel buffers, sets the default format to 'Unknown' when not specified via 
the `vk::image_format` attribute. If this option is not used, the format is 
inferred from the resource's data type.")
 LANGOPT(HLSLSpvEnableMaximalReconvergence, 1, 0, NotCompatible, "Enables the 
MaximallyReconvergesKHR execution mode for this module. This ensures that 
control flow reconverges at well-defined merge points as defined by the Vulkan 
spec.")
+LANGOPT(HLSLSpvPreserveInterface, 1, 0, NotCompatible, "Preserve entry-point 
interface variables from dead-code elimination.")
 LANGOPT(EmitLogicalPointer, 1, 0, NotCompatible, "Allow emitting structured 
GEP/alloca intrinsics instead of normal GEP/alloca instructions.")
 
 LANGOPT(CUDAIsDevice      , 1, 0, NotCompatible, "compiling for CUDA device")
diff --git a/clang/include/clang/Options/Options.td 
b/clang/include/clang/Options/Options.td
index 281ccc9614b58..b979ac2060b0d 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -9762,6 +9762,15 @@ def fhlsl_spv_enable_maximal_reconvergence
                "well-defined merge points as defined by the Vulkan spec.">,
       MarshallingInfoFlag<LangOpts<"HLSLSpvEnableMaximalReconvergence">>;
 
+def fhlsl_spv_preserve_interface
+    : Flag<["-"], "fspv-preserve-interface">,
+      Group<dxc_Group>,
+      Visibility<[CC1Option, DXCOption]>,
+      HelpText<"Preserve all interface variables at entry points, preventing "
+               "dead-code elimination of variables with Location or BuiltIn "
+               "decorations.">,
+      MarshallingInfoFlag<LangOpts<"HLSLSpvPreserveInterface">>;
+
 def fexperimental_logical_pointer
     : Flag<["-"], "fexperimental-logical-pointer">,
       Visibility<[CC1Option, DXCOption]>,
diff --git a/clang/lib/CodeGen/CGHLSLRuntime.cpp 
b/clang/lib/CodeGen/CGHLSLRuntime.cpp
index 33d76cbda494a..2ce381e7a7f9a 100644
--- a/clang/lib/CodeGen/CGHLSLRuntime.cpp
+++ b/clang/lib/CodeGen/CGHLSLRuntime.cpp
@@ -45,6 +45,7 @@
 #include "llvm/Support/Alignment.h"
 #include "llvm/Support/ErrorHandling.h"
 #include "llvm/Support/FormatVariadic.h"
+#include "llvm/Transforms/Utils/ModuleUtils.h"
 #include <cstdint>
 #include <optional>
 
@@ -596,6 +597,18 @@ void CGHLSLRuntime::finishCodeGen() {
     M.setModuleFlag(llvm::Module::ModFlagBehavior::Error, "dx.nativelowprec",
                     1);
 
+  if (LangOpts.HLSLSpvPreserveInterface && T.isSPIRV()) {
+    // Runs before optimization. Keeps Input/Output globals from GlobalDCE.
+    SmallVector<GlobalValue *, 8> InterfaceVars;
+    for (GlobalVariable &GV : M.globals()) {
+      unsigned AS = GV.getAddressSpace();
+      if (AS == 7 || AS == 8) // addrspace 7 = Input, addrspace 8 = Output
+        InterfaceVars.push_back(&GV);
+    }
+    if (!InterfaceVars.empty())
+      appendToCompilerUsed(M, InterfaceVars);
+  }
+
   generateGlobalCtorDtorCalls();
 }
 
diff --git a/clang/test/CodeGenHLSL/preserve-interface-dce.hlsl 
b/clang/test/CodeGenHLSL/preserve-interface-dce.hlsl
new file mode 100644
index 0000000000000..6431df6d01a95
--- /dev/null
+++ b/clang/test/CodeGenHLSL/preserve-interface-dce.hlsl
@@ -0,0 +1,25 @@
+// RUN: %clang_cc1 -triple spirv-unknown-vulkan1.3-vertex -std=hlsl2021 \
+// RUN:   -finclude-default-header -disable-llvm-passes -emit-llvm -o - %s \
+// RUN:   | FileCheck %s --check-prefix=O0
+// RUN: %clang_cc1 -triple spirv-unknown-vulkan1.3-vertex -std=hlsl2021 \
+// RUN:   -finclude-default-header -O2 -emit-llvm -o - %s \
+// RUN:   | FileCheck %s --check-prefix=O2
+//
+// Confirm that the frontend creates addrspace(7) globals for all entry-point
+// input parameters (O0), and that GlobalDCE removes the unused one at O2.
+// A passing O2 run confirms that -fspv-preserve-interface requires a DCE 
guard.
+
+// Both input globals must be present before optimization.
+// O0-DAG: @POSITION0 = external hidden thread_local addrspace(7)
+// O0-DAG: @TEXCOORD0 = external hidden thread_local addrspace(7)
+
+// The used POSITION0 global must survive optimization.
+// O2: @POSITION0 = external hidden thread_local{{.*}}addrspace(7)
+
+// The unused TEXCOORD0 global must be eliminated by GlobalDCE at -O2.
+// O2-NOT: @TEXCOORD0 = {{.*}}addrspace(7)
+
+[shader("vertex")]
+float4 main(float4 pos : POSITION0, float4 uv : TEXCOORD0) : SV_Position {
+  return pos;
+}
diff --git a/clang/test/CodeGenHLSL/preserve-interface.hlsl 
b/clang/test/CodeGenHLSL/preserve-interface.hlsl
new file mode 100644
index 0000000000000..af30b2db84f93
--- /dev/null
+++ b/clang/test/CodeGenHLSL/preserve-interface.hlsl
@@ -0,0 +1,24 @@
+// RUN: %clang_cc1 -triple spirv-unknown-vulkan1.3-vertex -std=hlsl2021 \
+// RUN:   -finclude-default-header -fspv-preserve-interface -O2 -emit-llvm \
+// RUN:   -o - %s | FileCheck %s
+//
+// Confirm that -fspv-preserve-interface prevents GlobalDCE from removing an
+// unused entry-point input semantic at -O2. Without the flag, @TEXCOORD0 is
+// eliminated (see preserve-interface-dce.hlsl). With the flag, it must 
survive.
+
+// Both input globals must be present in the optimized IR.
+// CHECK-DAG: @POSITION0 = external hidden thread_local addrspace(7)
+// CHECK-DAG: @TEXCOORD0 = external hidden thread_local addrspace(7)
+
+// Both globals must appear in llvm.compiler.used. The implementation adds all
+// addrspace(7) and addrspace(8) globals, not just the ones the optimizer would
+// otherwise remove, matching DXC's behavior of marking the entire OpEntryPoint
+// as live.
+// CHECK: @llvm.compiler.used = appending
+// CHECK-DAG: @POSITION0 to ptr
+// CHECK-DAG: @TEXCOORD0 to ptr
+
+[shader("vertex")]
+float4 main(float4 pos : POSITION0, float4 uv : TEXCOORD0) : SV_Position {
+  return pos;
+}
diff --git a/clang/test/Driver/dxc_fspv_preserve_interface.hlsl 
b/clang/test/Driver/dxc_fspv_preserve_interface.hlsl
new file mode 100644
index 0000000000000..a3c883aa54418
--- /dev/null
+++ b/clang/test/Driver/dxc_fspv_preserve_interface.hlsl
@@ -0,0 +1,10 @@
+// Verify that -fspv-preserve-interface is accepted by the driver and forwarded
+// to cc1 as -fspv-preserve-interface.
+// RUN: %clang_dxc -spirv -Tlib_6_7 -fspv-preserve-interface -### %s 2>&1 \
+// RUN:   | FileCheck %s --check-prefix=CHECK-PRESERVE
+// CHECK-PRESERVE: "-fspv-preserve-interface"
+
+// Without the flag, -fspv-preserve-interface must not appear in cc1 args.
+// RUN: %clang_dxc -spirv -Tlib_6_7 -### %s 2>&1 \
+// RUN:   | FileCheck %s --check-prefix=CHECK-NO-PRESERVE
+// CHECK-NO-PRESERVE-NOT: "-fspv-preserve-interface"
diff --git a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp 
b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
index 97fa49d8836fb..48e19bbca32b9 100644
--- a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
@@ -2491,15 +2491,50 @@ Instruction 
*SPIRVEmitIntrinsics::visitUnreachableInst(UnreachableInst &I) {
   return &I;
 }
 
+// llvm.compiler.used and llvm.used hold use-list entries that protect their
+// referenced globals from DCE without participating in code generation.
+static bool isUseListGlobal(StringRef Name) {
+  return Name == "llvm.compiler.used" || Name == "llvm.used";
+}
+
+// Returns true for module-level globals that should not have SPIR-V intrinsics
+// emitted (use-list globals plus llvm.global.annotations).
+static bool isArtificialGlobal(StringRef Name) {
+  return isUseListGlobal(Name) || Name == "llvm.global.annotations";
+}
+
+// Returns true if every use of GV traces back to llvm.compiler.used or
+// llvm.used. Such uses are not real function uses. They protect the variable
+// from GlobalDCE without participating in code generation.
+static bool hasOnlyArtificialUses(const GlobalVariable &GV) {
+  SmallPtrSet<const Value *, 8> Visited;
+  SmallVector<const Value *> Stack(GV.users());
+  while (!Stack.empty()) {
+    const Value *V = Stack.pop_back_val();
+    if (!Visited.insert(V).second)
+      continue;
+    if (const auto *GVUser = dyn_cast<GlobalVariable>(V)) {
+      if (!isUseListGlobal(GVUser->getName()))
+        return false;
+      continue;
+    }
+    if (isa<Instruction>(V))
+      return false;
+    if (const auto *C = dyn_cast<Constant>(V)) {
+      Stack.append(C->user_begin(), C->user_end());
+      continue;
+    }
+    return false;
+  }
+  return true;
+}
+
 static bool
 shouldEmitIntrinsicsForGlobalValue(const GlobalVariableUsers &GVUsers,
                                    const GlobalVariable &GV,
                                    const Function *F) {
   // Skip special artificial variables.
-  static const StringSet<> ArtificialGlobals{"llvm.global.annotations",
-                                             "llvm.compiler.used", 
"llvm.used"};
-
-  if (ArtificialGlobals.contains(GV.getName()))
+  if (isArtificialGlobal(GV.getName()))
     return false;
 
   auto &UserFunctions = GVUsers.getTransitiveUserFunctions(GV);
@@ -2582,7 +2617,7 @@ void 
SPIRVEmitIntrinsics::processGlobalValue(GlobalVariable &GV,
                                        {GV.getType(), Ty}, {&GV, Const});
     InitInst->setArgOperand(1, InitOp);
   }
-  if (!Init && GV.use_empty())
+  if (!Init && (GV.use_empty() || hasOnlyArtificialUses(GV)))
     B.CreateIntrinsic(Intrinsic::spv_unref_global, GV.getType(), &GV);
 }
 
diff --git a/llvm/test/CodeGen/SPIRV/preserve-interface-dce.ll 
b/llvm/test/CodeGen/SPIRV/preserve-interface-dce.ll
new file mode 100644
index 0000000000000..8b5a9310e8b6f
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/preserve-interface-dce.ll
@@ -0,0 +1,51 @@
+; RUN: opt -passes=globaldce -S -o - %s | llc 
-mtriple=spirv-unknown-vulkan1.3-vertex \
+; RUN:   -o - | FileCheck %s
+; RUN: %if spirv-tools %{ opt -passes=globaldce -S -o - %s \
+; RUN:   | llc -mtriple=spirv-unknown-vulkan1.3-vertex -filetype=obj -o - \
+; RUN:   | spirv-val --target-env vulkan1.3 %}
+;
+; Confirm that GlobalDCE removes an addrspace(7) global with no users before
+; the SPIR-V backend emits OpEntryPoint. A passing run means the dead variable
+; is absent from the interface list and -fspv-preserve-interface must guard it.
+;
+; @used_input has a live load. It must appear in OpEntryPoint.
+; @dead_input has no users. GlobalDCE removes it before backend emission.
+
+; OpEntryPoint appears before OpVariable in SPIR-V module order. Check it 
first.
+; CHECK: OpCapability Shader
+; CHECK: OpEntryPoint Vertex %[[#]] "main"
+
+; Exactly one Input variable must appear: the one for @used_input.
+; CHECK: %[[#]] = OpTypePointer Input
+; CHECK: %[[#UsedVar:]] = OpVariable %[[#]] Input
+; CHECK-NOT: = OpVariable %[[#]] Input
+
+@used_input = external hidden thread_local addrspace(7) global float,
+    !spirv.Decorations !0
+@dead_input = external hidden thread_local addrspace(7) global float,
+    !spirv.Decorations !2
+
+define void @main() #0 {
+  ; Load from @used_input. The result feeds the store to @output.
+  %v = load float, ptr addrspace(7) @used_input, align 4
+  store float %v, ptr addrspace(8) @output, align 4
+  ; @dead_input is never referenced.
+  ret void
+}
+
+@output = external hidden thread_local addrspace(8) global float,
+    !spirv.Decorations !4
+
+attributes #0 = { "hlsl.shader"="vertex" }
+
+; Location = 0 on @used_input
+!0 = !{!1}
+!1 = !{i32 30, i32 0}
+
+; Location = 1 on @dead_input
+!2 = !{!3}
+!3 = !{i32 30, i32 1}
+
+; Location = 0 on @output
+!4 = !{!5}
+!5 = !{i32 30, i32 0}
diff --git a/llvm/test/CodeGen/SPIRV/preserve-interface.ll 
b/llvm/test/CodeGen/SPIRV/preserve-interface.ll
new file mode 100644
index 0000000000000..b0d5fd79b8a62
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/preserve-interface.ll
@@ -0,0 +1,55 @@
+; RUN: llc -mtriple=spirv-unknown-vulkan1.3-vertex -o - %s | FileCheck %s
+; RUN: %if spirv-tools %{ llc -mtriple=spirv-unknown-vulkan1.3-vertex \
+; RUN:   -filetype=obj -o - %s | spirv-val --target-env vulkan1.3 %}
+;
+; Confirm that an addrspace(7) global protected by llvm.compiler.used appears
+; in the SPIR-V output as a distinct OpVariable, even though it has no IR 
users.
+;
+; @used_input has a real load. @dead_input is only in llvm.compiler.used.
+;
+; %[[#USED]] and %[[#DEAD]] capture different IDs (Location 0 vs 1), so the
+; OpVariable checks below require two distinct OpVariable Input instructions.
+;
+; Without the processGlobalValue fix in SPIRVEmitIntrinsics.cpp, @dead_input
+; gets no spv_unref_global, buildGlobalVariable is never called for it, and
+; both OpDecorate Location 1 and the second OpVariable Input are absent.
+
+; CHECK: OpCapability Shader
+; CHECK: OpEntryPoint Vertex %[[#]] "main"
+
+; CHECK-DAG: OpDecorate %[[#USED:]] Location 0
+; CHECK-DAG: OpDecorate %[[#DEAD:]] Location 1
+; CHECK-DAG: %[[#USED]] = OpVariable %[[#]] Input
+; CHECK-DAG: %[[#DEAD]] = OpVariable %[[#]] Input
+; CHECK-DAG: %[[#]]     = OpVariable %[[#]] Output
+
+@used_input = external hidden thread_local addrspace(7) global float,
+    !spirv.Decorations !0
+@dead_input = external hidden thread_local addrspace(7) global float,
+    !spirv.Decorations !2
+
[email protected] = appending global [1 x ptr addrspace(7)]
+    [ptr addrspace(7) @dead_input], section "llvm.metadata"
+
+define void @main() #0 {
+  %v = load float, ptr addrspace(7) @used_input, align 4
+  store float %v, ptr addrspace(8) @output, align 4
+  ret void
+}
+
+@output = external hidden thread_local addrspace(8) global float,
+    !spirv.Decorations !4
+
+attributes #0 = { "hlsl.shader"="vertex" }
+
+; Location = 0 on @used_input
+!0 = !{!1}
+!1 = !{i32 30, i32 0}
+
+; Location = 1 on @dead_input
+!2 = !{!3}
+!3 = !{i32 30, i32 1}
+
+; Location = 0 on @output
+!4 = !{!5}
+!5 = !{i32 30, i32 0}

>From da42a70b19ae4ad43d2b9a40d738ca2ca9cf4eae Mon Sep 17 00:00:00 2001
From: Diego Novillo <[email protected]>
Date: Tue, 12 May 2026 17:10:34 -0400
Subject: [PATCH 2/8] Address review feedback.

---
 clang/lib/CodeGen/CGHLSLRuntime.cpp           |  5 ++++-
 llvm/test/CodeGen/SPIRV/preserve-interface.ll | 14 +++++++++++---
 2 files changed, 15 insertions(+), 4 deletions(-)

diff --git a/clang/lib/CodeGen/CGHLSLRuntime.cpp 
b/clang/lib/CodeGen/CGHLSLRuntime.cpp
index 2ce381e7a7f9a..731668b14a1ce 100644
--- a/clang/lib/CodeGen/CGHLSLRuntime.cpp
+++ b/clang/lib/CodeGen/CGHLSLRuntime.cpp
@@ -599,10 +599,13 @@ void CGHLSLRuntime::finishCodeGen() {
 
   if (LangOpts.HLSLSpvPreserveInterface && T.isSPIRV()) {
     // Runs before optimization. Keeps Input/Output globals from GlobalDCE.
+    const ASTContext &Ctx = CGM.getContext();
+    unsigned InputAS = Ctx.getTargetAddressSpace(LangAS::hlsl_input);
+    unsigned OutputAS = Ctx.getTargetAddressSpace(LangAS::hlsl_output);
     SmallVector<GlobalValue *, 8> InterfaceVars;
     for (GlobalVariable &GV : M.globals()) {
       unsigned AS = GV.getAddressSpace();
-      if (AS == 7 || AS == 8) // addrspace 7 = Input, addrspace 8 = Output
+      if (AS == InputAS || AS == OutputAS)
         InterfaceVars.push_back(&GV);
     }
     if (!InterfaceVars.empty())
diff --git a/llvm/test/CodeGen/SPIRV/preserve-interface.ll 
b/llvm/test/CodeGen/SPIRV/preserve-interface.ll
index b0d5fd79b8a62..12f2a9cde55e3 100644
--- a/llvm/test/CodeGen/SPIRV/preserve-interface.ll
+++ b/llvm/test/CodeGen/SPIRV/preserve-interface.ll
@@ -3,7 +3,8 @@
 ; RUN:   -filetype=obj -o - %s | spirv-val --target-env vulkan1.3 %}
 ;
 ; Confirm that an addrspace(7) global protected by llvm.compiler.used appears
-; in the SPIR-V output as a distinct OpVariable, even though it has no IR 
users.
+; in the SPIR-V output as a distinct OpVariable, even though it has no IR
+; users, and that it is also listed in the OpEntryPoint interface.
 ;
 ; @used_input has a real load. @dead_input is only in llvm.compiler.used.
 ;
@@ -13,15 +14,22 @@
 ; Without the processGlobalValue fix in SPIRVEmitIntrinsics.cpp, @dead_input
 ; gets no spv_unref_global, buildGlobalVariable is never called for it, and
 ; both OpDecorate Location 1 and the second OpVariable Input are absent.
+;
+; The OpEntryPoint check pins the interface to exactly three IDs. The SPIR-V
+; backend builds the interface by iterating every Input/Output OpVariable in
+; the module, so combined with the three OpVariable checks below this proves
+; all three preserved variables appear in OpEntryPoint regardless of the
+; backend's interface ordering.
 
 ; CHECK: OpCapability Shader
-; CHECK: OpEntryPoint Vertex %[[#]] "main"
+; CHECK: OpEntryPoint Vertex %[[#]] "main" %[[#]] %[[#]] %[[#]]
 
 ; CHECK-DAG: OpDecorate %[[#USED:]] Location 0
 ; CHECK-DAG: OpDecorate %[[#DEAD:]] Location 1
+; CHECK-DAG: OpDecorate %[[#OUTPUT:]] Location 0
 ; CHECK-DAG: %[[#USED]] = OpVariable %[[#]] Input
 ; CHECK-DAG: %[[#DEAD]] = OpVariable %[[#]] Input
-; CHECK-DAG: %[[#]]     = OpVariable %[[#]] Output
+; CHECK-DAG: %[[#OUTPUT]] = OpVariable %[[#]] Output
 
 @used_input = external hidden thread_local addrspace(7) global float,
     !spirv.Decorations !0

>From 8a52f6254009810832fcefee9be07d92da9735eb Mon Sep 17 00:00:00 2001
From: Diego Novillo <[email protected]>
Date: Thu, 4 Jun 2026 12:29:52 -0400
Subject: [PATCH 3/8] Update llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Co-authored-by: Juan Manuel Martinez Caamaño <[email protected]>
---
 llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp | 2 --
 1 file changed, 2 deletions(-)

diff --git a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp 
b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
index 48e19bbca32b9..1b7bd14af1555 100644
--- a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
@@ -2518,8 +2518,6 @@ static bool hasOnlyArtificialUses(const GlobalVariable 
&GV) {
         return false;
       continue;
     }
-    if (isa<Instruction>(V))
-      return false;
     if (const auto *C = dyn_cast<Constant>(V)) {
       Stack.append(C->user_begin(), C->user_end());
       continue;

>From 033791e1933519303e32dfdd77d76c44fd5f561e Mon Sep 17 00:00:00 2001
From: Diego Novillo <[email protected]>
Date: Thu, 4 Jun 2026 12:30:21 -0400
Subject: [PATCH 4/8] Update llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Co-authored-by: Juan Manuel Martinez Caamaño <[email protected]>
---
 llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp 
b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
index 1b7bd14af1555..4bf9356d69343 100644
--- a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
@@ -2615,7 +2615,7 @@ void 
SPIRVEmitIntrinsics::processGlobalValue(GlobalVariable &GV,
                                        {GV.getType(), Ty}, {&GV, Const});
     InitInst->setArgOperand(1, InitOp);
   }
-  if (!Init && (GV.use_empty() || hasOnlyArtificialUses(GV)))
+  if (!Init && hasOnlyArtificialUses(GV))
     B.CreateIntrinsic(Intrinsic::spv_unref_global, GV.getType(), &GV);
 }
 

>From f97711514340db72621b138423e9a80f08e936b8 Mon Sep 17 00:00:00 2001
From: Diego Novillo <[email protected]>
Date: Thu, 4 Jun 2026 14:31:09 -0400
Subject: [PATCH 5/8] Address review feedback.

---
 llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp |  6 +--
 llvm/test/CodeGen/SPIRV/preserve-interface.ll | 40 +++++++++++--------
 2 files changed, 26 insertions(+), 20 deletions(-)

diff --git a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp 
b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
index 4bf9356d69343..ee05b9d26f9b4 100644
--- a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
@@ -2503,9 +2503,7 @@ static bool isArtificialGlobal(StringRef Name) {
   return isUseListGlobal(Name) || Name == "llvm.global.annotations";
 }
 
-// Returns true if every use of GV traces back to llvm.compiler.used or
-// llvm.used. Such uses are not real function uses. They protect the variable
-// from GlobalDCE without participating in code generation.
+// Returns true if every use of GV traces back to llvm.compiler.used or 
llvm.used.
 static bool hasOnlyArtificialUses(const GlobalVariable &GV) {
   SmallPtrSet<const Value *, 8> Visited;
   SmallVector<const Value *> Stack(GV.users());
@@ -2615,6 +2613,8 @@ void 
SPIRVEmitIntrinsics::processGlobalValue(GlobalVariable &GV,
                                        {GV.getType(), Ty}, {&GV, Const});
     InitInst->setArgOperand(1, InitOp);
   }
+  // Globals with only use-list references have no real function uses. Emit
+  // spv_unref_global so buildGlobalVariable is called for them.
   if (!Init && hasOnlyArtificialUses(GV))
     B.CreateIntrinsic(Intrinsic::spv_unref_global, GV.getType(), &GV);
 }
diff --git a/llvm/test/CodeGen/SPIRV/preserve-interface.ll 
b/llvm/test/CodeGen/SPIRV/preserve-interface.ll
index 12f2a9cde55e3..b61c8e487b6cf 100644
--- a/llvm/test/CodeGen/SPIRV/preserve-interface.ll
+++ b/llvm/test/CodeGen/SPIRV/preserve-interface.ll
@@ -2,39 +2,41 @@
 ; RUN: %if spirv-tools %{ llc -mtriple=spirv-unknown-vulkan1.3-vertex \
 ; RUN:   -filetype=obj -o - %s | spirv-val --target-env vulkan1.3 %}
 ;
-; Confirm that an addrspace(7) global protected by llvm.compiler.used appears
-; in the SPIR-V output as a distinct OpVariable, even though it has no IR
-; users, and that it is also listed in the OpEntryPoint interface.
+; Confirm that addrspace(7) globals appear in the SPIR-V output as distinct
+; OpVariables and in the OpEntryPoint interface. Three cases are tested:
 ;
-; @used_input has a real load. @dead_input is only in llvm.compiler.used.
+;   @used_input   -- has a real load in @main.
+;   @dead_input   -- no IR uses; protected by llvm.compiler.used.
+;   @bare_input   -- no IR uses; NOT in llvm.compiler.used.
 ;
-; %[[#USED]] and %[[#DEAD]] capture different IDs (Location 0 vs 1), so the
-; OpVariable checks below require two distinct OpVariable Input instructions.
+; @bare_input tests that the backend emits any addrspace(7) global that reaches
+; it without an initializer and without real function uses, regardless of
+; llvm.compiler.used.
 ;
-; Without the processGlobalValue fix in SPIRVEmitIntrinsics.cpp, @dead_input
-; gets no spv_unref_global, buildGlobalVariable is never called for it, and
-; both OpDecorate Location 1 and the second OpVariable Input are absent.
-;
-; The OpEntryPoint check pins the interface to exactly three IDs. The SPIR-V
+; The OpEntryPoint check pins the interface to exactly four IDs. The SPIR-V
 ; backend builds the interface by iterating every Input/Output OpVariable in
-; the module, so combined with the three OpVariable checks below this proves
-; all three preserved variables appear in OpEntryPoint regardless of the
+; the module, so combined with the four OpVariable checks below this proves
+; all four preserved variables appear in OpEntryPoint regardless of the
 ; backend's interface ordering.
 
 ; CHECK: OpCapability Shader
-; CHECK: OpEntryPoint Vertex %[[#]] "main" %[[#]] %[[#]] %[[#]]
+; CHECK: OpEntryPoint Vertex %[[#]] "main" %[[#]] %[[#]] %[[#]] %[[#]]
 
 ; CHECK-DAG: OpDecorate %[[#USED:]] Location 0
 ; CHECK-DAG: OpDecorate %[[#DEAD:]] Location 1
+; CHECK-DAG: OpDecorate %[[#BARE:]] Location 2
 ; CHECK-DAG: OpDecorate %[[#OUTPUT:]] Location 0
 ; CHECK-DAG: %[[#USED]] = OpVariable %[[#]] Input
 ; CHECK-DAG: %[[#DEAD]] = OpVariable %[[#]] Input
+; CHECK-DAG: %[[#BARE]] = OpVariable %[[#]] Input
 ; CHECK-DAG: %[[#OUTPUT]] = OpVariable %[[#]] Output
 
 @used_input = external hidden thread_local addrspace(7) global float,
     !spirv.Decorations !0
 @dead_input = external hidden thread_local addrspace(7) global float,
     !spirv.Decorations !2
+@bare_input = external hidden thread_local addrspace(7) global float,
+    !spirv.Decorations !4
 
 @llvm.compiler.used = appending global [1 x ptr addrspace(7)]
     [ptr addrspace(7) @dead_input], section "llvm.metadata"
@@ -46,7 +48,7 @@ define void @main() #0 {
 }
 
 @output = external hidden thread_local addrspace(8) global float,
-    !spirv.Decorations !4
+    !spirv.Decorations !6
 
 attributes #0 = { "hlsl.shader"="vertex" }
 
@@ -58,6 +60,10 @@ attributes #0 = { "hlsl.shader"="vertex" }
 !2 = !{!3}
 !3 = !{i32 30, i32 1}
 
-; Location = 0 on @output
+; Location = 2 on @bare_input
 !4 = !{!5}
-!5 = !{i32 30, i32 0}
+!5 = !{i32 30, i32 2}
+
+; Location = 0 on @output
+!6 = !{!7}
+!7 = !{i32 30, i32 0}

>From 8e00dde25ea9db09e7a0ad956e009fa7426df632 Mon Sep 17 00:00:00 2001
From: Diego Novillo <[email protected]>
Date: Thu, 4 Jun 2026 14:43:33 -0400
Subject: [PATCH 6/8] Fix formatting.

---
 llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp 
b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
index ee05b9d26f9b4..e879319649320 100644
--- a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
@@ -2503,7 +2503,8 @@ static bool isArtificialGlobal(StringRef Name) {
   return isUseListGlobal(Name) || Name == "llvm.global.annotations";
 }
 
-// Returns true if every use of GV traces back to llvm.compiler.used or 
llvm.used.
+// Returns true if every use of GV traces back to llvm.compiler.used or
+// llvm.used.
 static bool hasOnlyArtificialUses(const GlobalVariable &GV) {
   SmallPtrSet<const Value *, 8> Visited;
   SmallVector<const Value *> Stack(GV.users());

>From fdfdd6207941e494f758011de0f5f9b5fe21bba7 Mon Sep 17 00:00:00 2001
From: Diego Novillo <[email protected]>
Date: Fri, 5 Jun 2026 12:27:57 -0400
Subject: [PATCH 7/8] Add fspv-preserve-interface to ForwardedArguments.

---
 clang/lib/Driver/ToolChains/Clang.cpp | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/clang/lib/Driver/ToolChains/Clang.cpp 
b/clang/lib/Driver/ToolChains/Clang.cpp
index 8a0efd70e6c0d..1a0042d226ff0 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -3822,7 +3822,8 @@ static void RenderHLSLOptions(const ArgList &Args, 
ArgStringList &CmdArgs,
       options::OPT_fdx_rootsignature_define,
       options::OPT_fdx_rootsignature_version,
       options::OPT_fhlsl_spv_use_unknown_image_format,
-      options::OPT_fhlsl_spv_enable_maximal_reconvergence};
+      options::OPT_fhlsl_spv_enable_maximal_reconvergence,
+      options::OPT_fhlsl_spv_preserve_interface};
   if (!types::isHLSL(InputType))
     return;
   for (const auto &Arg : ForwardedArguments)

>From de49733b88c8e8a68b9059d5c89afe16375a9b5f Mon Sep 17 00:00:00 2001
From: Diego Novillo <[email protected]>
Date: Sat, 6 Jun 2026 13:36:18 -0400
Subject: [PATCH 8/8] Update clang/lib/CodeGen/CGHLSLRuntime.cpp

Co-authored-by: Justin Bogner <[email protected]>
---
 clang/lib/CodeGen/CGHLSLRuntime.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang/lib/CodeGen/CGHLSLRuntime.cpp 
b/clang/lib/CodeGen/CGHLSLRuntime.cpp
index 731668b14a1ce..61343af225937 100644
--- a/clang/lib/CodeGen/CGHLSLRuntime.cpp
+++ b/clang/lib/CodeGen/CGHLSLRuntime.cpp
@@ -602,7 +602,7 @@ void CGHLSLRuntime::finishCodeGen() {
     const ASTContext &Ctx = CGM.getContext();
     unsigned InputAS = Ctx.getTargetAddressSpace(LangAS::hlsl_input);
     unsigned OutputAS = Ctx.getTargetAddressSpace(LangAS::hlsl_output);
-    SmallVector<GlobalValue *, 8> InterfaceVars;
+    SmallVector<GlobalValue *> InterfaceVars;
     for (GlobalVariable &GV : M.globals()) {
       unsigned AS = GV.getAddressSpace();
       if (AS == InputAS || AS == OutputAS)

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

Reply via email to