[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:
Akira Hatanaka 2017-07-13 06:08:27 +00:00
parent fa5183b028
commit 2246167362
11 changed files with 172 additions and 114 deletions

View File

@ -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.

View File

@ -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);

View File

@ -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)

View File

@ -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) {

View File

@ -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);

View File

@ -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

View File

@ -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);
}

View File

@ -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())

View File

@ -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

View File

@ -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();
}
}

View File

@ -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();
};