[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