Skip to content

Commit 0ad4c80

Browse files
authored
[clang] Fix PS "selective" DLL import/export of vtable & typeinfo (#92579)
Prior to this patch, for "selective" DLL import/export, the vtable & typeinfo would be imported/exported on the condition that all non-inline virtual methods are imported/exported. This condition was based upon MS guidelines related to "selective" DLL import/export. However, in reality, this condition is too rigid and can result in undefined vtable & typeinfo symbols for code that builds fine with MSVC. Therefore, relax this condition to be if any non-inline method is imported/exported.
1 parent 5434b04 commit 0ad4c80

File tree

3 files changed

+156
-260
lines changed

3 files changed

+156
-260
lines changed

clang/lib/CodeGen/ItaniumCXXABI.cpp

Lines changed: 42 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1793,6 +1793,37 @@ void ItaniumCXXABI::EmitDestructorCall(CodeGenFunction &CGF,
17931793
ThisTy, VTT, VTTTy, nullptr);
17941794
}
17951795

1796+
// Check if any non-inline method has the specified attribute.
1797+
template <typename T>
1798+
static bool CXXRecordNonInlineHasAttr(const CXXRecordDecl *RD) {
1799+
for (const auto *D : RD->noload_decls()) {
1800+
if (const auto *FD = dyn_cast<FunctionDecl>(D)) {
1801+
if (FD->isInlined() || FD->doesThisDeclarationHaveABody() ||
1802+
FD->isPureVirtual())
1803+
continue;
1804+
if (D->hasAttr<T>())
1805+
return true;
1806+
}
1807+
}
1808+
1809+
return false;
1810+
}
1811+
1812+
static void setVTableSelectiveDLLImportExport(CodeGenModule &CGM,
1813+
llvm::GlobalVariable *VTable,
1814+
const CXXRecordDecl *RD) {
1815+
if (VTable->getDLLStorageClass() !=
1816+
llvm::GlobalVariable::DefaultStorageClass ||
1817+
RD->hasAttr<DLLImportAttr>() || RD->hasAttr<DLLExportAttr>())
1818+
return;
1819+
1820+
if (CGM.getVTables().isVTableExternal(RD)) {
1821+
if (CXXRecordNonInlineHasAttr<DLLImportAttr>(RD))
1822+
VTable->setDLLStorageClass(llvm::GlobalValue::DLLImportStorageClass);
1823+
} else if (CXXRecordNonInlineHasAttr<DLLExportAttr>(RD))
1824+
VTable->setDLLStorageClass(llvm::GlobalValue::DLLExportStorageClass);
1825+
}
1826+
17961827
void ItaniumCXXABI::emitVTableDefinitions(CodeGenVTables &CGVT,
17971828
const CXXRecordDecl *RD) {
17981829
llvm::GlobalVariable *VTable = getAddrOfVTable(RD, CharUnits());
@@ -1818,6 +1849,9 @@ void ItaniumCXXABI::emitVTableDefinitions(CodeGenVTables &CGVT,
18181849
if (CGM.supportsCOMDAT() && VTable->isWeakForLinker())
18191850
VTable->setComdat(CGM.getModule().getOrInsertComdat(VTable->getName()));
18201851

1852+
if (CGM.getTarget().hasPS4DLLImportExport())
1853+
setVTableSelectiveDLLImportExport(CGM, VTable, RD);
1854+
18211855
// Set the right visibility.
18221856
CGM.setGVProperties(VTable, RD);
18231857

@@ -1905,29 +1939,6 @@ ItaniumCXXABI::getVTableAddressPoint(BaseSubobject Base,
19051939
VTable->getValueType(), VTable, Indices, /*InBounds=*/true, InRange);
19061940
}
19071941

1908-
// Check whether all the non-inline virtual methods for the class have the
1909-
// specified attribute.
1910-
template <typename T>
1911-
static bool CXXRecordAllNonInlineVirtualsHaveAttr(const CXXRecordDecl *RD) {
1912-
bool FoundNonInlineVirtualMethodWithAttr = false;
1913-
for (const auto *D : RD->noload_decls()) {
1914-
if (const auto *FD = dyn_cast<FunctionDecl>(D)) {
1915-
if (!FD->isVirtualAsWritten() || FD->isInlineSpecified() ||
1916-
FD->doesThisDeclarationHaveABody())
1917-
continue;
1918-
if (!D->hasAttr<T>())
1919-
return false;
1920-
FoundNonInlineVirtualMethodWithAttr = true;
1921-
}
1922-
}
1923-
1924-
// We didn't find any non-inline virtual methods missing the attribute. We
1925-
// will return true when we found at least one non-inline virtual with the
1926-
// attribute. (This lets our caller know that the attribute needs to be
1927-
// propagated up to the vtable.)
1928-
return FoundNonInlineVirtualMethodWithAttr;
1929-
}
1930-
19311942
llvm::Value *ItaniumCXXABI::getVTableAddressPointInStructorWithVTT(
19321943
CodeGenFunction &CGF, const CXXRecordDecl *VTableClass, BaseSubobject Base,
19331944
const CXXRecordDecl *NearestVBase) {
@@ -1981,26 +1992,10 @@ llvm::GlobalVariable *ItaniumCXXABI::getAddrOfVTable(const CXXRecordDecl *RD,
19811992
getContext().toCharUnitsFromBits(PAlign).getAsAlign());
19821993
VTable->setUnnamedAddr(llvm::GlobalValue::UnnamedAddr::Global);
19831994

1984-
// In MS C++ if you have a class with virtual functions in which you are using
1985-
// selective member import/export, then all virtual functions must be exported
1986-
// unless they are inline, otherwise a link error will result. To match this
1987-
// behavior, for such classes, we dllimport the vtable if it is defined
1988-
// externally and all the non-inline virtual methods are marked dllimport, and
1989-
// we dllexport the vtable if it is defined in this TU and all the non-inline
1990-
// virtual methods are marked dllexport.
1991-
if (CGM.getTarget().hasPS4DLLImportExport()) {
1992-
if ((!RD->hasAttr<DLLImportAttr>()) && (!RD->hasAttr<DLLExportAttr>())) {
1993-
if (CGM.getVTables().isVTableExternal(RD)) {
1994-
if (CXXRecordAllNonInlineVirtualsHaveAttr<DLLImportAttr>(RD))
1995-
VTable->setDLLStorageClass(llvm::GlobalValue::DLLImportStorageClass);
1996-
} else {
1997-
if (CXXRecordAllNonInlineVirtualsHaveAttr<DLLExportAttr>(RD))
1998-
VTable->setDLLStorageClass(llvm::GlobalValue::DLLExportStorageClass);
1999-
}
2000-
}
2001-
}
2002-
CGM.setGVProperties(VTable, RD);
1995+
if (CGM.getTarget().hasPS4DLLImportExport())
1996+
setVTableSelectiveDLLImportExport(CGM, VTable, RD);
20031997

1998+
CGM.setGVProperties(VTable, RD);
20041999
return VTable;
20052000
}
20062001

@@ -3285,7 +3280,7 @@ ItaniumRTTIBuilder::GetAddrOfExternalRTTIDescriptor(QualType Ty) {
32853280
// Import the typeinfo symbol when all non-inline virtual methods are
32863281
// imported.
32873282
if (CGM.getTarget().hasPS4DLLImportExport()) {
3288-
if (RD && CXXRecordAllNonInlineVirtualsHaveAttr<DLLImportAttr>(RD)) {
3283+
if (RD && CXXRecordNonInlineHasAttr<DLLImportAttr>(RD)) {
32893284
GV->setDLLStorageClass(llvm::GlobalVariable::DLLImportStorageClass);
32903285
CGM.setDSOLocal(GV);
32913286
}
@@ -3938,13 +3933,13 @@ llvm::Constant *ItaniumRTTIBuilder::BuildTypeInfo(
39383933

39393934
// Export the typeinfo in the same circumstances as the vtable is exported.
39403935
auto GVDLLStorageClass = DLLStorageClass;
3941-
if (CGM.getTarget().hasPS4DLLImportExport()) {
3936+
if (CGM.getTarget().hasPS4DLLImportExport() &&
3937+
GVDLLStorageClass != llvm::GlobalVariable::DLLExportStorageClass) {
39423938
if (const RecordType *RecordTy = dyn_cast<RecordType>(Ty)) {
39433939
const CXXRecordDecl *RD = cast<CXXRecordDecl>(RecordTy->getDecl());
39443940
if (RD->hasAttr<DLLExportAttr>() ||
3945-
CXXRecordAllNonInlineVirtualsHaveAttr<DLLExportAttr>(RD)) {
3941+
CXXRecordNonInlineHasAttr<DLLExportAttr>(RD))
39463942
GVDLLStorageClass = llvm::GlobalVariable::DLLExportStorageClass;
3947-
}
39483943
}
39493944
}
39503945

@@ -3984,9 +3979,7 @@ llvm::Constant *ItaniumRTTIBuilder::BuildTypeInfo(
39843979
CGM.setDSOLocal(GV);
39853980

39863981
TypeName->setDLLStorageClass(DLLStorageClass);
3987-
GV->setDLLStorageClass(CGM.getTarget().hasPS4DLLImportExport()
3988-
? GVDLLStorageClass
3989-
: DLLStorageClass);
3982+
GV->setDLLStorageClass(GVDLLStorageClass);
39903983

39913984
TypeName->setPartition(CGM.getCodeGenOpts().SymbolPartition);
39923985
GV->setPartition(CGM.getCodeGenOpts().SymbolPartition);
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/// For a class that has a vtable and typeinfo symbol for RTTI, if a user marks
2+
/// either:
3+
///
4+
/// (a) The entire class as dllexport (dllimport)
5+
/// (b) Any non-inline method of the class as dllexport (dllimport)
6+
///
7+
/// then Clang must export the vtable and typeinfo symbol from the TU where they
8+
/// are defined (the TU containing the definition of the Itanium C++ ABI "key
9+
/// function") and must import them in other modules where they are referenced.
10+
11+
// RUN: %clang_cc1 -I%S -fdeclspec -triple x86_64-unknown-windows-itanium -emit-llvm -o - %s -fhalf-no-semantic-interposition \
12+
// RUN: | FileCheck %s -check-prefix=WI
13+
// RUN: %clang_cc1 -I%S -fdeclspec -triple x86_64-scei-windows-itanium -emit-llvm -o - %s -fhalf-no-semantic-interposition \
14+
// RUN: | FileCheck %s --check-prefixes=PS
15+
// RUN: %clang_cc1 -I%S -fdeclspec -triple x86_64-scei-ps4 -emit-llvm -o - %s -fhalf-no-semantic-interposition \
16+
// RUN: | FileCheck %s --check-prefixes=PS
17+
// RUN: %clang_cc1 -I%S -fdeclspec -triple x86_64-sie-ps5 -emit-llvm -o - %s -fhalf-no-semantic-interposition \
18+
// RUN: | FileCheck %s --check-prefixes=PS
19+
20+
#include <typeinfo>
21+
22+
/// Case (a) -- Import Aspect
23+
/// The entire class is imported. The typeinfo symbol must also be imported, but
24+
/// the vtable will not be referenced, and so does not need to be imported.
25+
26+
// PS-DAG: @_ZTI10FullImport = {{.*}}dllimport
27+
// WI-DAG: @_ZTI10FullImport = external dllimport constant ptr
28+
struct __declspec(dllimport) FullImport {
29+
virtual void inlineFunc() const {}
30+
virtual void key();
31+
virtual void func();
32+
};
33+
34+
/// 'FullImport::key()' is the key function, so the vtable and typeinfo symbol
35+
/// of 'FullImport' will be defined in the TU that contains the definition of
36+
/// 'key()' (and they must be exported from there).
37+
void FullImportTest() { typeid(FullImport).name(); }
38+
39+
/// Case (a) -- Export Aspect
40+
/// The entire class is exported. The vtable and typeinfo symbols must also be
41+
/// exported.
42+
43+
// PS-DAG: @_ZTV10FullExport = {{.*}}dllexport
44+
// WI-DAG: @_ZTV10FullExport = {{.*}}dllexport
45+
// PS-DAG: @_ZTI10FullExport = {{.*}}dllexport
46+
// WI-DAG: @_ZTI10FullExport = dso_local dllexport constant {
47+
struct __declspec(dllexport) FullExport {
48+
virtual void inlineFunc() const {}
49+
virtual void key();
50+
virtual void func();
51+
};
52+
53+
/// This is the key function of the class 'FullExport', so the vtable and
54+
/// typeinfo symbols of 'FullExport' will be defined in this TU, and so they
55+
/// must be exported from this TU.
56+
void FullExport::key() { typeid(FullExport).name(); }
57+
58+
/// Case (b) -- Import Aspect
59+
/// The class as a whole is not imported, but a non-inline method of the class
60+
/// is, so the vtable and typeinfo symbol must be imported.
61+
62+
// PS-DAG: @_ZTV10PartImport = {{.*}}dllimport
63+
// WI-DAG: @_ZTV10PartImport = external dso_local unnamed_addr constant {
64+
// PS-DAG: @_ZTI10PartImport = {{.*}}dllimport
65+
// WI-DAG: @_ZTI10PartImport = external dso_local constant ptr
66+
struct PartImport {
67+
virtual void inlineFunc() const {}
68+
virtual void key();
69+
__declspec(dllimport) virtual void func();
70+
};
71+
72+
/// 'PartImport::key()' is the key function, so the vtable and typeinfo symbol
73+
/// of 'PartImport' will be defined in the TU that contains the definition of
74+
/// 'key()' (and they must be exported from there). Here, we will reference the
75+
/// vtable and typeinfo symbol, so we must also import them.
76+
void PartImportTest() {
77+
PartImport f;
78+
typeid(PartImport).name();
79+
}
80+
81+
/// Case (b) -- Export Aspect
82+
/// The class as a whole is not exported, but a non-inline method of the class
83+
/// is, so the vtable and typeinfo symbol must be exported.
84+
85+
// PS-DAG: @_ZTV10PartExport = {{.*}}dllexport
86+
// WI-DAG: @_ZTV10PartExport = dso_local unnamed_addr constant {
87+
// PS-DAG: @_ZTI10PartExport = {{.*}}dllexport
88+
// WI-DAG: @_ZTI10PartExport = dso_local constant {
89+
struct PartExport {
90+
virtual void inlineFunc() const {}
91+
virtual void key();
92+
__declspec(dllexport) virtual void func();
93+
};
94+
95+
/// This is the key function of the class 'PartExport', so the vtable and
96+
/// typeinfo symbol of 'PartExport' will be defined in this TU, and so they must
97+
/// be exported from this TU.
98+
void PartExport::key() { typeid(PartExport).name(); }
99+
100+
/// Case (b) -- Export Aspect
101+
/// The class as a whole is not exported, but the constructor of the class
102+
/// is, so the vtable and typeinfo symbol must be exported.
103+
104+
// PS-DAG: @_ZTV10ConsExport = {{.*}}dllexport
105+
// WI-DAG: @_ZTV10ConsExport = dso_local unnamed_addr constant {
106+
// PS-DAG: @_ZTI10ConsExport = {{.*}}dllexport
107+
// WI-DAG: @_ZTI10ConsExport = dso_local constant {
108+
struct ConsExport {
109+
__declspec(dllexport) ConsExport();
110+
virtual void key();
111+
};
112+
113+
ConsExport::ConsExport() {}
114+
void ConsExport::key() { typeid(ConsExport).name(); }

0 commit comments

Comments
 (0)