[clang] [C++20] [Modules] [Itanium ABI] Generate the vtable in the module unit of dynamic classes (PR #75912)
Chuanqi Xu via cfe-commits
cfe-commits at lists.llvm.org
Mon Apr 1 19:17:35 PDT 2024
https://github.com/ChuanqiXu9 updated https://github.com/llvm/llvm-project/pull/75912
>From 7399d2417a4b758fa0a98da1f99f3b4ec0eb1046 Mon Sep 17 00:00:00 2001
From: Chuanqi Xu <yedeng.yd at linux.alibaba.com>
Date: Tue, 19 Dec 2023 17:00:59 +0800
Subject: [PATCH 1/3] [C++20] [Modules] [Itanium ABI] Generate the vtable in
the module unit of dynamic classes
Close https://github.com/llvm/llvm-project/issues/70585 and reflect
https://github.com/itanium-cxx-abi/cxx-abi/issues/170.
The significant change of the patch is: for dynamic classes attached to
module units, we generate the vtable to the attached module units
directly and the key functions for such classes is meaningless.
---
clang/lib/CodeGen/CGVTables.cpp | 28 ++++++++++----
clang/lib/CodeGen/CodeGenModule.cpp | 9 +++++
clang/lib/CodeGen/ItaniumCXXABI.cpp | 6 +++
clang/lib/Sema/SemaDecl.cpp | 9 +++++
clang/lib/Sema/SemaDeclCXX.cpp | 13 +++++--
clang/lib/Serialization/ASTReaderDecl.cpp | 13 ++++++-
clang/lib/Serialization/ASTWriterDecl.cpp | 13 +++++--
clang/test/CodeGenCXX/modules-vtable.cppm | 27 ++++++++-----
clang/test/CodeGenCXX/pr70585.cppm | 47 +++++++++++++++++++++++
9 files changed, 139 insertions(+), 26 deletions(-)
create mode 100644 clang/test/CodeGenCXX/pr70585.cppm
diff --git a/clang/lib/CodeGen/CGVTables.cpp b/clang/lib/CodeGen/CGVTables.cpp
index 8dee3f74b44b4e..54a2c725a10ba3 100644
--- a/clang/lib/CodeGen/CGVTables.cpp
+++ b/clang/lib/CodeGen/CGVTables.cpp
@@ -1046,6 +1046,11 @@ CodeGenModule::getVTableLinkage(const CXXRecordDecl *RD) {
if (!RD->isExternallyVisible())
return llvm::GlobalVariable::InternalLinkage;
+ // V-tables for non-template classes with an owning module are always
+ // uniquely emitted in that module.
+ if (Module *M = RD->getOwningModule(); M && M->isNamedModule())
+ return llvm::GlobalVariable::ExternalLinkage;
+
// We're at the end of the translation unit, so the current key
// function is fully correct.
const CXXMethodDecl *keyFunction = Context.getCurrentKeyFunction(RD);
@@ -1180,6 +1185,21 @@ bool CodeGenVTables::isVTableExternal(const CXXRecordDecl *RD) {
TSK == TSK_ExplicitInstantiationDefinition)
return false;
+ // Itanium C++ ABI [5.2.3]:
+ // Virtual tables for dynamic classes are emitted as follows:
+ //
+ // - If the class is templated, the tables are emitted in every object that
+ // references any of them.
+ // - Otherwise, if the class is attached to a module, the tables are uniquely
+ // emitted in the object for the module unit in which it is defined.
+ // - Otherwise, if the class has a key function (see below), the tables are
+ // emitted in the object for the translation unit containing the definition of
+ // the key function. This is unique if the key function is not inline.
+ // - Otherwise, the tables are emitted in every object that references any of
+ // them.
+ if (Module *M = RD->getOwningModule(); M && M->isNamedModule())
+ return M != CGM.getContext().getCurrentNamedModule();
+
// Otherwise, if the class doesn't have a key function (possibly
// anymore), the vtable must be defined here.
const CXXMethodDecl *keyFunction = CGM.getContext().getCurrentKeyFunction(RD);
@@ -1189,13 +1209,7 @@ bool CodeGenVTables::isVTableExternal(const CXXRecordDecl *RD) {
const FunctionDecl *Def;
// Otherwise, if we don't have a definition of the key function, the
// vtable must be defined somewhere else.
- if (!keyFunction->hasBody(Def))
- return true;
-
- assert(Def && "The body of the key function is not assigned to Def?");
- // If the non-inline key function comes from another module unit, the vtable
- // must be defined there.
- return Def->isInAnotherModuleUnit() && !Def->isInlineSpecified();
+ return !keyFunction->hasBody(Def);
}
/// Given that we're currently at the end of the translation unit, and
diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp
index e3ed5e90f2d36b..61369bedd147eb 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -6777,6 +6777,15 @@ void CodeGenModule::EmitTopLevelDecl(Decl *D) {
if (ES->hasExternalDefinitions(D) == ExternalASTSource::EK_Never)
DI->completeUnusedClass(*CRD);
}
+ // If we're emitting a dynamic class from the importable module we're
+ // emitting, we always need to emit the virtual table according to the ABI
+ // requirement.
+ if (CRD->getOwningModule() &&
+ CRD->getOwningModule()->isInterfaceOrPartition() &&
+ CRD->getDefinition() && CRD->isDynamicClass() &&
+ CRD->getOwningModule() == getContext().getCurrentNamedModule())
+ EmitVTable(CRD);
+
// Emit any static data members, they may be definitions.
for (auto *I : CRD->decls())
if (isa<VarDecl>(I) || isa<CXXRecordDecl>(I))
diff --git a/clang/lib/CodeGen/ItaniumCXXABI.cpp b/clang/lib/CodeGen/ItaniumCXXABI.cpp
index bdd53a192f828a..6473f356d5bb19 100644
--- a/clang/lib/CodeGen/ItaniumCXXABI.cpp
+++ b/clang/lib/CodeGen/ItaniumCXXABI.cpp
@@ -1802,6 +1802,12 @@ void ItaniumCXXABI::emitVTableDefinitions(CodeGenVTables &CGVT,
if (VTable->hasInitializer())
return;
+ // If the class is attached to a C++ named module other than the one
+ // we're currently compiling, the vtable should be defined there.
+ if (Module *M = RD->getOwningModule();
+ M && M->isNamedModule() && M != CGM.getContext().getCurrentNamedModule())
+ return;
+
ItaniumVTableContext &VTContext = CGM.getItaniumVTableContext();
const VTableLayout &VTLayout = VTContext.getVTableLayout(RD);
llvm::GlobalVariable::LinkageTypes Linkage = CGM.getVTableLinkage(RD);
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 0bd88ece2aa544..e77d57e3490cd6 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -18441,6 +18441,15 @@ void Sema::ActOnTagFinishDefinition(Scope *S, Decl *TagD,
if (NumInitMethods > 1 || !Def->hasInitMethod())
Diag(RD->getLocation(), diag::err_sycl_special_type_num_init_method);
}
+
+ // If we're defining a dynamic class in a module interface unit, we always
+ // need to produce the vtable for it even if the vtable is not used in the
+ // current TU.
+ //
+ // The case that the current class is not dynamic is handled in
+ // MarkVTableUsed.
+ if (getCurrentModule() && getCurrentModule()->isInterfaceOrPartition())
+ MarkVTableUsed(RD->getLocation(), RD, /*DefinitionRequired=*/true);
}
// Exit this scope of this tag's definition.
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index f32ff396f8a543..a8b02767eed5c1 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -18674,11 +18674,16 @@ bool Sema::DefineUsedVTables() {
bool DefineVTable = true;
- // If this class has a key function, but that key function is
- // defined in another translation unit, we don't need to emit the
- // vtable even though we're using it.
const CXXMethodDecl *KeyFunction = Context.getCurrentKeyFunction(Class);
- if (KeyFunction && !KeyFunction->hasBody()) {
+ // V-tables for non-template classes with an owning module are always
+ // uniquely emitted in that module.
+ if (Class->getOwningModule() &&
+ Class->getOwningModule()->isInterfaceOrPartition())
+ DefineVTable = true;
+ else if (KeyFunction && !KeyFunction->hasBody()) {
+ // If this class has a key function, but that key function is
+ // defined in another translation unit, we don't need to emit the
+ // vtable even though we're using it.
// The key function is in another translation unit.
DefineVTable = false;
TemplateSpecializationKind TSK =
diff --git a/clang/lib/Serialization/ASTReaderDecl.cpp b/clang/lib/Serialization/ASTReaderDecl.cpp
index a22f760408c634..c0e1a93cd3a32a 100644
--- a/clang/lib/Serialization/ASTReaderDecl.cpp
+++ b/clang/lib/Serialization/ASTReaderDecl.cpp
@@ -2274,7 +2274,10 @@ ASTDeclReader::VisitCXXRecordDeclImpl(CXXRecordDecl *D) {
// Lazily load the key function to avoid deserializing every method so we can
// compute it.
- if (WasDefinition) {
+ //
+ // The key function in named module is meaningless.
+ if (WasDefinition && (!D->getOwningModule() ||
+ !D->getOwningModule()->isInterfaceOrPartition())) {
DeclID KeyFn = readDeclID();
if (KeyFn && D->isCompleteDefinition())
// FIXME: This is wrong for the ARM ABI, where some other module may have
@@ -3235,6 +3238,14 @@ static bool isConsumerInterestedIn(ASTContext &Ctx, Decl *D, bool HasBody) {
if (ES->hasExternalDefinitions(D) == ExternalASTSource::EK_Never)
return true;
+ // The dynamic class defined in a named module is interesting.
+ // The code generator needs to emit its vtable there.
+ if (const auto *Class = dyn_cast<CXXRecordDecl>(D))
+ return Class->getOwningModule() &&
+ Class->getOwningModule()->isInterfaceOrPartition() &&
+ Class->getOwningModule() == Ctx.getCurrentNamedModule() &&
+ Class->getDefinition() && Class->isDynamicClass();
+
return false;
}
diff --git a/clang/lib/Serialization/ASTWriterDecl.cpp b/clang/lib/Serialization/ASTWriterDecl.cpp
index 86f64bf2a24250..47b995aa7caa8f 100644
--- a/clang/lib/Serialization/ASTWriterDecl.cpp
+++ b/clang/lib/Serialization/ASTWriterDecl.cpp
@@ -1529,10 +1529,15 @@ void ASTDeclWriter::VisitCXXRecordDecl(CXXRecordDecl *D) {
if (D->isThisDeclarationADefinition())
Record.AddCXXDefinitionData(D);
- // Store (what we currently believe to be) the key function to avoid
- // deserializing every method so we can compute it.
- if (D->isCompleteDefinition())
- Record.AddDeclRef(Context.getCurrentKeyFunction(D));
+ if (D->isCompleteDefinition()) {
+ if (D->getOwningModule() && D->getOwningModule()->isInterfaceOrPartition())
+ Writer.ModularCodegenDecls.push_back(Writer.GetDeclRef(D));
+ else {
+ // Store (what we currently believe to be) the key function to avoid
+ // deserializing every method so we can compute it.
+ Record.AddDeclRef(Context.getCurrentKeyFunction(D));
+ }
+ }
Code = serialization::DECL_CXX_RECORD;
}
diff --git a/clang/test/CodeGenCXX/modules-vtable.cppm b/clang/test/CodeGenCXX/modules-vtable.cppm
index fb179b1de4880b..abbbc403ace243 100644
--- a/clang/test/CodeGenCXX/modules-vtable.cppm
+++ b/clang/test/CodeGenCXX/modules-vtable.cppm
@@ -24,6 +24,8 @@
// RUN: %t/M-A.cppm -o %t/M-A.pcm
// RUN: %clang_cc1 -triple %itanium_abi_triple -std=c++20 -fmodule-file=M:A=%t/M-A.pcm \
// RUN: %t/M-B.cppm -emit-llvm -o - | FileCheck %t/M-B.cppm
+// RUN: %clang_cc1 -triple %itanium_abi_triple -std=c++20 \
+// RUN: %t/M-A.pcm -emit-llvm -o - | FileCheck %t/M-A.cppm
//--- Mod.cppm
export module Mod;
@@ -41,9 +43,10 @@ Base::~Base() {}
// CHECK: @_ZTSW3Mod4Base = constant
// CHECK: @_ZTIW3Mod4Base = constant
-// CHECK-INLINE: @_ZTVW3Mod4Base = linkonce_odr {{.*}}unnamed_addr constant
-// CHECK-INLINE: @_ZTSW3Mod4Base = linkonce_odr {{.*}}constant
-// CHECK-INLINE: @_ZTIW3Mod4Base = linkonce_odr {{.*}}constant
+// With the new Itanium C++ ABI, the linkage of vtables in modules don't need to be linkonce ODR.
+// CHECK-INLINE: @_ZTVW3Mod4Base = {{.*}}unnamed_addr constant
+// CHECK-INLINE: @_ZTSW3Mod4Base = {{.*}}constant
+// CHECK-INLINE: @_ZTIW3Mod4Base = {{.*}}constant
module :private;
int private_use() {
@@ -60,11 +63,11 @@ int use() {
// CHECK-NOT: @_ZTSW3Mod4Base = constant
// CHECK-NOT: @_ZTIW3Mod4Base = constant
-// CHECK: @_ZTVW3Mod4Base = external unnamed_addr
+// CHECK: @_ZTVW3Mod4Base = external
-// CHECK-INLINE: @_ZTVW3Mod4Base = linkonce_odr {{.*}}unnamed_addr constant
-// CHECK-INLINE: @_ZTSW3Mod4Base = linkonce_odr {{.*}}constant
-// CHECK-INLINE: @_ZTIW3Mod4Base = linkonce_odr {{.*}}constant
+// CHECK-INLINE-NOT: @_ZTSW3Mod4Base = constant
+// CHECK-INLINE-NOT: @_ZTIW3Mod4Base = constant
+// CHECK-INLINE: @_ZTVW3Mod4Base = external
// Check the case that the declaration of the key function comes from another
// module unit but the definition of the key function comes from the current
@@ -82,6 +85,10 @@ int a_use() {
return 43;
}
+// CHECK: @_ZTVW1M1C = unnamed_addr constant
+// CHECK: @_ZTSW1M1C = constant
+// CHECK: @_ZTIW1M1C = constant
+
//--- M-B.cppm
export module M:B;
import :A;
@@ -93,6 +100,6 @@ int b_use() {
return 43;
}
-// CHECK: @_ZTVW1M1C = unnamed_addr constant
-// CHECK: @_ZTSW1M1C = constant
-// CHECK: @_ZTIW1M1C = constant
+// CHECK: @_ZTVW1M1C = external
+// CHECK-NOT: @_ZTSW1M1C = constant
+// CHECK-NOT: @_ZTIW1M1C = constant
diff --git a/clang/test/CodeGenCXX/pr70585.cppm b/clang/test/CodeGenCXX/pr70585.cppm
new file mode 100644
index 00000000000000..a2d6e02b5833ee
--- /dev/null
+++ b/clang/test/CodeGenCXX/pr70585.cppm
@@ -0,0 +1,47 @@
+// REQUIRES: !system-windows
+
+// RUN: rm -rf %t
+// RUN: split-file %s %t
+// RUN: cd %t
+//
+// RUN: %clang_cc1 -std=c++20 %t/layer1.cppm -triple %itanium_abi_triple \
+// RUN: -emit-module-interface -o %t/foo-layer1.pcm
+// RUN: %clang_cc1 -std=c++20 %t/layer2.cppm -triple %itanium_abi_triple \
+// RUN: -emit-module-interface -fmodule-file=foo:layer1=%t/foo-layer1.pcm \
+// RUN: -o %t/foo-layer2.pcm
+// RUN: %clang_cc1 -std=c++20 %t/foo-layer1.pcm -S -emit-llvm -o - | FileCheck %t/layer1.cppm
+// RUN: %clang_cc1 -std=c++20 %t/foo-layer2.pcm -S -emit-llvm -o - \
+// RUN: -fmodule-file=foo:layer1=%t/foo-layer1.pcm | FileCheck %t/layer2.cppm
+//
+// Check the case about emitting object files from sources directly.
+// RUN: %clang_cc1 -std=c++20 %t/layer1.cppm -triple %itanium_abi_triple \
+// RUN: -S -emit-llvm -o - | FileCheck %t/layer1.cppm
+// RUN: %clang_cc1 -std=c++20 %t/layer2.cppm -triple %itanium_abi_triple -S -emit-llvm \
+// RUN: -fmodule-file=foo:layer1=%t/foo-layer1.pcm -o - | FileCheck %t/layer2.cppm
+
+//--- layer1.cppm
+export module foo:layer1;
+struct Fruit {
+ virtual ~Fruit() = default;
+ virtual void eval();
+};
+
+// CHECK-DAG: @_ZTVW3foo5Fruit = unnamed_addr constant
+// CHECK-DAG: @_ZTSW3foo5Fruit = constant
+// CHECK-DAG: @_ZTIW3foo5Fruit = constant
+
+// Testing that:
+// (1) The use of virtual functions won't produce the vtable.
+// (2) The definition of key functions won't produce the vtable.
+//
+//--- layer2.cppm
+export module foo:layer2;
+import :layer1;
+export void layer2_fun() {
+ Fruit *b = new Fruit();
+ b->eval();
+}
+void Fruit::eval() {}
+// CHECK: @_ZTVW3foo5Fruit = external unnamed_addr constant
+// CHECK-NOT: @_ZTSW3foo5Fruit
+// CHECK-NOT: @_ZTIW3foo5Fruit
>From f05b4e67c6d64782cecdcc3fceb19fbd3035737f Mon Sep 17 00:00:00 2001
From: Chuanqi Xu <yedeng.yd at linux.alibaba.com>
Date: Fri, 29 Mar 2024 10:43:59 +0800
Subject: [PATCH 2/3] update test
---
clang/test/CodeGenCXX/modules-vtable.cppm | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/clang/test/CodeGenCXX/modules-vtable.cppm b/clang/test/CodeGenCXX/modules-vtable.cppm
index abbbc403ace243..5cc3504d72628f 100644
--- a/clang/test/CodeGenCXX/modules-vtable.cppm
+++ b/clang/test/CodeGenCXX/modules-vtable.cppm
@@ -61,12 +61,12 @@ int use() {
return 43;
}
-// CHECK-NOT: @_ZTSW3Mod4Base = constant
-// CHECK-NOT: @_ZTIW3Mod4Base = constant
+// CHECK-NOT: @_ZTSW3Mod4Base
+// CHECK-NOT: @_ZTIW3Mod4Base
// CHECK: @_ZTVW3Mod4Base = external
-// CHECK-INLINE-NOT: @_ZTSW3Mod4Base = constant
-// CHECK-INLINE-NOT: @_ZTIW3Mod4Base = constant
+// CHECK-INLINE-NOT: @_ZTSW3Mod4Base
+// CHECK-INLINE-NOT: @_ZTIW3Mod4Base
// CHECK-INLINE: @_ZTVW3Mod4Base = external
// Check the case that the declaration of the key function comes from another
@@ -101,5 +101,5 @@ int b_use() {
}
// CHECK: @_ZTVW1M1C = external
-// CHECK-NOT: @_ZTSW1M1C = constant
-// CHECK-NOT: @_ZTIW1M1C = constant
+// CHECK-NOT: @_ZTSW1M1C
+// CHECK-NOT: @_ZTIW1M1C
>From 2a4d14ac1d19dc5d629e9684e305c1fa5bbd0fe8 Mon Sep 17 00:00:00 2001
From: Chuanqi Xu <yedeng.yd at linux.alibaba.com>
Date: Tue, 2 Apr 2024 10:17:13 +0800
Subject: [PATCH 3/3] Always write key functions as before
---
clang/lib/Serialization/ASTReaderDecl.cpp | 5 +----
clang/lib/Serialization/ASTWriterDecl.cpp | 20 +++++++++++---------
2 files changed, 12 insertions(+), 13 deletions(-)
diff --git a/clang/lib/Serialization/ASTReaderDecl.cpp b/clang/lib/Serialization/ASTReaderDecl.cpp
index c0e1a93cd3a32a..f3bb5bc8cd6f7b 100644
--- a/clang/lib/Serialization/ASTReaderDecl.cpp
+++ b/clang/lib/Serialization/ASTReaderDecl.cpp
@@ -2274,10 +2274,7 @@ ASTDeclReader::VisitCXXRecordDeclImpl(CXXRecordDecl *D) {
// Lazily load the key function to avoid deserializing every method so we can
// compute it.
- //
- // The key function in named module is meaningless.
- if (WasDefinition && (!D->getOwningModule() ||
- !D->getOwningModule()->isInterfaceOrPartition())) {
+ if (WasDefinition) {
DeclID KeyFn = readDeclID();
if (KeyFn && D->isCompleteDefinition())
// FIXME: This is wrong for the ARM ABI, where some other module may have
diff --git a/clang/lib/Serialization/ASTWriterDecl.cpp b/clang/lib/Serialization/ASTWriterDecl.cpp
index 47b995aa7caa8f..088ae08e7b89af 100644
--- a/clang/lib/Serialization/ASTWriterDecl.cpp
+++ b/clang/lib/Serialization/ASTWriterDecl.cpp
@@ -1529,15 +1529,17 @@ void ASTDeclWriter::VisitCXXRecordDecl(CXXRecordDecl *D) {
if (D->isThisDeclarationADefinition())
Record.AddCXXDefinitionData(D);
- if (D->isCompleteDefinition()) {
- if (D->getOwningModule() && D->getOwningModule()->isInterfaceOrPartition())
- Writer.ModularCodegenDecls.push_back(Writer.GetDeclRef(D));
- else {
- // Store (what we currently believe to be) the key function to avoid
- // deserializing every method so we can compute it.
- Record.AddDeclRef(Context.getCurrentKeyFunction(D));
- }
- }
+ if (D->isCompleteDefinition() && D->getOwningModule() &&
+ D->getOwningModule()->isInterfaceOrPartition())
+ Writer.ModularCodegenDecls.push_back(Writer.GetDeclRef(D));
+
+ // Store (what we currently believe to be) the key function to avoid
+ // deserializing every method so we can compute it.
+ //
+ // FIXME: Avoid adding the key function if the class is defined in
+ // module purview since the key function is meaningless in module purview.
+ if (D->isCompleteDefinition())
+ Record.AddDeclRef(Context.getCurrentKeyFunction(D));
Code = serialization::DECL_CXX_RECORD;
}
More information about the cfe-commits
mailing list