Author: Diego Novillo
Date: 2026-06-17T14:41:43+02:00
New Revision: 60ad6ed5dfd225db07f96eb7bbfbf2f8f2f3a8c4

URL: 
https://github.com/llvm/llvm-project/commit/60ad6ed5dfd225db07f96eb7bbfbf2f8f2f3a8c4
DIFF: 
https://github.com/llvm/llvm-project/commit/60ad6ed5dfd225db07f96eb7bbfbf2f8f2f3a8c4.diff

LOG: Reapply "[clang][SPIR-V] Implement -fspv-preserve-interface (#196404)" 
(#204249)

This reverts commit 6746898d2bfc086947d86715e065f8dbf74e9690.

[clang][SPIR-V] Re-land -fspv-preserve-interface (#196404)

This had been reverted in #202558 due to a missing symbol
(llvm::removeFromUsedLists). That symbol is now available in main.

@jmmartinez @jplehr could you please review?

I did not need to modify the original PR #196404 . As I described in
https://github.com/llvm/llvm-project/pull/196404#issuecomment-4661219367,
the build failure on `main` was not due to it.

Tested locally. Will watch the bots once the PR is created.

Added: 
    clang/test/CodeGenHLSL/preserve-interface-dce.hlsl
    clang/test/CodeGenHLSL/preserve-interface.hlsl
    clang/test/Driver/dxc_fspv_preserve_interface.hlsl
    llvm/test/CodeGen/SPIRV/preserve-interface-dce.ll
    llvm/test/CodeGen/SPIRV/preserve-interface.ll

Modified: 
    clang/include/clang/Basic/LangOptions.def
    clang/include/clang/Options/Options.td
    clang/lib/CodeGen/CGHLSLRuntime.cpp
    clang/lib/Driver/ToolChains/Clang.cpp
    llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp

Removed: 
    


################################################################################
diff  --git a/clang/include/clang/Basic/LangOptions.def 
b/clang/include/clang/Basic/LangOptions.def
index d3ec6bc1ebd23..75267ffea941d 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 a4b9cb802af4d..fdc288b27017d 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -9805,6 +9805,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 40b29559c2a1c..4ecab6ba79553 100644
--- a/clang/lib/CodeGen/CGHLSLRuntime.cpp
+++ b/clang/lib/CodeGen/CGHLSLRuntime.cpp
@@ -867,6 +867,21 @@ 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.
+    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 == InputAS || AS == OutputAS)
+        InterfaceVars.push_back(&GV);
+    }
+    if (!InterfaceVars.empty())
+      appendToCompilerUsed(M, InterfaceVars);
+  }
+
   generateGlobalCtorDtorCalls();
 }
 

diff  --git a/clang/lib/Driver/ToolChains/Clang.cpp 
b/clang/lib/Driver/ToolChains/Clang.cpp
index 7af2d41c2856e..dcab2e41391bb 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -3827,7 +3827,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)

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 e2f061cc552b6..e84df720efd66 100644
--- a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
@@ -2600,15 +2600,47 @@ 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.
+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 (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);
@@ -2693,7 +2725,9 @@ void 
SPIRVEmitIntrinsics::processGlobalValue(GlobalVariable &GV,
         Intrinsic::spv_init_global, {GV.getType(), Ty}, {&GV, Const});
     InitInst->setArgOperand(1, InitOp);
   }
-  if (!Init && GV.use_empty())
+  // 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-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..b61c8e487b6cf
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/preserve-interface.ll
@@ -0,0 +1,69 @@
+; 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 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 in @main.
+;   @dead_input   -- no IR uses; protected by llvm.compiler.used.
+;   @bare_input   -- no IR uses; NOT in llvm.compiler.used.
+;
+; @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.
+;
+; 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 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-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
+
[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 !6
+
+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 = 2 on @bare_input
+!4 = !{!5}
+!5 = !{i32 30, i32 2}
+
+; Location = 0 on @output
+!6 = !{!7}
+!7 = !{i32 30, i32 0}


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

Reply via email to