forked from OSchip/llvm-project
[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
This commit is contained in:
parent
fa5183b028
commit
2246167362
|
@ -1886,6 +1886,19 @@ public:
|
|||
return (CD->begin_overridden_methods() != CD->end_overridden_methods());
|
||||
}
|
||||
|
||||
/// If it's possible to devirtualize a call to this method, return the called
|
||||
/// function. Otherwise, return null.
|
||||
|
||||
/// \param Base The object on which this virtual function is called.
|
||||
/// \param IsAppleKext True if we are compiling for Apple kext.
|
||||
CXXMethodDecl *getDevirtualizedMethod(const Expr *Base, bool IsAppleKext);
|
||||
|
||||
const CXXMethodDecl *getDevirtualizedMethod(const Expr *Base,
|
||||
bool IsAppleKext) const {
|
||||
return const_cast<CXXMethodDecl *>(this)->getDevirtualizedMethod(
|
||||
Base, IsAppleKext);
|
||||
}
|
||||
|
||||
/// \brief Determine whether this is a usual deallocation function
|
||||
/// (C++ [basic.stc.dynamic.deallocation]p2), which is an overloaded
|
||||
/// delete or delete[] operator with a particular signature.
|
||||
|
|
|
@ -3944,7 +3944,7 @@ public:
|
|||
void MarkFunctionReferenced(SourceLocation Loc, FunctionDecl *Func,
|
||||
bool MightBeOdrUse = true);
|
||||
void MarkVariableReferenced(SourceLocation Loc, VarDecl *Var);
|
||||
void MarkDeclRefReferenced(DeclRefExpr *E);
|
||||
void MarkDeclRefReferenced(DeclRefExpr *E, const Expr *Base = nullptr);
|
||||
void MarkMemberReferenced(MemberExpr *E);
|
||||
|
||||
void UpdateMarkingForLValueToRValue(Expr *E);
|
||||
|
|
|
@ -1605,6 +1605,84 @@ CXXMethodDecl *CXXMethodDecl::CreateDeserialized(ASTContext &C, unsigned ID) {
|
|||
SC_None, false, false, SourceLocation());
|
||||
}
|
||||
|
||||
CXXMethodDecl *CXXMethodDecl::getDevirtualizedMethod(const Expr *Base,
|
||||
bool IsAppleKext) {
|
||||
assert(isVirtual() && "this method is expected to be virtual");
|
||||
|
||||
// When building with -fapple-kext, all calls must go through the vtable since
|
||||
// the kernel linker can do runtime patching of vtables.
|
||||
if (IsAppleKext)
|
||||
return nullptr;
|
||||
|
||||
// If the member function is marked 'final', we know that it can't be
|
||||
// overridden and can therefore devirtualize it unless it's pure virtual.
|
||||
if (hasAttr<FinalAttr>())
|
||||
return isPure() ? nullptr : this;
|
||||
|
||||
// If Base is unknown, we cannot devirtualize.
|
||||
if (!Base)
|
||||
return nullptr;
|
||||
|
||||
// If the base expression (after skipping derived-to-base conversions) is a
|
||||
// class prvalue, then we can devirtualize.
|
||||
Base = Base->getBestDynamicClassTypeExpr();
|
||||
if (Base->isRValue() && Base->getType()->isRecordType())
|
||||
return this;
|
||||
|
||||
// If we don't even know what we would call, we can't devirtualize.
|
||||
const CXXRecordDecl *BestDynamicDecl = Base->getBestDynamicClassType();
|
||||
if (!BestDynamicDecl)
|
||||
return nullptr;
|
||||
|
||||
// There may be a method corresponding to MD in a derived class.
|
||||
CXXMethodDecl *DevirtualizedMethod =
|
||||
getCorrespondingMethodInClass(BestDynamicDecl);
|
||||
|
||||
// If that method is pure virtual, we can't devirtualize. If this code is
|
||||
// reached, the result would be UB, not a direct call to the derived class
|
||||
// function, and we can't assume the derived class function is defined.
|
||||
if (DevirtualizedMethod->isPure())
|
||||
return nullptr;
|
||||
|
||||
// If that method is marked final, we can devirtualize it.
|
||||
if (DevirtualizedMethod->hasAttr<FinalAttr>())
|
||||
return DevirtualizedMethod;
|
||||
|
||||
// Similarly, if the class itself is marked 'final' it can't be overridden
|
||||
// and we can therefore devirtualize the member function call.
|
||||
if (BestDynamicDecl->hasAttr<FinalAttr>())
|
||||
return DevirtualizedMethod;
|
||||
|
||||
if (const DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(Base)) {
|
||||
if (const VarDecl *VD = dyn_cast<VarDecl>(DRE->getDecl()))
|
||||
if (VD->getType()->isRecordType())
|
||||
// This is a record decl. We know the type and can devirtualize it.
|
||||
return DevirtualizedMethod;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// We can devirtualize calls on an object accessed by a class member access
|
||||
// expression, since by C++11 [basic.life]p6 we know that it can't refer to
|
||||
// a derived class object constructed in the same location.
|
||||
if (const MemberExpr *ME = dyn_cast<MemberExpr>(Base))
|
||||
if (const ValueDecl *VD = dyn_cast<ValueDecl>(ME->getMemberDecl()))
|
||||
return VD->getType()->isRecordType() ? DevirtualizedMethod : nullptr;
|
||||
|
||||
// Likewise for calls on an object accessed by a (non-reference) pointer to
|
||||
// member access.
|
||||
if (auto *BO = dyn_cast<BinaryOperator>(Base)) {
|
||||
if (BO->isPtrMemOp()) {
|
||||
auto *MPT = BO->getRHS()->getType()->castAs<MemberPointerType>();
|
||||
if (MPT->getPointeeType()->isRecordType())
|
||||
return DevirtualizedMethod;
|
||||
}
|
||||
}
|
||||
|
||||
// We can't devirtualize the call.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool CXXMethodDecl::isUsualDeallocationFunction() const {
|
||||
if (getOverloadedOperator() != OO_Delete &&
|
||||
getOverloadedOperator() != OO_Array_Delete)
|
||||
|
|
|
@ -2716,88 +2716,6 @@ llvm::Value *CodeGenFunction::EmitVTableTypeCheckedLoad(
|
|||
cast<llvm::PointerType>(VTable->getType())->getElementType());
|
||||
}
|
||||
|
||||
bool
|
||||
CodeGenFunction::CanDevirtualizeMemberFunctionCall(const Expr *Base,
|
||||
const CXXMethodDecl *MD) {
|
||||
// When building with -fapple-kext, all calls must go through the vtable since
|
||||
// the kernel linker can do runtime patching of vtables.
|
||||
if (getLangOpts().AppleKext)
|
||||
return false;
|
||||
|
||||
// If the member function is marked 'final', we know that it can't be
|
||||
// overridden and can therefore devirtualize it unless it's pure virtual.
|
||||
if (MD->hasAttr<FinalAttr>())
|
||||
return !MD->isPure();
|
||||
|
||||
// If the base expression (after skipping derived-to-base conversions) is a
|
||||
// class prvalue, then we can devirtualize.
|
||||
Base = Base->getBestDynamicClassTypeExpr();
|
||||
if (Base->isRValue() && Base->getType()->isRecordType())
|
||||
return true;
|
||||
|
||||
// If we don't even know what we would call, we can't devirtualize.
|
||||
const CXXRecordDecl *BestDynamicDecl = Base->getBestDynamicClassType();
|
||||
if (!BestDynamicDecl)
|
||||
return false;
|
||||
|
||||
// There may be a method corresponding to MD in a derived class.
|
||||
const CXXMethodDecl *DevirtualizedMethod =
|
||||
MD->getCorrespondingMethodInClass(BestDynamicDecl);
|
||||
|
||||
// If that method is pure virtual, we can't devirtualize. If this code is
|
||||
// reached, the result would be UB, not a direct call to the derived class
|
||||
// function, and we can't assume the derived class function is defined.
|
||||
if (DevirtualizedMethod->isPure())
|
||||
return false;
|
||||
|
||||
// If that method is marked final, we can devirtualize it.
|
||||
if (DevirtualizedMethod->hasAttr<FinalAttr>())
|
||||
return true;
|
||||
|
||||
// Similarly, if the class itself is marked 'final' it can't be overridden
|
||||
// and we can therefore devirtualize the member function call.
|
||||
if (BestDynamicDecl->hasAttr<FinalAttr>())
|
||||
return true;
|
||||
|
||||
if (const DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(Base)) {
|
||||
if (const VarDecl *VD = dyn_cast<VarDecl>(DRE->getDecl())) {
|
||||
// This is a record decl. We know the type and can devirtualize it.
|
||||
return VD->getType()->isRecordType();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// We can devirtualize calls on an object accessed by a class member access
|
||||
// expression, since by C++11 [basic.life]p6 we know that it can't refer to
|
||||
// a derived class object constructed in the same location. However, we avoid
|
||||
// devirtualizing a call to a template function that we could instantiate
|
||||
// implicitly, but have not decided to do so. This is needed because if this
|
||||
// function does not get instantiated, the devirtualization will create a
|
||||
// direct call to a function whose body may not exist. In contrast, calls to
|
||||
// template functions that are not defined in this TU are allowed to be
|
||||
// devirtualized under assumption that it is user responsibility to
|
||||
// instantiate them in some other TU.
|
||||
if (const MemberExpr *ME = dyn_cast<MemberExpr>(Base))
|
||||
if (const ValueDecl *VD = dyn_cast<ValueDecl>(ME->getMemberDecl()))
|
||||
return VD->getType()->isRecordType() &&
|
||||
(MD->instantiationIsPending() || MD->isDefined() ||
|
||||
!MD->isImplicitlyInstantiable());
|
||||
|
||||
// Likewise for calls on an object accessed by a (non-reference) pointer to
|
||||
// member access.
|
||||
if (auto *BO = dyn_cast<BinaryOperator>(Base)) {
|
||||
if (BO->isPtrMemOp()) {
|
||||
auto *MPT = BO->getRHS()->getType()->castAs<MemberPointerType>();
|
||||
if (MPT->getPointeeType()->isRecordType())
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// We can't devirtualize the call.
|
||||
return false;
|
||||
}
|
||||
|
||||
void CodeGenFunction::EmitForwardingCallToLambda(
|
||||
const CXXMethodDecl *callOperator,
|
||||
CallArgList &callArgs) {
|
||||
|
|
|
@ -199,7 +199,8 @@ RValue CodeGenFunction::EmitCXXMemberOrOperatorMemberCallExpr(
|
|||
bool CanUseVirtualCall = MD->isVirtual() && !HasQualifier;
|
||||
|
||||
const CXXMethodDecl *DevirtualizedMethod = nullptr;
|
||||
if (CanUseVirtualCall && CanDevirtualizeMemberFunctionCall(Base, MD)) {
|
||||
if (CanUseVirtualCall &&
|
||||
MD->getDevirtualizedMethod(Base, getLangOpts().AppleKext)) {
|
||||
const CXXRecordDecl *BestDynamicDecl = Base->getBestDynamicClassType();
|
||||
DevirtualizedMethod = MD->getCorrespondingMethodInClass(BestDynamicDecl);
|
||||
assert(DevirtualizedMethod);
|
||||
|
|
|
@ -1752,11 +1752,6 @@ public:
|
|||
llvm::Value *EmitVTableTypeCheckedLoad(const CXXRecordDecl *RD, llvm::Value *VTable,
|
||||
uint64_t VTableByteOffset);
|
||||
|
||||
/// CanDevirtualizeMemberFunctionCalls - Checks whether virtual calls on given
|
||||
/// expr can be devirtualized.
|
||||
bool CanDevirtualizeMemberFunctionCall(const Expr *Base,
|
||||
const CXXMethodDecl *MD);
|
||||
|
||||
/// EnterDtorCleanups - Enter the cleanups necessary to complete the
|
||||
/// given phase of destruction for a destructor. The end result
|
||||
/// should call destructors on members and base classes in reverse
|
||||
|
|
|
@ -14665,24 +14665,24 @@ static void MarkExprReferenced(Sema &SemaRef, SourceLocation Loc,
|
|||
ME->performsVirtualDispatch(SemaRef.getLangOpts());
|
||||
if (!IsVirtualCall)
|
||||
return;
|
||||
const Expr *Base = ME->getBase();
|
||||
const CXXRecordDecl *MostDerivedClassDecl = Base->getBestDynamicClassType();
|
||||
if (!MostDerivedClassDecl)
|
||||
return;
|
||||
CXXMethodDecl *DM = MD->getCorrespondingMethodInClass(MostDerivedClassDecl);
|
||||
if (!DM || DM->isPure())
|
||||
return;
|
||||
SemaRef.MarkAnyDeclReferenced(Loc, DM, MightBeOdrUse);
|
||||
|
||||
// If it's possible to devirtualize the call, mark the called function
|
||||
// referenced.
|
||||
CXXMethodDecl *DM = MD->getDevirtualizedMethod(
|
||||
ME->getBase(), SemaRef.getLangOpts().AppleKext);
|
||||
if (DM)
|
||||
SemaRef.MarkAnyDeclReferenced(Loc, DM, MightBeOdrUse);
|
||||
}
|
||||
|
||||
/// \brief Perform reference-marking and odr-use handling for a DeclRefExpr.
|
||||
void Sema::MarkDeclRefReferenced(DeclRefExpr *E) {
|
||||
void Sema::MarkDeclRefReferenced(DeclRefExpr *E, const Expr *Base) {
|
||||
// TODO: update this with DR# once a defect report is filed.
|
||||
// C++11 defect. The address of a pure member should not be an ODR use, even
|
||||
// if it's a qualified reference.
|
||||
bool OdrUse = true;
|
||||
if (CXXMethodDecl *Method = dyn_cast<CXXMethodDecl>(E->getDecl()))
|
||||
if (Method->isVirtual())
|
||||
if (const CXXMethodDecl *Method = dyn_cast<CXXMethodDecl>(E->getDecl()))
|
||||
if (Method->isVirtual() &&
|
||||
!Method->getDevirtualizedMethod(Base, getLangOpts().AppleKext))
|
||||
OdrUse = false;
|
||||
MarkExprReferenced(*this, E->getLocation(), E->getDecl(), E, OdrUse);
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ static bool functionHasPassObjectSizeParams(const FunctionDecl *FD) {
|
|||
/// A convenience routine for creating a decayed reference to a function.
|
||||
static ExprResult
|
||||
CreateFunctionRefExpr(Sema &S, FunctionDecl *Fn, NamedDecl *FoundDecl,
|
||||
bool HadMultipleCandidates,
|
||||
const Expr *Base, bool HadMultipleCandidates,
|
||||
SourceLocation Loc = SourceLocation(),
|
||||
const DeclarationNameLoc &LocInfo = DeclarationNameLoc()){
|
||||
if (S.DiagnoseUseOfDecl(FoundDecl, Loc))
|
||||
|
@ -68,7 +68,7 @@ CreateFunctionRefExpr(Sema &S, FunctionDecl *Fn, NamedDecl *FoundDecl,
|
|||
if (HadMultipleCandidates)
|
||||
DRE->setHadMultipleCandidates(true);
|
||||
|
||||
S.MarkDeclRefReferenced(DRE);
|
||||
S.MarkDeclRefReferenced(DRE, Base);
|
||||
return S.ImpCastExprToType(DRE, S.Context.getPointerType(DRE->getType()),
|
||||
CK_FunctionToPointerDecay);
|
||||
}
|
||||
|
@ -11946,6 +11946,7 @@ Sema::CreateOverloadedUnaryOp(SourceLocation OpLoc, UnaryOperatorKind Opc,
|
|||
FunctionDecl *FnDecl = Best->Function;
|
||||
|
||||
if (FnDecl) {
|
||||
Expr *Base = nullptr;
|
||||
// We matched an overloaded operator. Build a call to that
|
||||
// operator.
|
||||
|
||||
|
@ -11958,7 +11959,7 @@ Sema::CreateOverloadedUnaryOp(SourceLocation OpLoc, UnaryOperatorKind Opc,
|
|||
Best->FoundDecl, Method);
|
||||
if (InputRes.isInvalid())
|
||||
return ExprError();
|
||||
Input = InputRes.get();
|
||||
Base = Input = InputRes.get();
|
||||
} else {
|
||||
// Convert the arguments.
|
||||
ExprResult InputInit
|
||||
|
@ -11974,7 +11975,8 @@ Sema::CreateOverloadedUnaryOp(SourceLocation OpLoc, UnaryOperatorKind Opc,
|
|||
|
||||
// Build the actual expression node.
|
||||
ExprResult FnExpr = CreateFunctionRefExpr(*this, FnDecl, Best->FoundDecl,
|
||||
HadMultipleCandidates, OpLoc);
|
||||
Base, HadMultipleCandidates,
|
||||
OpLoc);
|
||||
if (FnExpr.isInvalid())
|
||||
return ExprError();
|
||||
|
||||
|
@ -12159,6 +12161,7 @@ Sema::CreateOverloadedBinOp(SourceLocation OpLoc,
|
|||
FunctionDecl *FnDecl = Best->Function;
|
||||
|
||||
if (FnDecl) {
|
||||
Expr *Base = nullptr;
|
||||
// We matched an overloaded operator. Build a call to that
|
||||
// operator.
|
||||
|
||||
|
@ -12180,7 +12183,7 @@ Sema::CreateOverloadedBinOp(SourceLocation OpLoc,
|
|||
Best->FoundDecl, Method);
|
||||
if (Arg0.isInvalid())
|
||||
return ExprError();
|
||||
Args[0] = Arg0.getAs<Expr>();
|
||||
Base = Args[0] = Arg0.getAs<Expr>();
|
||||
Args[1] = RHS = Arg1.getAs<Expr>();
|
||||
} else {
|
||||
// Convert the arguments.
|
||||
|
@ -12204,7 +12207,7 @@ Sema::CreateOverloadedBinOp(SourceLocation OpLoc,
|
|||
|
||||
// Build the actual expression node.
|
||||
ExprResult FnExpr = CreateFunctionRefExpr(*this, FnDecl,
|
||||
Best->FoundDecl,
|
||||
Best->FoundDecl, Base,
|
||||
HadMultipleCandidates, OpLoc);
|
||||
if (FnExpr.isInvalid())
|
||||
return ExprError();
|
||||
|
@ -12426,6 +12429,7 @@ Sema::CreateOverloadedArraySubscriptExpr(SourceLocation LLoc,
|
|||
OpLocInfo.setCXXOperatorNameRange(SourceRange(LLoc, RLoc));
|
||||
ExprResult FnExpr = CreateFunctionRefExpr(*this, FnDecl,
|
||||
Best->FoundDecl,
|
||||
Base,
|
||||
HadMultipleCandidates,
|
||||
OpLocInfo.getLoc(),
|
||||
OpLocInfo.getInfo());
|
||||
|
@ -12984,7 +12988,7 @@ Sema::BuildCallToObjectOfClassType(Scope *S, Expr *Obj,
|
|||
Context.DeclarationNames.getCXXOperatorName(OO_Call), LParenLoc);
|
||||
OpLocInfo.setCXXOperatorNameRange(SourceRange(LParenLoc, RParenLoc));
|
||||
ExprResult NewFn = CreateFunctionRefExpr(*this, Method, Best->FoundDecl,
|
||||
HadMultipleCandidates,
|
||||
Obj, HadMultipleCandidates,
|
||||
OpLocInfo.getLoc(),
|
||||
OpLocInfo.getInfo());
|
||||
if (NewFn.isInvalid())
|
||||
|
@ -13175,7 +13179,7 @@ Sema::BuildOverloadedArrowExpr(Scope *S, Expr *Base, SourceLocation OpLoc,
|
|||
|
||||
// Build the operator call.
|
||||
ExprResult FnExpr = CreateFunctionRefExpr(*this, Method, Best->FoundDecl,
|
||||
HadMultipleCandidates, OpLoc);
|
||||
Base, HadMultipleCandidates, OpLoc);
|
||||
if (FnExpr.isInvalid())
|
||||
return ExprError();
|
||||
|
||||
|
@ -13234,7 +13238,7 @@ ExprResult Sema::BuildLiteralOperatorCall(LookupResult &R,
|
|||
|
||||
FunctionDecl *FD = Best->Function;
|
||||
ExprResult Fn = CreateFunctionRefExpr(*this, FD, Best->FoundDecl,
|
||||
HadMultipleCandidates,
|
||||
nullptr, HadMultipleCandidates,
|
||||
SuffixInfo.getLoc(),
|
||||
SuffixInfo.getInfo());
|
||||
if (Fn.isInvalid())
|
||||
|
|
|
@ -21,7 +21,7 @@ public:
|
|||
struct Wrapper {
|
||||
TmplWithArray<bool, 10> data;
|
||||
bool indexIt(int a) {
|
||||
if (a > 6) return data[a] ; // Should not devirtualize
|
||||
if (a > 6) return data[a] ; // Should devirtualize
|
||||
if (a > 4) return data.func1(a); // Should devirtualize
|
||||
return data.func2(a); // Should devirtualize
|
||||
}
|
||||
|
@ -53,7 +53,7 @@ bool stuff(int p)
|
|||
}
|
||||
#endif
|
||||
|
||||
// CHECK-NOT: call {{.*}} @_ZN13TmplWithArrayIbLi10EEixEi
|
||||
// CHECK-DAG: call {{.*}} @_ZN13TmplWithArrayIbLi10EEixEi
|
||||
// CHECK-DAG: call {{.*}} @_ZN13TmplWithArrayIbLi10EE5func1Ei
|
||||
// CHECK-DAG: call {{.*}} @_ZN13TmplWithArrayIbLi10EE5func2Ei
|
||||
|
||||
|
|
|
@ -241,3 +241,53 @@ namespace Test10 {
|
|||
return static_cast<A *>(b)->f();
|
||||
}
|
||||
}
|
||||
|
||||
namespace Test11 {
|
||||
// Check that the definitions of Derived's operators are emitted.
|
||||
|
||||
// CHECK-LABEL: define linkonce_odr void @_ZN6Test111SIiE4foo1Ev(
|
||||
// CHECK: call void @_ZN6Test111SIiE7DerivedclEv(
|
||||
// CHECK: call zeroext i1 @_ZN6Test111SIiE7DerivedeqERKNS_4BaseE(
|
||||
// CHECK: call zeroext i1 @_ZN6Test111SIiE7DerivedntEv(
|
||||
// CHECK: call dereferenceable(4) %"class.Test11::Base"* @_ZN6Test111SIiE7DerivedixEi(
|
||||
// CHECK: define linkonce_odr void @_ZN6Test111SIiE7DerivedclEv(
|
||||
// CHECK: define linkonce_odr zeroext i1 @_ZN6Test111SIiE7DerivedeqERKNS_4BaseE(
|
||||
// CHECK: define linkonce_odr zeroext i1 @_ZN6Test111SIiE7DerivedntEv(
|
||||
// CHECK: define linkonce_odr dereferenceable(4) %"class.Test11::Base"* @_ZN6Test111SIiE7DerivedixEi(
|
||||
class Base {
|
||||
public:
|
||||
virtual void operator()() {}
|
||||
virtual bool operator==(const Base &other) { return false; }
|
||||
virtual bool operator!() { return false; }
|
||||
virtual Base &operator[](int i) { return *this; }
|
||||
};
|
||||
|
||||
template<class T>
|
||||
struct S {
|
||||
class Derived final : public Base {
|
||||
public:
|
||||
void operator()() override {}
|
||||
bool operator==(const Base &other) override { return true; }
|
||||
bool operator!() override { return true; }
|
||||
Base &operator[](int i) override { return *this; }
|
||||
};
|
||||
|
||||
Derived *ptr = nullptr, *ptr2 = nullptr;
|
||||
|
||||
void foo1() {
|
||||
if (ptr && ptr2) {
|
||||
// These calls get devirtualized. Linkage fails if the definitions of
|
||||
// the called functions are not emitted.
|
||||
(*ptr)();
|
||||
(void)(*ptr == *ptr2);
|
||||
(void)(!(*ptr));
|
||||
(void)((*ptr)[1]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void foo2() {
|
||||
S<int> *s = new S<int>;
|
||||
s->foo1();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -275,9 +275,8 @@ struct C {
|
|||
virtual D& operator=(const D&);
|
||||
};
|
||||
|
||||
// Cannot emit D's vtable available_externally, because we cannot create
|
||||
// a reference to the inline virtual D::operator= function.
|
||||
// CHECK-TEST11: @_ZTVN6Test111DE = external unnamed_addr constant
|
||||
// Can emit D's vtable available_externally.
|
||||
// CHECK-TEST11: @_ZTVN6Test111DE = available_externally unnamed_addr constant
|
||||
struct D : C {
|
||||
virtual void key();
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue