Skip to content

Commit 2246167

Browse files
committedJul 13, 2017
[Sema] Mark a virtual CXXMethodDecl as used if a call to it can be
devirtualized. The code to detect devirtualized calls is already in IRGen, so move the code to lib/AST and make it a shared utility between Sema and IRGen. This commit fixes a linkage error I was seeing when compiling the following code: $ cat test1.cpp struct Base { virtual void operator()() {} }; template<class T> struct Derived final : Base { void operator()() override {} }; Derived<int> *d; int main() { if (d) (*d)(); return 0; } rdar://problem/33195657 Differential Revision: https://reviews.llvm.org/D34301 llvm-svn: 307883
1 parent fa5183b commit 2246167

11 files changed

+172
-114
lines changed
 

‎clang/include/clang/AST/DeclCXX.h

+13
Original file line numberDiff line numberDiff line change
@@ -1886,6 +1886,19 @@ class CXXMethodDecl : public FunctionDecl {
18861886
return (CD->begin_overridden_methods() != CD->end_overridden_methods());
18871887
}
18881888

1889+
/// If it's possible to devirtualize a call to this method, return the called
1890+
/// function. Otherwise, return null.
1891+
1892+
/// \param Base The object on which this virtual function is called.
1893+
/// \param IsAppleKext True if we are compiling for Apple kext.
1894+
CXXMethodDecl *getDevirtualizedMethod(const Expr *Base, bool IsAppleKext);
1895+
1896+
const CXXMethodDecl *getDevirtualizedMethod(const Expr *Base,
1897+
bool IsAppleKext) const {
1898+
return const_cast<CXXMethodDecl *>(this)->getDevirtualizedMethod(
1899+
Base, IsAppleKext);
1900+
}
1901+
18891902
/// \brief Determine whether this is a usual deallocation function
18901903
/// (C++ [basic.stc.dynamic.deallocation]p2), which is an overloaded
18911904
/// delete or delete[] operator with a particular signature.

‎clang/include/clang/Sema/Sema.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -3944,7 +3944,7 @@ class Sema {
39443944
void MarkFunctionReferenced(SourceLocation Loc, FunctionDecl *Func,
39453945
bool MightBeOdrUse = true);
39463946
void MarkVariableReferenced(SourceLocation Loc, VarDecl *Var);
3947-
void MarkDeclRefReferenced(DeclRefExpr *E);
3947+
void MarkDeclRefReferenced(DeclRefExpr *E, const Expr *Base = nullptr);
39483948
void MarkMemberReferenced(MemberExpr *E);
39493949

39503950
void UpdateMarkingForLValueToRValue(Expr *E);

‎clang/lib/AST/DeclCXX.cpp

+78
Original file line numberDiff line numberDiff line change
@@ -1605,6 +1605,84 @@ CXXMethodDecl *CXXMethodDecl::CreateDeserialized(ASTContext &C, unsigned ID) {
16051605
SC_None, false, false, SourceLocation());
16061606
}
16071607

