[lld] [llvm] [LTO] Check COMDAT key before marking the group non-prevailing (PR #222568)

via llvm-commits llvm-commits at lists.llvm.org
Thu Sep 10 02:43:15 PDT 2026


https://github.com/XinlongZHANG-Bob created https://github.com/llvm/llvm-project/pull/222568

  Regular LTO currently adds a COMDAT into NonPrevailingComdats when
  any member of the group is non-prevailing. This is not correct when
  the member is not the COMDAT key.

  The problem can be reproduced with the following code.

  lto-dtor-comdat.h:
  ```
    struct Base {
      virtual ~Base() {}
    };

    template <int N> struct Derived final : Base {
      ~Derived() {}
    };
  ```
  d0-only.cc:
  ```
    #include "lto-dtor-comdat.h"

    Derived<1> *make_derived() { return new Derived<1>; }

    void delete_derived(Derived<1> *p) { delete p; }
  ```
  d5.cc:
  ```
    #include "lto-dtor-comdat.h"

    template struct Derived<1>;
  ```
  Compile and link with:

    clang++ -std=c++17 -O1 -fPIC -flto=thin \
      -fwhole-program-vtables -c d0-only.cc -o d0-only.o

    clang++ -std=c++17 -O1 -fPIC -flto=thin \
      -fwhole-program-vtables -c d5.cc -o d5.o

    ld.lld -shared --unresolved-symbols=ignore-all \
      d0-only.o d5.o -o output.so

  The link fails with:

    Alias must point to a definition
    void (%struct.Derived*)* @_ZN7DerivedILi1EED1Ev
    LLVM ERROR: Broken module found, compilation aborted!

  d0-only.o defines D0 in a self-keyed D0 COMDAT. d5.o defines D0 and
  D2 in a D5 COMDAT, and D1 is an alias of D2.

  D0 from d5.o is non-prevailing, but D1 and D2 from d5.o are still
  prevailing. The current code sees that D0 belongs to the D5 COMDAT and
  marks the whole D5 COMDAT non-prevailing. As a result, D2 is also
  changed to available_externally, and D1 becomes an alias to a linker
  declaration.

  A non-prevailing member does not mean that its whole COMDAT is
  non-prevailing. Only add the COMDAT into NonPrevailingComdats when the
  non-prevailing GlobalObject has the same name as the COMDAT key. This
  also matches the existing ThinLTO handling.

>From c573eb728a5182914d01f6b7334816ace7b06d3e Mon Sep 17 00:00:00 2001
From: XinlongZHANG-Bob <zhangxinlong.bob at bytedance.com>
Date: Thu, 10 Sep 2026 17:40:49 +0800
Subject: [PATCH] [LTO] Check COMDAT key before marking the group
 non-prevailing

  Regular LTO currently adds a COMDAT into NonPrevailingComdats when
  any member of the group is non-prevailing. This is not correct when
  the member is not the COMDAT key.

  The problem can be reproduced with the following code.

  lto-dtor-comdat.h:
  ```
    struct Base {
      virtual ~Base() {}
    };

    template <int N> struct Derived final : Base {
      ~Derived() {}
    };
  ```
  d0-only.cc:
  ```
    #include "lto-dtor-comdat.h"

    Derived<1> *make_derived() { return new Derived<1>; }

    void delete_derived(Derived<1> *p) { delete p; }
  ```
  d5.cc:
  ```
    #include "lto-dtor-comdat.h"

    template struct Derived<1>;
  ```
  Compile and link with:

    clang++ -std=c++17 -O1 -fPIC -flto=thin \
      -fwhole-program-vtables -c d0-only.cc -o d0-only.o

    clang++ -std=c++17 -O1 -fPIC -flto=thin \
      -fwhole-program-vtables -c d5.cc -o d5.o

    ld.lld -shared --unresolved-symbols=ignore-all \
      d0-only.o d5.o -o output.so

  The link fails with:

    Alias must point to a definition
    void (%struct.Derived*)* @_ZN7DerivedILi1EED1Ev
    LLVM ERROR: Broken module found, compilation aborted!

  d0-only.o defines D0 in a self-keyed D0 COMDAT. d5.o defines D0 and
  D2 in a D5 COMDAT, and D1 is an alias of D2.

  D0 from d5.o is non-prevailing, but D1 and D2 from d5.o are still
  prevailing. The current code sees that D0 belongs to the D5 COMDAT and
  marks the whole D5 COMDAT non-prevailing. As a result, D2 is also
  changed to available_externally, and D1 becomes an alias to a linker
  declaration.

  A non-prevailing member does not mean that its whole COMDAT is
  non-prevailing. Only add the COMDAT into NonPrevailingComdats when the
  non-prevailing GlobalObject has the same name as the COMDAT key. This
  also matches the existing ThinLTO handling.
---
 .../Inputs/non-prevailing-comdat-member-a.ll  |  8 +++++
 .../Inputs/non-prevailing-comdat-member-b.ll  | 14 ++++++++
 .../ELF/lto/non-prevailing-comdat-member.ll   | 11 ++++++
 llvm/lib/LTO/LTO.cpp                          |  3 +-
 .../Inputs/non-prevailing-comdat-member-a.ll  |  8 +++++
 .../Inputs/non-prevailing-comdat-member-b.ll  | 14 ++++++++
 .../X86/non-prevailing-comdat-member.ll       | 34 +++++++++++++++++++
 7 files changed, 91 insertions(+), 1 deletion(-)
 create mode 100644 lld/test/ELF/lto/Inputs/non-prevailing-comdat-member-a.ll
 create mode 100644 lld/test/ELF/lto/Inputs/non-prevailing-comdat-member-b.ll
 create mode 100644 lld/test/ELF/lto/non-prevailing-comdat-member.ll
 create mode 100644 llvm/test/LTO/Resolution/X86/Inputs/non-prevailing-comdat-member-a.ll
 create mode 100644 llvm/test/LTO/Resolution/X86/Inputs/non-prevailing-comdat-member-b.ll
 create mode 100644 llvm/test/LTO/Resolution/X86/non-prevailing-comdat-member.ll

diff --git a/lld/test/ELF/lto/Inputs/non-prevailing-comdat-member-a.ll b/lld/test/ELF/lto/Inputs/non-prevailing-comdat-member-a.ll
new file mode 100644
index 0000000000000..982321f72c417
--- /dev/null
+++ b/lld/test/ELF/lto/Inputs/non-prevailing-comdat-member-a.ll
@@ -0,0 +1,8 @@
+target datalayout = "e-m:e-p270:32:32:32-p271:32:32:32-p272:64:64:64-i64:64-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-unknown-linux-gnu"
+
+$_ZN1AIiED0Ev = comdat any
+
+define linkonce_odr void @_ZN1AIiED0Ev(ptr %this) unnamed_addr comdat {
+  ret void
+}
diff --git a/lld/test/ELF/lto/Inputs/non-prevailing-comdat-member-b.ll b/lld/test/ELF/lto/Inputs/non-prevailing-comdat-member-b.ll
new file mode 100644
index 0000000000000..1f6d7849a0c29
--- /dev/null
+++ b/lld/test/ELF/lto/Inputs/non-prevailing-comdat-member-b.ll
@@ -0,0 +1,14 @@
+target datalayout = "e-m:e-p270:32:32:32-p271:32:32:32-p272:64:64:64-i64:64-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-unknown-linux-gnu"
+
+$_ZN1AIiED5Ev = comdat any
+
+ at _ZN1AIiED1Ev = weak_odr unnamed_addr alias void (ptr), ptr @_ZN1AIiED2Ev
+
+define weak_odr void @_ZN1AIiED2Ev(ptr %this) unnamed_addr comdat($_ZN1AIiED5Ev) {
+  ret void
+}
+
+define weak_odr void @_ZN1AIiED0Ev(ptr %this) unnamed_addr comdat($_ZN1AIiED5Ev) {
+  ret void
+}
diff --git a/lld/test/ELF/lto/non-prevailing-comdat-member.ll b/lld/test/ELF/lto/non-prevailing-comdat-member.ll
new file mode 100644
index 0000000000000..bcf9308744272
--- /dev/null
+++ b/lld/test/ELF/lto/non-prevailing-comdat-member.ll
@@ -0,0 +1,11 @@
+; REQUIRES: x86
+; RUN: llvm-as %p/Inputs/non-prevailing-comdat-member-a.ll -o %t.a.bc
+; RUN: llvm-as %p/Inputs/non-prevailing-comdat-member-b.ll -o %t.b.bc
+; RUN: ld.lld -shared %t.a.bc %t.b.bc -o %t.forward.so
+; RUN: llvm-nm -D %t.forward.so | FileCheck %s
+; RUN: ld.lld -shared %t.b.bc %t.a.bc -o %t.reverse.so
+; RUN: llvm-nm -D %t.reverse.so | FileCheck %s
+;
+; CHECK: W _ZN1AIiED0Ev
+; CHECK-NEXT: W _ZN1AIiED1Ev
+; CHECK-NEXT: W _ZN1AIiED2Ev
diff --git a/llvm/lib/LTO/LTO.cpp b/llvm/lib/LTO/LTO.cpp
index 4594c52fb5f6e..b2b3973971f52 100644
--- a/llvm/lib/LTO/LTO.cpp
+++ b/llvm/lib/LTO/LTO.cpp
@@ -1074,7 +1074,8 @@ LTO::addRegularLTO(InputFile &Input, ArrayRef<SymbolResolution> InputRes,
         // module (in linkRegularLTO), based on whether it is undefined.
         Mod.Keep.push_back(GV);
         GV->setLinkage(GlobalValue::AvailableExternallyLinkage);
-        if (GV->hasComdat())
+        if (GV->hasComdat() &&
+            GV->getComdat()->getName() == GV->getName())
           NonPrevailingComdats.insert(GV->getComdat());
         cast<GlobalObject>(GV)->setComdat(nullptr);
       }
diff --git a/llvm/test/LTO/Resolution/X86/Inputs/non-prevailing-comdat-member-a.ll b/llvm/test/LTO/Resolution/X86/Inputs/non-prevailing-comdat-member-a.ll
new file mode 100644
index 0000000000000..982321f72c417
--- /dev/null
+++ b/llvm/test/LTO/Resolution/X86/Inputs/non-prevailing-comdat-member-a.ll
@@ -0,0 +1,8 @@
+target datalayout = "e-m:e-p270:32:32:32-p271:32:32:32-p272:64:64:64-i64:64-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-unknown-linux-gnu"
+
+$_ZN1AIiED0Ev = comdat any
+
+define linkonce_odr void @_ZN1AIiED0Ev(ptr %this) unnamed_addr comdat {
+  ret void
+}
diff --git a/llvm/test/LTO/Resolution/X86/Inputs/non-prevailing-comdat-member-b.ll b/llvm/test/LTO/Resolution/X86/Inputs/non-prevailing-comdat-member-b.ll
new file mode 100644
index 0000000000000..1f6d7849a0c29
--- /dev/null
+++ b/llvm/test/LTO/Resolution/X86/Inputs/non-prevailing-comdat-member-b.ll
@@ -0,0 +1,14 @@
+target datalayout = "e-m:e-p270:32:32:32-p271:32:32:32-p272:64:64:64-i64:64-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-unknown-linux-gnu"
+
+$_ZN1AIiED5Ev = comdat any
+
+ at _ZN1AIiED1Ev = weak_odr unnamed_addr alias void (ptr), ptr @_ZN1AIiED2Ev
+
+define weak_odr void @_ZN1AIiED2Ev(ptr %this) unnamed_addr comdat($_ZN1AIiED5Ev) {
+  ret void
+}
+
+define weak_odr void @_ZN1AIiED0Ev(ptr %this) unnamed_addr comdat($_ZN1AIiED5Ev) {
+  ret void
+}
diff --git a/llvm/test/LTO/Resolution/X86/non-prevailing-comdat-member.ll b/llvm/test/LTO/Resolution/X86/non-prevailing-comdat-member.ll
new file mode 100644
index 0000000000000..fd53b3119726a
--- /dev/null
+++ b/llvm/test/LTO/Resolution/X86/non-prevailing-comdat-member.ll
@@ -0,0 +1,34 @@
+; Test that a non-prevailing non-key COMDAT member does not make the whole
+; COMDAT non-prevailing.
+;
+; input1 defines D0 in a self-keyed COMDAT. input2 defines D0 and D2 in a D5
+; COMDAT, with D1 aliasing D2. When input1's D0 prevails, input2's D2 must
+; remain a definition even though input2's D0 is non-prevailing.
+;
+; Also test the reverse input order, where the D5 D0 prevails and the
+; self-keyed D0 COMDAT is discarded.
+;
+; RUN: llvm-as %p/Inputs/non-prevailing-comdat-member-a.ll -o %t.a.bc
+; RUN: llvm-as %p/Inputs/non-prevailing-comdat-member-b.ll -o %t.b.bc
+; RUN: llvm-lto2 run %t.a.bc %t.b.bc --save-temps -o %t.forward \
+; RUN:   -r=%t.a.bc,_ZN1AIiED0Ev,px \
+; RUN:   -r=%t.b.bc,_ZN1AIiED0Ev, \
+; RUN:   -r=%t.b.bc,_ZN1AIiED1Ev,px \
+; RUN:   -r=%t.b.bc,_ZN1AIiED2Ev,px
+; RUN: llvm-dis %t.forward.0.0.preopt.bc -o - | FileCheck %s --check-prefix=FORWARD
+; RUN: llvm-lto2 run %t.b.bc %t.a.bc --save-temps -o %t.reverse \
+; RUN:   -r=%t.b.bc,_ZN1AIiED0Ev,px \
+; RUN:   -r=%t.b.bc,_ZN1AIiED1Ev,px \
+; RUN:   -r=%t.b.bc,_ZN1AIiED2Ev,px \
+; RUN:   -r=%t.a.bc,_ZN1AIiED0Ev,
+; RUN: llvm-dis %t.reverse.0.0.preopt.bc -o - | FileCheck %s --check-prefix=REVERSE
+;
+; FORWARD: $_ZN1AIiED5Ev = comdat any
+; FORWARD-DAG: @_ZN1AIiED1Ev = weak_odr unnamed_addr alias void (ptr), ptr @_ZN1AIiED2Ev
+; FORWARD-DAG: define weak_odr void @_ZN1AIiED0Ev(ptr %this) unnamed_addr comdat {
+; FORWARD-DAG: define weak_odr void @_ZN1AIiED2Ev(ptr %this) unnamed_addr comdat($_ZN1AIiED5Ev)
+;
+; REVERSE: $_ZN1AIiED5Ev = comdat any
+; REVERSE-DAG: @_ZN1AIiED1Ev = weak_odr unnamed_addr alias void (ptr), ptr @_ZN1AIiED2Ev
+; REVERSE-DAG: define weak_odr void @_ZN1AIiED2Ev(ptr %this) unnamed_addr comdat($_ZN1AIiED5Ev)
+; REVERSE-DAG: define weak_odr void @_ZN1AIiED0Ev(ptr %this) unnamed_addr comdat($_ZN1AIiED5Ev)



More information about the llvm-commits mailing list