[llvm] [llvm] don't merge constants with COMDAT (PR #192477)

via llvm-commits llvm-commits at lists.llvm.org
Sun Apr 19 13:10:37 PDT 2026


https://github.com/8051Enthusiast updated https://github.com/llvm/llvm-project/pull/192477

>From e47be65bbede730b4e4dfc4e662bc7203253affb Mon Sep 17 00:00:00 2001
From: 8051enthusiast <8051enthusiast at protonmail.com>
Date: Thu, 16 Apr 2026 17:20:23 +0200
Subject: [PATCH] [llvm] don't merge constants with differing COMDAT

After merging #190995 (now reverted) the CI failed because
ConstantMerge currently merges constants with differing COMDATs.
This can result in a function referencing globals with a different
COMDAT than before the merge, producing a linking error when that
global gets discarded.
---
 llvm/lib/Transforms/IPO/ConstantMerge.cpp     | 27 +++++++++++++++++++
 .../Transforms/ConstantMerge/dont-merge.ll    | 22 +++++++++++++++
 .../Transforms/ConstantMerge/merge-comdat.ll  | 18 +++++++++++++
 3 files changed, 67 insertions(+)
 create mode 100644 llvm/test/Transforms/ConstantMerge/merge-comdat.ll

diff --git a/llvm/lib/Transforms/IPO/ConstantMerge.cpp b/llvm/lib/Transforms/IPO/ConstantMerge.cpp
index 282bcabc34534..636d8de535456 100644
--- a/llvm/lib/Transforms/IPO/ConstantMerge.cpp
+++ b/llvm/lib/Transforms/IPO/ConstantMerge.cpp
@@ -92,14 +92,41 @@ isUnmergeableGlobal(GlobalVariable *GV,
 }
 
 enum class CanMerge { No, Yes };
+
+static CanMerge isMergeableComdat(GlobalVariable *Old, GlobalVariable *New) {
+  auto OldComdat = Old->getComdat();
+  auto NewComdat = New->getComdat();
+
+  if (OldComdat == NewComdat)
+    return CanMerge::Yes;
+
+  // If one of the globals doesn't have comdat, we can merge the globals into
+  // a global without comdat. The references to the global with comdat
+  // effectively get moved to the one without comdat, and the global with comdat
+  // has local linkage so it is allowed to be removed after having been merged
+  if ((!OldComdat && New->hasLocalLinkage()) ||
+      (!NewComdat && Old->hasLocalLinkage()))
+    return CanMerge::Yes;
+
+  return CanMerge::No;
+}
+
 static CanMerge makeMergeable(GlobalVariable *Old, GlobalVariable *New) {
   if (!Old->hasGlobalUnnamedAddr() && !New->hasGlobalUnnamedAddr())
     return CanMerge::No;
   if (Old->hasMetadataOtherThanDebugLoc())
     return CanMerge::No;
+  // Merging constants with different comdats means one group cannot in general
+  // be dropped independently without the other group now having an invalid
+  // reference to the dropped constant
+  if (isMergeableComdat(Old, New) == CanMerge::No)
+    return CanMerge::No;
   assert(!New->hasMetadataOtherThanDebugLoc());
   if (!Old->hasGlobalUnnamedAddr())
     New->setUnnamedAddr(GlobalValue::UnnamedAddr::None);
+  if (!Old->hasComdat())
+    New->setComdat(nullptr);
+
   return CanMerge::Yes;
 }
 
diff --git a/llvm/test/Transforms/ConstantMerge/dont-merge.ll b/llvm/test/Transforms/ConstantMerge/dont-merge.ll
index 0d90d82809693..4f2f734cd1e77 100644
--- a/llvm/test/Transforms/ConstantMerge/dont-merge.ll
+++ b/llvm/test/Transforms/ConstantMerge/dont-merge.ll
@@ -92,3 +92,25 @@ define void @test5(ptr %P1, ptr %P2) {
         store ptr @T5ua, ptr %P2
         ret void
 }
+
+$test6a = comdat any
+$test6b = comdat any
+
+; If @T6a were to get merged into @T6b and the $test6b comdat group gets dropped,
+; @test6a would have a (now invalid) reference to the dropped @T6b constant
+
+ at T6a = private unnamed_addr constant i32 666, comdat($test6a)
+ at T6b = private unnamed_addr constant i32 666, comdat($test6b)
+
+; CHECK: @T6a
+; CHECK: @T6b
+
+define void @test6a(ptr %P) comdat {
+        store ptr @T6a, ptr %P
+        ret void
+}
+
+define void @test6b(ptr %P) comdat {
+        store ptr @T6b, ptr %P
+        ret void
+}
diff --git a/llvm/test/Transforms/ConstantMerge/merge-comdat.ll b/llvm/test/Transforms/ConstantMerge/merge-comdat.ll
new file mode 100644
index 0000000000000..93a767cd4abd3
--- /dev/null
+++ b/llvm/test/Transforms/ConstantMerge/merge-comdat.ll
@@ -0,0 +1,18 @@
+; RUN: opt -passes=constmerge -S < %s | FileCheck %s
+
+; @ComdatGlobal can be merged into @RegularGlobal which does not have
+; a comdat group and should not get dropped by the linker
+
+$test = comdat any
+
+ at ComdatGlobal = private unnamed_addr constant i32 111, comdat($test)
+ at RegularGlobal = constant i32 111
+
+; CHECK NOT: @ComdatGlobal
+; CHECK: @RegularGlobal
+
+define void @test(ptr %P) comdat {
+        store ptr @ComdatGlobal, ptr %P
+        store ptr @RegularGlobal, ptr %P
+        ret void
+}



More information about the llvm-commits mailing list