1608+
CXXMethodDecl *CXXMethodDecl::getDevirtualizedMethod(const Expr *Base,
1609+
bool IsAppleKext) {
1610+
assert(isVirtual() && "this method is expected to be virtual");
1611+
1612+
// When building with -fapple-kext, all calls must go through the vtable since
1613+
// the kernel linker can do runtime patching of vtables.
1614+
if (IsAppleKext)
1615+
return nullptr;
1616+
1617+
// If the member function is marked 'final', we know that it can't be
1618+
// overridden and can therefore devirtualize it unless it's pure virtual.
1619+
if (hasAttr<FinalAttr>())
1620+
return isPure() ? nullptr : this;
1621+
1622+
// If Base is unknown, we cannot devirtualize.
1623+
if (!Base)
1624+
return nullptr;
1625+
1626+
// If the base expression (after skipping derived-to-base conversions) is a
1627+
// class prvalue, then we can devirtualize.
1628+
Base = Base->getBestDynamicClassTypeExpr();
1629+
if (Base->isRValue() && Base->getType()->isRecordType())
1630+
return this;
1631+
1632+
// If we don't even know what we would call, we can't devirtualize.
1633+
const CXXRecordDecl *BestDynamicDecl = Base->getBestDynamicClassType();
1634+
if (!BestDynamicDecl)
1635+
return nullptr;
1636+
1637+
// There may be a method corresponding to MD in a derived class.
1638+
CXXMethodDecl *DevirtualizedMethod =
1639+
getCorrespondingMethodInClass(BestDynamicDecl);
1640+
1641+
// If that method is pure virtual, we can't devirtualize. If this code is
1642+
// reached, the result would be UB, not a direct call to the derived class
1643+
// function, and we can't assume the derived class function is defined.
1644+
if (DevirtualizedMethod->isPure())
1645+
return nullptr;
1646+
1647+
// If that method is marked final, we can devirtualize it.
1648+
if (DevirtualizedMethod->hasAttr<FinalAttr>())
1649+
return DevirtualizedMethod;
1650+
1651+
// Similarly, if the class itself is marked 'final' it can't be overridden
1652+
// and we can therefore devirtualize the member function call.
1653+
if (BestDynamicDecl->hasAttr<FinalAttr>())
1654+
return DevirtualizedMethod;
1655+
1656+
if (const DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(Base)) {
1657+
if (const VarDecl *VD = dyn_cast<VarDecl>(DRE->getDecl()))
1658+
if (VD->getType()->isRecordType())
1659+
// This is a record decl. We know the type and can devirtualize it.
1660+
return DevirtualizedMethod;
1661+
1662+
return nullptr;
1663+
}
1664+
1665+
// We can devirtualize calls on an object accessed by a class member access
1666+
// expression, since by C++11 [basic.life]p6 we know that it can't refer to
1667+
// a derived class object constructed in the same location.
1668+
if (const MemberExpr *ME = dyn_cast<MemberExpr>(Base))
1669+
if (const ValueDecl *VD = dyn_cast<ValueDecl>(ME->getMemberDecl()))
1670+
return VD->getType()->isRecordType() ? DevirtualizedMethod : nullptr;
1671+
1672+
// Likewise for calls on an object accessed by a (non-reference) pointer to
1673+
// member access.
1674+
if (auto *BO = dyn_cast<BinaryOperator>(Base)) {
1675+
if (BO->isPtrMemOp()) {
1676+
auto *MPT = BO->getRHS()->getType()->castAs<MemberPointerType>();
1677+
if (MPT->getPointeeType()->isRecordType())
1678+
return DevirtualizedMethod;
1679+
}
1680+
}
1681+
1682+
// We can't devirtualize the call.
1683+
return nullptr;
1684+
}
1685+
16081686
bool CXXMethodDecl::isUsualDeallocationFunction() const {
16091687
if (getOverloadedOperator() != OO_Delete &&
16101688
getOverloadedOperator() != OO_Array_Delete)

‎clang/lib/CodeGen/CGClass.cpp

-82
Original file line numberDiff line numberDiff line change
@@ -2716,88 +2716,6 @@ llvm::Value *CodeGenFunction::EmitVTableTypeCheckedLoad(
27162716
cast<llvm::PointerType>(VTable->getType())->getElementType());
27172717
}
27182718

