From 50298bd8e58727fa25eaf4b15cd29798a68f1448 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Fri, 24 Oct 2025 06:49:07 +0000 Subject: [PATCH] [wasm][IRGen] Stop attaching COMDATs to dead stub methods When generating dead stub methods for vtable entries, we should not attach COMDATs to their definitions. This is because such stubs may be referenced only through aliases, and the presence of a COMDAT on the stub definition would cause the aliased symbol, we want to keep, to be discarded from the symbol table when other object files also have a dead stub method. Given the following two object files generated: ```mermaid graph TD subgraph O0[A.swift.o] subgraph C0[COMDAT Group swift_dead_method_stub] D0_0["[Def] swift_dead_method_stub"] end S0_0["[Symbol] swift_dead_method_stub"] --> D0_0 S1["[Symbol] C1.dead"] --alias--> D0_0 end subgraph O1[B.swift.o] subgraph C1[COMDAT Group swift_dead_method_stub] D0_1["[Def] swift_dead_method_stub"] end S0_1["[Symbol] swift_dead_method_stub"] --> D0_1 S2["[Symbol] C2.beef"] --alias--> D0_1 end ``` When linking these two object files, the linker will pick one of the COMDAT groups of `swift_dead_method_stub`. The other COMDAT group will be discarded, along with all symbols aliased to the discarded definition, even though those aliased symbols (`C1.dead` and `C2.beef`) are the ones we want to keep to construct vtables. This change stops attaching COMDATs to dead stub method definitions. This effectively only affects WebAssembly targets because MachO's `supportsCOMDAT` returns false, and COFF targets don't use `linkonce_odr` for dead stub methods. The COMDAT was added in 7e8f7824577335743ef33cad3b66b437dcf1797a --- include/swift/IRGen/Linking.h | 15 ++++++--- lib/IRGen/GenDecl.cpp | 2 +- test/IRGen/dead-stub-method-dedup.swift | 42 +++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 5 deletions(-) create mode 100644 test/IRGen/dead-stub-method-dedup.swift diff --git a/include/swift/IRGen/Linking.h b/include/swift/IRGen/Linking.h index 7570d5ac7c3fd..d60a3d977d90f 100644 --- a/include/swift/IRGen/Linking.h +++ b/include/swift/IRGen/Linking.h @@ -1886,7 +1886,7 @@ class ApplyIRLinkage { IRLinkage IRL; public: ApplyIRLinkage(IRLinkage IRL) : IRL(IRL) {} - void to(llvm::GlobalValue *GV, bool definition = true) const { + void to(llvm::GlobalValue *GV, bool nonAliasedDefinition = true) const { llvm::Module *M = GV->getParent(); const llvm::Triple Triple(M->getTargetTriple()); @@ -1899,9 +1899,16 @@ class ApplyIRLinkage { if (Triple.isOSBinFormatELF()) return; - // COMDATs cannot be applied to declarations. If we have a definition, - // apply the COMDAT. - if (definition) + // COMDATs cannot be applied to declarations. Also, definitions that are + // exported through aliases should not have COMDATs, because the alias + // itself might represent an externally visible symbol but such symbols + // are discarded from the symtab when other object files have a COMDAT + // group with the same signature. + // + // If we have a non-aliased definition with ODR-based linkage, attach it + // to a COMDAT group so that duplicate definitions across object files + // can be merged by the linker. + if (nonAliasedDefinition) if (IRL.Linkage == llvm::GlobalValue::LinkOnceODRLinkage || IRL.Linkage == llvm::GlobalValue::WeakODRLinkage) if (Triple.supportsCOMDAT()) diff --git a/lib/IRGen/GenDecl.cpp b/lib/IRGen/GenDecl.cpp index cf41c9e353d0c..971fbc1edeb0f 100644 --- a/lib/IRGen/GenDecl.cpp +++ b/lib/IRGen/GenDecl.cpp @@ -2201,7 +2201,7 @@ void IRGenModule::emitVTableStubs() { &Module); ApplyIRLinkage(canLinkOnce ? IRLinkage::InternalLinkOnceODR : IRLinkage::Internal) - .to(stub); + .to(stub, /* nonAliasedDefinition */ false); stub->setAttributes(constructInitialAttributes()); stub->setCallingConv(DefaultCC); auto *entry = llvm::BasicBlock::Create(getLLVMContext(), "entry", stub); diff --git a/test/IRGen/dead-stub-method-dedup.swift b/test/IRGen/dead-stub-method-dedup.swift new file mode 100644 index 0000000000000..c5d75dddaa34f --- /dev/null +++ b/test/IRGen/dead-stub-method-dedup.swift @@ -0,0 +1,42 @@ +// RUN: %empty-directory(%t) +// RUN: split-file --leading-lines %s %t + +// Note: IRGen uses internal linkage instead of linkonce_odr only for COFF for dead stub methods. +// UNSUPPORTED: OS=windows-msvc + +// Ensure that swift_dead_method_stub is emitted without comdat linkage +// RUN: %target-swift-frontend -target %target-swift-5.1-abi-triple -parse-as-library -O -module-name A -c -primary-file %t/A.swift %t/B.swift -emit-ir -o - | %FileCheck %s -check-prefix CHECK +// RUN: %target-swift-frontend -target %target-swift-5.1-abi-triple -parse-as-library -O -module-name A -c %t/A.swift -primary-file %t/B.swift -emit-ir -o - | %FileCheck %s -check-prefix CHECK + +// CHECK-LABEL: define {{(linkonce_odr )?}}hidden void @_swift_dead_method_stub( +// CHECK-NOT: comdat +// CHECK: { + + +// Ensure that link-time deduplication for swift_dead_method_stub works correctly +// RUN: %target-swift-frontend -target %target-swift-5.1-abi-triple -parse-as-library -O -module-name A -c -primary-file %t/A.swift %t/B.swift -o %t/A.swift.o +// RUN: %target-swift-frontend -target %target-swift-5.1-abi-triple -parse-as-library -O -module-name A -c %t/A.swift -primary-file %t/B.swift -o %t/B.swift.o +// RUN: %target-build-swift -target %target-swift-5.1-abi-triple %t/B.swift.o %t/A.swift.o -o %t/a.out + +//--- A.swift + +// Define an open class with a dead vtable entry +class C1 { + private func dead() {} +} + +//--- B.swift + +class C2: C1 { + // Define another dead vtable entry to ensure that this object file + // also should have dead vtable stub definition + private func beef() {} +} + +@main +struct Entry { + static func main() { + _ = C2() + } +} +