[llvm-branch-commits] [llvm] [llvm][GlobalOpt] Preserve COMDAT during SRA splitting (PR #220721)

Miguel A. Arroyo via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Thu Sep 17 09:04:10 PDT 2026


https://github.com/mayanez updated https://github.com/llvm/llvm-project/pull/220721

>From bcc840960b39f905f4034931116983c661d02a0c Mon Sep 17 00:00:00 2001
From: Miguel Arroyo <Miguel.Arroyo at rockstargames.com>
Date: Wed, 2 Sep 2026 14:24:16 -0700
Subject: [PATCH 1/3] [llvm][GlobalOpt] Preserve COMDATs during SRA splitting

---
 llvm/lib/Transforms/IPO/GlobalOpt.cpp         | 13 ++++-
 .../Transforms/GlobalOpt/globalsra-comdat.ll  | 50 +++++++++++++++++++
 2 files changed, 62 insertions(+), 1 deletion(-)
 create mode 100644 llvm/test/Transforms/GlobalOpt/globalsra-comdat.ll

diff --git a/llvm/lib/Transforms/IPO/GlobalOpt.cpp b/llvm/lib/Transforms/IPO/GlobalOpt.cpp
index 6f8d60ab8e2518..bdec78f09342ee 100644
--- a/llvm/lib/Transforms/IPO/GlobalOpt.cpp
+++ b/llvm/lib/Transforms/IPO/GlobalOpt.cpp
@@ -587,6 +587,7 @@ static GlobalVariable *SRAGlobal(GlobalVariable *GV, const DataLayout &DL) {
         GV->getThreadLocalMode(), GV->getAddressSpace());
     // Start out by copying attributes from the original, including alignment.
     NGV->copyAttributesFrom(GV);
+    NGV->setComdat(GV->getComdat());
     NewGlobals.insert({OffsetForTy, NGV});
 
     // Calculate the known alignment of the field.  If the original aggregate
@@ -656,7 +657,17 @@ static GlobalVariable *SRAGlobal(GlobalVariable *GV, const DataLayout &DL) {
   ++NumSRA;
 
   assert(NewGlobals.size() > 0);
-  return NewGlobals.begin()->second;
+
+  auto *FirstNewGV = NewGlobals.begin()->second;
+
+  // For COFF, the comdat must contain a member which has the same name as the
+  // group. We rename the first new global to match.
+  if (auto *C = FirstNewGV->getComdat()) {
+    auto ComdatName = C->getName();
+    FirstNewGV->setName(ComdatName);
+  }
+
+  return FirstNewGV;
 }
 
 /// Return true if all users of the specified value will trap if the value is
diff --git a/llvm/test/Transforms/GlobalOpt/globalsra-comdat.ll b/llvm/test/Transforms/GlobalOpt/globalsra-comdat.ll
new file mode 100644
index 00000000000000..d6ccb3ca4a88a0
--- /dev/null
+++ b/llvm/test/Transforms/GlobalOpt/globalsra-comdat.ll
@@ -0,0 +1,50 @@
+; RUN: opt < %s -S -passes=globalopt | FileCheck %s
+
+; This global is externally_initialized, so if we split it into scalars we
+; should keep the original COMDAT grouping. 
+; CHECK: @a = internal unnamed_addr externally_initialized global i32 poison, comdat
+; CHECK-NOT: @a.1
+$a = comdat any
+ at a = internal externally_initialized global [2 x i32] poison, comdat, align 4
+
+; CHECK: @b = internal unnamed_addr externally_initialized global i32 poison, comdat
+; CHECK-NOT: @b.1
+$b = comdat any
+ at b = internal externally_initialized global {i32, i32} poison, comdat, align 4
+
+define i32 @foo() {
+; CHECK-LABEL: define i32 @foo
+entry:
+; This load uses the split global, but cannot be constant-propagated away.
+; CHECK: %0 = load i32, ptr @a
+  %0 = load i32, ptr @a, align 4
+  ret i32 %0
+}
+
+define i32 @bar() {
+; CHECK-LABEL: define i32 @bar
+entry:
+; This load uses the split global, but cannot be constant-propagated away.
+; CHECK: %0 = load i32, ptr @b
+  %0 = load i32, ptr @b, align 4
+  ret i32 %0
+}
+
+define void @init() {
+; CHECK-LABEL: define void @init
+entry:
+; This store uses the split global, but cannot be constant-propagated away.
+; CHECK: store i32 1, ptr @a
+  store i32 1, ptr @a, align 4
+; This store can be removed, because the second element of @a is never read.
+; CHECK-NOT: store i32 2, ptr @a.1
+  store i32 2, ptr getelementptr inbounds ([2 x i32], ptr @a, i32 0, i32 1), align 4
+
+; This store uses the split global, but cannot be constant-propagated away.
+; CHECK: store i32 3, ptr @b
+  store i32 3, ptr @b, align 4
+; This store can be removed, because the second element of @b is never read.
+; CHECK-NOT: store i32 4, ptr @b.1
+  store i32 4, ptr getelementptr inbounds ({i32, i32}, ptr @b, i32 0, i32 1), align 4
+  ret void
+}

>From 0d771d4c16db650c21160c15d20cc701d98339bb Mon Sep 17 00:00:00 2001
From: Miguel Arroyo <Miguel.Arroyo at rockstargames.com>
Date: Wed, 9 Sep 2026 19:44:29 -0700
Subject: [PATCH 2/3] Use Dummy Global when doing SRA

---
 llvm/lib/Transforms/IPO/GlobalOpt.cpp         | 24 ++++++--
 .../Transforms/GlobalOpt/globalsra-comdat.ll  | 61 +++++--------------
 2 files changed, 36 insertions(+), 49 deletions(-)

diff --git a/llvm/lib/Transforms/IPO/GlobalOpt.cpp b/llvm/lib/Transforms/IPO/GlobalOpt.cpp
index bdec78f09342ee..aabe2f296bb5c1 100644
--- a/llvm/lib/Transforms/IPO/GlobalOpt.cpp
+++ b/llvm/lib/Transforms/IPO/GlobalOpt.cpp
@@ -660,11 +660,16 @@ static GlobalVariable *SRAGlobal(GlobalVariable *GV, const DataLayout &DL) {
 
   auto *FirstNewGV = NewGlobals.begin()->second;
 
-  // For COFF, the comdat must contain a member which has the same name as the
-  // group. We rename the first new global to match.
+  // For COFF, the comdat must contain a member which has the
+  // same name as the group.
   if (auto *C = FirstNewGV->getComdat()) {
-    auto ComdatName = C->getName();
-    FirstNewGV->setName(ComdatName);
+    auto *DummyGV = new GlobalVariable(
+        *FirstNewGV->getParent(), Type::getInt1Ty(FirstNewGV->getContext()),
+        false, FirstNewGV->getLinkage(),
+        ConstantInt::getFalse(FirstNewGV->getContext()), C->getName(),
+        FirstNewGV, FirstNewGV->getThreadLocalMode(),
+        FirstNewGV->getAddressSpace());
+    DummyGV->setComdat(C);
   }
 
   return FirstNewGV;
@@ -1536,6 +1541,17 @@ processInternalGlobal(GlobalVariable *GV, const GlobalStatus &GS,
       Changed = CleanupConstantGlobalUsers(GV, DL);
     }
 
+    // For COFF, the Comdat leader must be preserved.
+    if (auto *C = GV->getComdat()) {
+      auto IsComdatLeaderWithUses =
+          C->getName() == GV->getName() && C->getUsers().size() > 1;
+      if (IsComdatLeaderWithUses) {
+        LLVM_DEBUG(dbgs() << "GLOBAL IS COMDAT LEADER WITH USES: " << *GV
+                          << "\n");
+        return Changed;
+      }
+    }
+
     // If the global is dead now, delete it.
     if (GV->use_empty()) {
       GV->eraseFromParent();
diff --git a/llvm/test/Transforms/GlobalOpt/globalsra-comdat.ll b/llvm/test/Transforms/GlobalOpt/globalsra-comdat.ll
index d6ccb3ca4a88a0..1367abe4eb19d0 100644
--- a/llvm/test/Transforms/GlobalOpt/globalsra-comdat.ll
+++ b/llvm/test/Transforms/GlobalOpt/globalsra-comdat.ll
@@ -1,50 +1,21 @@
 ; RUN: opt < %s -S -passes=globalopt | FileCheck %s
 
-; This global is externally_initialized, so if we split it into scalars we
-; should keep the original COMDAT grouping. 
-; CHECK: @a = internal unnamed_addr externally_initialized global i32 poison, comdat
-; CHECK-NOT: @a.1
-$a = comdat any
- at a = internal externally_initialized global [2 x i32] poison, comdat, align 4
+$x = comdat any
+ at x = internal global [2 x i32] zeroinitializer, comdat, align 4
+; CHECK: @x = internal unnamed_addr global i1 false, comdat
+; CHECK: @x.0 = internal unnamed_addr global i32 0, comdat($x), align 4
+; CHECK: @x.1 = internal unnamed_addr global i32 0, comdat($x), align 4
 
-; CHECK: @b = internal unnamed_addr externally_initialized global i32 poison, comdat
-; CHECK-NOT: @b.1
-$b = comdat any
- at b = internal externally_initialized global {i32, i32} poison, comdat, align 4
-
-define i32 @foo() {
-; CHECK-LABEL: define i32 @foo
-entry:
-; This load uses the split global, but cannot be constant-propagated away.
-; CHECK: %0 = load i32, ptr @a
-  %0 = load i32, ptr @a, align 4
-  ret i32 %0
-}
-
-define i32 @bar() {
-; CHECK-LABEL: define i32 @bar
-entry:
-; This load uses the split global, but cannot be constant-propagated away.
-; CHECK: %0 = load i32, ptr @b
-  %0 = load i32, ptr @b, align 4
-  ret i32 %0
+define dso_local i32 @f() {
+  %1 = load i32, ptr @x, align 4
+  %2 = add nsw i32 %1, 1
+  store i32 %2, ptr @x, align 4
+  ret i32 %2
 }
 
-define void @init() {
-; CHECK-LABEL: define void @init
-entry:
-; This store uses the split global, but cannot be constant-propagated away.
-; CHECK: store i32 1, ptr @a
-  store i32 1, ptr @a, align 4
-; This store can be removed, because the second element of @a is never read.
-; CHECK-NOT: store i32 2, ptr @a.1
-  store i32 2, ptr getelementptr inbounds ([2 x i32], ptr @a, i32 0, i32 1), align 4
-
-; This store uses the split global, but cannot be constant-propagated away.
-; CHECK: store i32 3, ptr @b
-  store i32 3, ptr @b, align 4
-; This store can be removed, because the second element of @b is never read.
-; CHECK-NOT: store i32 4, ptr @b.1
-  store i32 4, ptr getelementptr inbounds ({i32, i32}, ptr @b, i32 0, i32 1), align 4
-  ret void
-}
+define dso_local i32 @f2() {
+  %1 = load i32, ptr getelementptr inbounds ([2 x i32], ptr @x, i64 0, i64 1), align 4
+  %2 = add nsw i32 %1, 1
+  store i32 %2, ptr getelementptr inbounds ([2 x i32], ptr @x, i64 0, i64 1), align 4
+  ret i32 %2
+}
\ No newline at end of file

>From 942c67eb4f57ed68cba7e3c6c1dc15a64adcf6c1 Mon Sep 17 00:00:00 2001
From: Miguel Arroyo <Miguel.Arroyo at rockstargames.com>
Date: Thu, 17 Sep 2026 09:02:36 -0700
Subject: [PATCH 3/3] Copy attributes into dummy and use [0 x i8]

---
 llvm/lib/Transforms/IPO/GlobalOpt.cpp              | 7 +++++--
 llvm/test/Transforms/GlobalOpt/globalsra-comdat.ll | 2 +-
 2 files changed, 6 insertions(+), 3 deletions(-)

diff --git a/llvm/lib/Transforms/IPO/GlobalOpt.cpp b/llvm/lib/Transforms/IPO/GlobalOpt.cpp
index aabe2f296bb5c1..024a8315be7d3f 100644
--- a/llvm/lib/Transforms/IPO/GlobalOpt.cpp
+++ b/llvm/lib/Transforms/IPO/GlobalOpt.cpp
@@ -663,13 +663,16 @@ static GlobalVariable *SRAGlobal(GlobalVariable *GV, const DataLayout &DL) {
   // For COFF, the comdat must contain a member which has the
   // same name as the group.
   if (auto *C = FirstNewGV->getComdat()) {
+    Type *GlobalType = ArrayType::get(Type::getInt8Ty(GV->getContext()),
+                                    0);
     auto *DummyGV = new GlobalVariable(
-        *FirstNewGV->getParent(), Type::getInt1Ty(FirstNewGV->getContext()),
+        *FirstNewGV->getParent(), GlobalType,
         false, FirstNewGV->getLinkage(),
-        ConstantInt::getFalse(FirstNewGV->getContext()), C->getName(),
+        UndefValue::get(GlobalType), C->getName(),
         FirstNewGV, FirstNewGV->getThreadLocalMode(),
         FirstNewGV->getAddressSpace());
     DummyGV->setComdat(C);
+    DummyGV->copyAttributesFrom(FirstNewGV);
   }
 
   return FirstNewGV;
diff --git a/llvm/test/Transforms/GlobalOpt/globalsra-comdat.ll b/llvm/test/Transforms/GlobalOpt/globalsra-comdat.ll
index 1367abe4eb19d0..75ae33eb38b8e0 100644
--- a/llvm/test/Transforms/GlobalOpt/globalsra-comdat.ll
+++ b/llvm/test/Transforms/GlobalOpt/globalsra-comdat.ll
@@ -2,7 +2,7 @@
 
 $x = comdat any
 @x = internal global [2 x i32] zeroinitializer, comdat, align 4
-; CHECK: @x = internal unnamed_addr global i1 false, comdat
+; CHECK: @x = internal unnamed_addr global [0 x i8] undef, comdat
 ; CHECK: @x.0 = internal unnamed_addr global i32 0, comdat($x), align 4
 ; CHECK: @x.1 = internal unnamed_addr global i32 0, comdat($x), align 4
 



More information about the llvm-branch-commits mailing list