2719-
bool
2720-
CodeGenFunction::CanDevirtualizeMemberFunctionCall(const Expr *Base,
2721-
const CXXMethodDecl *MD) {
2722-
// When building with -fapple-kext, all calls must go through the vtable since
2723-
// the kernel linker can do runtime patching of vtables.
2724-
if (getLangOpts().AppleKext)
2725-
return false;
2726-
2727-
// If the member function is marked 'final', we know that it can't be
2728-
// overridden and can therefore devirtualize it unless it's pure virtual.
2729-
if (MD->hasAttr<FinalAttr>())
2730-
return !MD->isPure();
2731-
2732-
// If the base expression (after skipping derived-to-base conversions) is a
2733-
// class prvalue, then we can devirtualize.
2734-
Base = Base->getBestDynamicClassTypeExpr();
2735-
if (Base->isRValue() && Base->getType()->isRecordType())
2736-
return true;
2737-
2738-
// If we don't even know what we would call, we can't devirtualize.
2739-
const CXXRecordDecl *BestDynamicDecl = Base->getBestDynamicClassType();
2740-
if (!BestDynamicDecl)
2741-
return false;
2742-
2743-
// There may be a method corresponding to MD in a derived class.
2744-
const CXXMethodDecl *DevirtualizedMethod =
2745-
MD->getCorrespondingMethodInClass(BestDynamicDecl);
2746-
2747-
// If that method is pure virtual, we can't devirtualize. If this code is
2748-
// reached, the result would be UB, not a direct call to the derived class
2749-
// function, and we can't assume the derived class function is defined.
2750-
if (DevirtualizedMethod->isPure())
2751-
return false;
2752-
2753-
// If that method is marked final, we can devirtualize it.
2754-
if (DevirtualizedMethod->hasAttr<FinalAttr>())
2755-
return true;
2756-
2757-
// Similarly, if the class itself is marked 'final' it can't be overridden
2758-
// and we can therefore devirtualize the member function call.
2759-
if (BestDynamicDecl->hasAttr<FinalAttr>())
2760-
return true;
2761-
2762-
if (const DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(Base)) {
2763-
if (const VarDecl *VD = dyn_cast<VarDecl>(DRE->getDecl())) {
2764-
// This is a record decl. We know the type and can devirtualize it.
2765-
return VD->getType()->isRecordType();
2766-
}
2767-
2768-
return false;
2769-
}
2770-
2771-
// We can devirtualize calls on an object accessed by a class member access
2772-
// expression, since by C++11 [basic.life]p6 we know that it can't refer to
2773-
// a derived class object constructed in the same location. However, we avoid
2774-
// devirtualizing a call to a template function that we could instantiate
2775-
// implicitly, but have not decided to do so. This is needed because if this
2776-
// function does not get instantiated, the devirtualization will create a
2777-
// direct call to a function whose body may not exist. In contrast, calls to
2778-
// template functions that are not defined in this TU are allowed to be
2779-
// devirtualized under assumption that it is user responsibility to
2780-
// instantiate them in some other TU.
2781-
if (const MemberExpr *ME = dyn_cast<MemberExpr>(Base))
2782-
if (const ValueDecl *VD = dyn_cast<ValueDecl>(ME->getMemberDecl()))
2783-
return VD->getType()->isRecordType() &&
2784-
(MD->instantiationIsPending() || MD->isDefined() ||
2785-
!MD->isImplicitlyInstantiable());
2786-
2787-
// Likewise for calls on an object accessed by a (non-reference) pointer to
2788-
// member access.
2789-
if (auto *BO = dyn_cast<BinaryOperator>(Base)) {
2790-
if (BO->isPtrMemOp()) {
2791-
auto *MPT = BO->getRHS()->getType()->castAs<MemberPointerType>();
2792-
if (MPT->getPointeeType()->isRecordType())
2793-
return true;
2794-
}
2795-
}
2796-
2797-
// We can't devirtualize the call.
2798-
return false;
2799-
}
2800-
28012719
void CodeGenFunction::EmitForwardingCallToLambda(
28022720
const CXXMethodDecl *callOperator,
28032721
CallArgList &callArgs) {

‎clang/lib/CodeGen/CGExprCXX.cpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,8 @@ RValue CodeGenFunction::EmitCXXMemberOrOperatorMemberCallExpr(
199199
bool CanUseVirtualCall = MD->isVirtual() && !HasQualifier;
200200

201201
const CXXMethodDecl *DevirtualizedMethod = nullptr;
202-
if (CanUseVirtualCall && CanDevirtualizeMemberFunctionCall(Base, MD)) {
202+
if (CanUseVirtualCall &&
203+
MD->getDevirtualizedMethod(Base, getLangOpts().AppleKext)) {
203204
const CXXRecordDecl *BestDynamicDecl = Base->getBestDynamicClassType();
204205
DevirtualizedMethod = MD->getCorrespondingMethodInClass(BestDynamicDecl);
205206
assert(DevirtualizedMethod);

‎clang/lib/CodeGen/CodeGenFunction.h

-5
Original file line numberDiff line numberDiff line change
@@ -1752,11 +1752,6 @@ class CodeGenFunction : public CodeGenTypeCache {
17521752
llvm::Value *EmitVTableTypeCheckedLoad(const CXXRecordDecl *RD, llvm::Value *VTable,
17531753
uint64_t VTableByteOffset);
17541754

1755-
/// CanDevirtualizeMemberFunctionCalls - Checks whether virtual calls on given
1756-
/// expr can be devirtualized.
1757-
bool CanDevirtualizeMemberFunctionCall(const Expr *Base,
1758-
const CXXMethodDecl *MD);
1759-
17601755
/// EnterDtorCleanups - Enter the cleanups necessary to complete the
17611756
/// given phase of destruction for a destructor. The end result
17621757
/// should call destructors on members and base classes in reverse

‎clang/lib/Sema/SemaExpr.cpp

+11-11
Original file line numberDiff line numberDiff line change
@@ -14665,24 +14665,24 @@ static void MarkExprReferenced(Sema &SemaRef, SourceLocation Loc,
1466514665
ME->performsVirtualDispatch(SemaRef.getLangOpts());
1466614666
if (!IsVirtualCall)
1466714667
return;
14668-
const Expr *Base = ME->getBase();
14669-
const CXXRecordDecl *MostDerivedClassDecl = Base->getBestDynamicClassType();
14670-
if (!MostDerivedClassDecl)
14671-
return;
14672-
CXXMethodDecl *DM = MD->getCorrespondingMethodInClass(MostDerivedClassDecl);
14673-
if (!DM || DM->isPure())
14674-
return;
14675-
SemaRef.MarkAnyDeclReferenced(Loc, DM, MightBeOdrUse);
14668+
14669+
// If it's possible to devirtualize the call, mark the called function
14670+
// referenced.
14671+
CXXMethodDecl *DM = MD->getDevirtualizedMethod(
14672+
ME->getBase(), SemaRef.getLangOpts().AppleKext);
14673+
if (DM)
14674+
SemaRef.MarkAnyDeclReferenced(Loc, DM, MightBeOdrUse);
1467614675
}
1467714676

1467814677
/// \brief Perform reference-marking and odr-use handling for a DeclRefExpr.
14679-
void Sema::MarkDeclRefReferenced(DeclRefExpr *E) {
14678+
void Sema::MarkDeclRefReferenced(DeclRefExpr *E, const Expr *Base) {
1468014679
// TODO: update this with DR# once a defect report is filed.
1468114680
// C++11 defect. The address of a pure member should not be an ODR use, even
1468214681
// if it's a qualified reference.
1468314682
bool OdrUse = true;
14684-
if (CXXMethodDecl *Method = dyn_cast<CXXMethodDecl>(E->getDecl()))
14685-
if (Method->isVirtual())
14683+
if (const CXXMethodDecl *Method = dyn_cast<CXXMethodDecl>(E->getDecl()))
14684+
if (Method->isVirtual() &&
14685+
!Method->getDevirtualizedMethod(Base, getLangOpts().AppleKext))
1468614686
OdrUse = false;
1468714687
MarkExprReferenced(*this, E->getLocation(), E->getDecl(), E, OdrUse);
1468814688
}

‎clang/lib/Sema/SemaOverload.cpp

+13-9
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ static bool functionHasPassObjectSizeParams(const FunctionDecl *FD) {
4848
/// A convenience routine for creating a decayed reference to a function.
4949
static ExprResult
5050
CreateFunctionRefExpr(Sema &S, FunctionDecl *Fn, NamedDecl *FoundDecl,
51-
bool HadMultipleCandidates,
51+
const Expr *Base, bool HadMultipleCandidates,
5252
SourceLocation Loc = SourceLocation(),
5353
const DeclarationNameLoc &LocInfo = DeclarationNameLoc()){
5454
if (S.DiagnoseUseOfDecl(FoundDecl, Loc))
@@ -68,7 +68,7 @@ CreateFunctionRefExpr(Sema &S, FunctionDecl *Fn, NamedDecl *FoundDecl,
6868
if (HadMultipleCandidates)
6969
DRE->setHadMultipleCandidates(true);
7070

71-
S.MarkDeclRefReferenced(DRE);
71+
S.MarkDeclRefReferenced(DRE, Base);
7272
return S.ImpCastExprToType(DRE, S.Context.getPointerType(DRE->getType()),
7373
CK_FunctionToPointerDecay);
7474
}
@@ -11946,6 +11946,7 @@ Sema::CreateOverloadedUnaryOp(SourceLocation OpLoc, UnaryOperatorKind Opc,
1194611946
FunctionDecl *FnDecl = Best->Function;
1194711947

1194811948
if (FnDecl) {
11949+
Expr *Base = nullptr;
1194911950
// We matched an overloaded operator. Build a call to that
1195011951
// operator.
1195111952

@@ -11958,7 +11959,7 @@ Sema::CreateOverloadedUnaryOp(SourceLocation OpLoc, UnaryOperatorKind Opc,
1195811959
Best->FoundDecl, Method);
1195911960
if (InputRes.isInvalid())
1196011961
return ExprError();
11961-
Input = InputRes.get();
11962+
Base = Input = InputRes.get();
1196211963
} else {
1196311964
// Convert the arguments.
1196411965
ExprResult InputInit
@@ -11974,7 +11975,8 @@ Sema::CreateOverloadedUnaryOp(SourceLocation OpLoc, UnaryOperatorKind Opc,
1197411975

1197511976
// Build the actual expression node.
1197611977
ExprResult FnExpr = CreateFunctionRefExpr(*this, FnDecl, Best->FoundDecl,
11977-
HadMultipleCandidates, OpLoc);
11978+
Base, HadMultipleCandidates,
11979+
OpLoc);
1197811980
if (FnExpr.isInvalid())
1197911981
return ExprError();
1198011982

@@ -12159,6 +12161,7 @@ Sema::CreateOverloadedBinOp(SourceLocation OpLoc,
1215912161
FunctionDecl *FnDecl = Best->Function;
1216012162

1216112163
if (FnDecl) {
12164+
Expr *Base = nullptr;
1216212165
// We matched an overloaded operator. Build a call to that
1216312166
// operator.
1216412167

@@ -12180,7 +12183,7 @@ Sema::CreateOverloadedBinOp(SourceLocation OpLoc,
1218012183
Best->FoundDecl, Method);
1218112184
if (Arg0.isInvalid())
1218212185
return ExprError();
12183-
Args[0] = Arg0.getAs<Expr>();
12186+
Base = Args[0] = Arg0.getAs<Expr>();
1218412187
Args[1] = RHS = Arg1.getAs<Expr>();
1218512188
} else {
1218612189
// Convert the arguments.
@@ -12204,7 +12207,7 @@ Sema::CreateOverloadedBinOp(SourceLocation OpLoc,
1220412207

1220512208
// Build the actual expression node.
1220612209
ExprResult FnExpr = CreateFunctionRefExpr(*this, FnDecl,
12207-
Best->FoundDecl,
12210+
Best->FoundDecl, Base,
1220812211
HadMultipleCandidates, OpLoc);
1220912212
if (FnExpr.isInvalid())
1221012213
return ExprError();
@@ -12426,6 +12429,7 @@ Sema::CreateOverloadedArraySubscriptExpr(SourceLocation LLoc,
1242612429
OpLocInfo.setCXXOperatorNameRange(SourceRange(LLoc, RLoc));
1242712430
ExprResult FnExpr = CreateFunctionRefExpr(*this, FnDecl,
1242812431
Best->FoundDecl,
12432+
Base,
1242912433
HadMultipleCandidates,
1243012434
OpLocInfo.getLoc(),
1243112435
OpLocInfo.getInfo());
@@ -12984,7 +12988,7 @@ Sema::BuildCallToObjectOfClassType(Scope *S, Expr *Obj,
1298412988
Context.DeclarationNames.getCXXOperatorName(OO_Call), LParenLoc);
1298512989
OpLocInfo.setCXXOperatorNameRange(SourceRange(LParenLoc, RParenLoc));
1298612990
ExprResult NewFn = CreateFunctionRefExpr(*this, Method, Best->FoundDecl,
12987-
HadMultipleCandidates,
12991+
Obj, HadMultipleCandidates,
1298812992
OpLocInfo.getLoc(),
1298912993
OpLocInfo.getInfo());
1299012994
if (NewFn.isInvalid())
@@ -13175,7 +13179,7 @@ Sema::BuildOverloadedArrowExpr(Scope *S, Expr *Base, SourceLocation OpLoc,
1317513179

1317613180
// Build the operator call.
1317713181
ExprResult FnExpr = CreateFunctionRefExpr(*this, Method, Best->FoundDecl,
13178-
HadMultipleCandidates, OpLoc);
13182+
Base, HadMultipleCandidates, OpLoc);
1317913183
if (FnExpr.isInvalid())
1318013184
return ExprError();
1318113185

@@ -13234,7 +13238,7 @@ ExprResult Sema::BuildLiteralOperatorCall(LookupResult &R,
1323413238

1323513239
FunctionDecl *FD = Best->Function;
1323613240
ExprResult Fn = CreateFunctionRefExpr(*this, FD, Best->FoundDecl,
13237-
HadMultipleCandidates,
13241+
nullptr, HadMultipleCandidates,
1323813242
SuffixInfo.getLoc(),
1323913243
SuffixInfo.getInfo());
1324013244
if (Fn.isInvalid())

‎clang/test/CodeGen/no-devirt.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ template < typename T, int N = 0 > class TmplWithArray {
2121
struct Wrapper {
2222
TmplWithArray<bool, 10> data;
2323
bool indexIt(int a) {
24-
if (a > 6) return data[a] ; // Should not devirtualize
24+
if (a > 6) return data[a] ; // Should devirtualize
2525
if (a > 4) return data.func1(a); // Should devirtualize
2626
return data.func2(a); // Should devirtualize
2727
}
@@ -53,7 +53,7 @@ bool stuff(int p)
5353
}
5454
#endif
5555

56-
// CHECK-NOT: call {{.*}} @_ZN13TmplWithArrayIbLi10EEixEi
56+
// CHECK-DAG: call {{.*}} @_ZN13TmplWithArrayIbLi10EEixEi
5757
// CHECK-DAG: call {{.*}} @_ZN13TmplWithArrayIbLi10EE5func1Ei
5858
// CHECK-DAG: call {{.*}} @_ZN13TmplWithArrayIbLi10EE5func2Ei
5959

‎clang/test/CodeGenCXX/devirtualize-virtual-function-calls-final.cpp

+50
Original file line numberDiff line numberDiff line change
@@ -241,3 +241,53 @@ namespace Test10 {
241241
return static_cast<A *>(b)->f();
242242
}
243243
}
244+
245+
namespace Test11 {
246+
// Check that the definitions of Derived's operators are emitted.
247+
248+
// CHECK-LABEL: define linkonce_odr void @_ZN6Test111SIiE4foo1Ev(
249+
// CHECK: call void @_ZN6Test111SIiE7DerivedclEv(
250+
// CHECK: call zeroext i1 @_ZN6Test111SIiE7DerivedeqERKNS_4BaseE(
251+
// CHECK: call zeroext i1 @_ZN6Test111SIiE7DerivedntEv(
252+
// CHECK: call dereferenceable(4) %"class.Test11::Base"* @_ZN6Test111SIiE7DerivedixEi(
253+
// CHECK: define linkonce_odr void @_ZN6Test111SIiE7DerivedclEv(
254+
// CHECK: define linkonce_odr zeroext i1 @_ZN6Test111SIiE7DerivedeqERKNS_4BaseE(
255+
// CHECK: define linkonce_odr zeroext i1 @_ZN6Test111SIiE7DerivedntEv(
256+
// CHECK: define linkonce_odr dereferenceable(4) %"class.Test11::Base"* @_ZN6Test111SIiE7DerivedixEi(
257+
class Base {
258+
public:
259+
virtual void operator()() {}
260+
virtual bool operator==(const Base &other) { return false; }
261+
virtual bool operator!() { return false; }
262+
virtual Base &operator[](int i) { return *this; }
263+
};
264+
265+
template<class T>
266+
struct S {
267+
class Derived final : public Base {
268+
public:
269+
void operator()() override {}
270+
bool operator==(const Base &other) override { return true; }
271+
bool operator!() override { return true; }
272+
Base &operator[](int i) override { return *this; }
273+
};
274+
275+
Derived *ptr = nullptr, *ptr2 = nullptr;
276+
277+
void foo1() {
278+
if (ptr && ptr2) {
279+
// These calls get devirtualized. Linkage fails if the definitions of
280+
// the called functions are not emitted.
281+
(*ptr)();
282+
(void)(*ptr == *ptr2);
283+
(void)(!(*ptr));
284+
(void)((*ptr)[1]);
285+
}
286+
}
287+
};
288+
289+
void foo2() {
290+
S<int> *s = new S<int>;
291+
s->foo1();
292+
}
293+
}

‎clang/test/CodeGenCXX/vtable-available-externally.cpp

+2-3
Original file line numberDiff line numberDiff line change
@@ -275,9 +275,8 @@ struct C {
275275
virtual D& operator=(const D&);
276276
};
277277

278-
// Cannot emit D's vtable available_externally, because we cannot create
279-
// a reference to the inline virtual D::operator= function.
280-
// CHECK-TEST11: @_ZTVN6Test111DE = external unnamed_addr constant
278+
// Can emit D's vtable available_externally.
279+
// CHECK-TEST11: @_ZTVN6Test111DE = available_externally unnamed_addr constant
281280
struct D : C {
282281
virtual void key();
283282
};

0 commit comments

Comments
 (0)
Please sign in to comment.