Perform two more constructor/destructor code-size optimizations:

1) emit base destructors as aliases to their unique base class destructors
under some careful conditions.  This is enabled for the same targets that can
support complete-to-base aliases, i.e. not darwin.

2) Emit non-variadic complete constructors for classes with no virtual bases
as calls to the base constructor.  This is enabled on all targets and in
theory can trigger in situations that the alias optimization can't (mostly
involving virtual bases, mostly not yet supported).

These are bundled together because I didn't think it worthwhile to split them,
not because they really need to be.

llvm-svn: 96842
This commit is contained in:
John McCall 2010-02-23 00:48:20 +00:00
parent 886915e3bb
commit f8ff7b9fd1
14 changed files with 425 additions and 60 deletions

View File

@ -710,7 +710,7 @@ public:
CXXConstructorDecl *getDefaultConstructor(ASTContext &Context);
/// getDestructor - Returns the destructor decl for this class.
CXXDestructorDecl *getDestructor(ASTContext &Context);
CXXDestructorDecl *getDestructor(ASTContext &Context) const;
/// isLocalClass - If the class is a local class [class.local], returns
/// the enclosing function declaration.

View File

@ -543,14 +543,14 @@ CXXRecordDecl::getDefaultConstructor(ASTContext &Context) {
return 0;
}
CXXDestructorDecl *CXXRecordDecl::getDestructor(ASTContext &Context) {
CXXDestructorDecl *CXXRecordDecl::getDestructor(ASTContext &Context) const {
QualType ClassType = Context.getTypeDeclType(this);
DeclarationName Name
= Context.DeclarationNames.getCXXDestructorName(
Context.getCanonicalType(ClassType));
DeclContext::lookup_iterator I, E;
DeclContext::lookup_const_iterator I, E;
llvm::tie(I, E) = lookup(Name);
assert(I != E && "Did not find a destructor!");

View File

@ -27,24 +27,88 @@
using namespace clang;
using namespace CodeGen;
/// Determines whether the given function has a trivial body that does
/// not require any specific codegen.
static bool HasTrivialBody(const FunctionDecl *FD) {
Stmt *S = FD->getBody();
if (!S)
return true;
if (isa<CompoundStmt>(S) && cast<CompoundStmt>(S)->body_empty())
return true;
return false;
}
/// Try to emit a base destructor as an alias to its primary
/// base-class destructor.
bool CodeGenModule::TryEmitBaseDestructorAsAlias(const CXXDestructorDecl *D) {
if (!getCodeGenOpts().CXXCtorDtorAliases)
return true;
// If the destructor doesn't have a trivial body, we have to emit it
// separately.
if (!HasTrivialBody(D))
return true;
const CXXRecordDecl *Class = D->getParent();
// If we need to manipulate a VTT parameter, give up.
if (Class->getNumVBases()) {
// Extra Credit: passing extra parameters is perfectly safe
// in many calling conventions, so only bail out if the ctor's
// calling convention is nonstandard.
return true;
}
// If any fields have a non-trivial destructor, we have to emit it
// separately.
for (CXXRecordDecl::field_iterator I = Class->field_begin(),
E = Class->field_end(); I != E; ++I)
if (const RecordType *RT = (*I)->getType()->getAs<RecordType>())
if (!cast<CXXRecordDecl>(RT->getDecl())->hasTrivialDestructor())
return true;
// Try to find a unique base class with a non-trivial destructor.
const CXXRecordDecl *UniqueBase = 0;
for (CXXRecordDecl::base_class_const_iterator I = Class->bases_begin(),
E = Class->bases_end(); I != E; ++I) {
// We're in the base destructor, so skip virtual bases.
if (I->isVirtual()) continue;
// Skip base classes with trivial destructors.
const CXXRecordDecl *Base
= cast<CXXRecordDecl>(I->getType()->getAs<RecordType>()->getDecl());
if (Base->hasTrivialDestructor()) continue;
// If we've already found a base class with a non-trivial
// destructor, give up.
if (UniqueBase) return true;
UniqueBase = Base;
}
// If we didn't find any bases with a non-trivial destructor, then
// the base destructor is actually effectively trivial, which can
// happen if it was needlessly user-defined or if there are virtual
// bases with non-trivial destructors.
if (!UniqueBase)
return true;
// If the base is at a non-zero offset, give up.
const ASTRecordLayout &ClassLayout = Context.getASTRecordLayout(Class);
if (ClassLayout.getBaseClassOffset(UniqueBase) != 0)
return true;
const CXXDestructorDecl *BaseD = UniqueBase->getDestructor(getContext());
return TryEmitDefinitionAsAlias(GlobalDecl(D, Dtor_Base),
GlobalDecl(BaseD, Dtor_Base));
}
/// Try to emit a definition as a global alias for another definition.
bool CodeGenModule::TryEmitDefinitionAsAlias(GlobalDecl AliasDecl,
GlobalDecl TargetDecl) {
if (!getCodeGenOpts().CXXCtorDtorAliases)
return true;
// Find the referrent.
llvm::GlobalValue *Ref = cast<llvm::GlobalValue>(GetAddrOfGlobal(TargetDecl));
// Look for an existing entry.
const char *MangledName = getMangledName(AliasDecl);
llvm::GlobalValue *&Entry = GlobalDeclMap[MangledName];
if (Entry) {
assert(Entry->isDeclaration() && "definition already exists for alias");
assert(Entry->getType() == Ref->getType() &&
"declaration exists with different type");
}
// The alias will use the linkage of the referrent. If we can't
// support aliases with that linkage, fail.
llvm::GlobalValue::LinkageTypes Linkage
@ -72,11 +136,32 @@ bool CodeGenModule::TryEmitDefinitionAsAlias(GlobalDecl AliasDecl,
return true;
}
// Derive the type for the alias.
const llvm::PointerType *AliasType
= getTypes().GetFunctionType(AliasDecl)->getPointerTo();
// Look for an existing entry.
const char *MangledName = getMangledName(AliasDecl);
llvm::GlobalValue *&Entry = GlobalDeclMap[MangledName];
if (Entry) {
assert(Entry->isDeclaration() && "definition already exists for alias");
assert(Entry->getType() == AliasType &&
"declaration exists with different type");
}
// Find the referrent. Some aliases might require a bitcast, in
// which case the caller is responsible for ensuring the soundness
// of these semantics.
llvm::GlobalValue *Ref = cast<llvm::GlobalValue>(GetAddrOfGlobal(TargetDecl));
llvm::Constant *Aliasee = Ref;
if (Ref->getType() != AliasType)
Aliasee = llvm::ConstantExpr::getBitCast(Ref, AliasType);
// Create the alias with no name.
llvm::GlobalAlias *Alias =
new llvm::GlobalAlias(Ref->getType(), Linkage, "", Ref, &getModule());
new llvm::GlobalAlias(AliasType, Linkage, "", Aliasee, &getModule());
// Switch any previous uses to the alias and continue.
// Switch any previous uses to the alias and kill the previous decl.
if (Entry) {
Entry->replaceAllUsesWith(Alias);
Entry->eraseFromParent();
@ -90,7 +175,6 @@ bool CodeGenModule::TryEmitDefinitionAsAlias(GlobalDecl AliasDecl,
return false;
}
void CodeGenModule::EmitCXXConstructors(const CXXConstructorDecl *D) {
// The constructor used for constructing this as a complete class;
// constucts the virtual bases, then calls the base constructor.
@ -169,6 +253,13 @@ void CodeGenModule::EmitCXXDestructor(const CXXDestructorDecl *D,
GlobalDecl(D, Dtor_Base)))
return;
// The base destructor is equivalent to the base destructor of its
// base class if there is exactly one non-virtual base class with a
// non-trivial destructor, there are no fields with a non-trivial
// destructor, and the body of the destructor is trivial.
if (Type == Dtor_Base && !TryEmitBaseDestructorAsAlias(D))
return;
llvm::Function *Fn = cast<llvm::Function>(GetAddrOfCXXDestructor(D, Type));
CodeGenFunction(*this).GenerateCode(GlobalDecl(D, Type), Fn);

View File

@ -416,6 +416,18 @@ bool CodeGenModule::ReturnTypeUsesSret(const CGFunctionInfo &FI) {
return FI.getReturnInfo().isIndirect();
}
const llvm::FunctionType *CodeGenTypes::GetFunctionType(GlobalDecl GD) {
const CGFunctionInfo &FI = getFunctionInfo(GD);
// For definition purposes, don't consider a K&R function variadic.
bool Variadic = false;
if (const FunctionProtoType *FPT =
cast<FunctionDecl>(GD.getDecl())->getType()->getAs<FunctionProtoType>())
Variadic = FPT->isVariadic();
return GetFunctionType(FI, Variadic);
}
const llvm::FunctionType *
CodeGenTypes::GetFunctionType(const CGFunctionInfo &FI, bool IsVariadic) {
std::vector<const llvm::Type*> ArgTys;

View File

@ -891,31 +891,80 @@ static void EmitMemberInitializer(CodeGenFunction &CGF,
}
}
/// Checks whether the given constructor is a valid subject for the
/// complete-to-base constructor delegation optimization, i.e.
/// emitting the complete constructor as a simple call to the base
/// constructor.
static bool IsConstructorDelegationValid(const CXXConstructorDecl *Ctor) {
// Currently we disable the optimization for classes with virtual
// bases because (1) the addresses of parameter variables need to be
// consistent across all initializers but (2) the delegate function
// call necessarily creates a second copy of the parameter variable.
//
// The limiting example (purely theoretical AFAIK):
// struct A { A(int &c) { c++; } };
// struct B : virtual A {
// B(int count) : A(count) { printf("%d\n", count); }
// };
// ...although even this example could in principle be emitted as a
// delegation since the address of the parameter doesn't escape.
if (Ctor->getParent()->getNumVBases()) {
// TODO: white-list trivial vbase initializers. This case wouldn't
// be subject to the restrictions below.
// TODO: white-list cases where:
// - there are no non-reference parameters to the constructor
// - the initializers don't access any non-reference parameters
// - the initializers don't take the address of non-reference
// parameters
// - etc.
// If we ever add any of the above cases, remember that:
// - function-try-blocks will always blacklist this optimization
// - we need to perform the constructor prologue and cleanup in
// EmitConstructorBody.
return false;
}
// We also disable the optimization for variadic functions because
// it's impossible to "re-pass" varargs.
if (Ctor->getType()->getAs<FunctionProtoType>()->isVariadic())
return false;
return true;
}
/// EmitConstructorBody - Emits the body of the current constructor.
void CodeGenFunction::EmitConstructorBody(FunctionArgList &Args) {
const CXXConstructorDecl *Ctor = cast<CXXConstructorDecl>(CurGD.getDecl());
CXXCtorType CtorType = CurGD.getCtorType();
// Before we go any further, try the complete->base constructor
// delegation optimization.
if (CtorType == Ctor_Complete && IsConstructorDelegationValid(Ctor)) {
EmitDelegateCXXConstructorCall(Ctor, Ctor_Base, Args);
return;
}
Stmt *Body = Ctor->getBody();
// Some of the optimizations we want to do can't be done with
// function try blocks.
// Enter the function-try-block before the constructor prologue if
// applicable.
CXXTryStmtInfo TryInfo;
bool isTryBody = (Body && isa<CXXTryStmt>(Body));
if (isTryBody)
bool IsTryBody = (Body && isa<CXXTryStmt>(Body));
if (IsTryBody)
TryInfo = EnterCXXTryStmt(*cast<CXXTryStmt>(Body));
unsigned CleanupStackSize = CleanupEntries.size();
// Emit the constructor prologue, i.e. the base and member initializers.
// TODO: for non-variadic complete-object constructors without a
// function try block for a body, we can get away with just emitting
// the vbase initializers, then calling the base constructor.
// Emit the constructor prologue, i.e. the base and member
// initializers.
EmitCtorPrologue(Ctor, CtorType);
// Emit the body of the statement.
if (isTryBody)
if (IsTryBody)
EmitStmt(cast<CXXTryStmt>(Body)->getTryBlock());
else if (Body)
EmitStmt(Body);
@ -933,7 +982,7 @@ void CodeGenFunction::EmitConstructorBody(FunctionArgList &Args) {
// constructed.
EmitCleanupBlocks(CleanupStackSize);
if (isTryBody)
if (IsTryBody)
ExitCXXTryStmt(*cast<CXXTryStmt>(Body), TryInfo);
}
@ -1406,6 +1455,71 @@ CodeGenFunction::EmitCXXConstructorCall(const CXXConstructorDecl *D,
EmitCXXMemberCall(D, Callee, ReturnValueSlot(), This, VTT, ArgBeg, ArgEnd);
}
void
CodeGenFunction::EmitDelegateCXXConstructorCall(const CXXConstructorDecl *Ctor,
CXXCtorType CtorType,
const FunctionArgList &Args) {
CallArgList DelegateArgs;
FunctionArgList::const_iterator I = Args.begin(), E = Args.end();
assert(I != E && "no parameters to constructor");
// this
DelegateArgs.push_back(std::make_pair(RValue::get(LoadCXXThis()),
I->second));
++I;
// vtt
if (llvm::Value *VTT = GetVTTParameter(*this, GlobalDecl(Ctor, CtorType))) {
QualType VoidPP = getContext().getPointerType(getContext().VoidPtrTy);
DelegateArgs.push_back(std::make_pair(RValue::get(VTT), VoidPP));
if (CGVtableInfo::needsVTTParameter(CurGD)) {
assert(I != E && "cannot skip vtt parameter, already done with args");
assert(I->second == VoidPP && "skipping parameter not of vtt type");
++I;
}
}
// Explicit arguments.
for (; I != E; ++I) {
const VarDecl *Param = I->first;
QualType ArgType = Param->getType(); // because we're passing it to itself
// StartFunction converted the ABI-lowered parameter(s) into a
// local alloca. We need to turn that into an r-value suitable
// for EmitCall.
llvm::Value *Local = GetAddrOfLocalVar(Param);
RValue Arg;
// For the most part, we just need to load the alloca, except:
// 1) aggregate r-values are actually pointers to temporaries, and
// 2) references to aggregates are pointers directly to the aggregate.
// I don't know why references to non-aggregates are different here.
if (ArgType->isReferenceType()) {
const ReferenceType *RefType = ArgType->getAs<ReferenceType>();
if (hasAggregateLLVMType(RefType->getPointeeType()))
Arg = RValue::getAggregate(Local);
else
// Locals which are references to scalars are represented
// with allocas holding the pointer.
Arg = RValue::get(Builder.CreateLoad(Local));
} else {
if (hasAggregateLLVMType(ArgType))
Arg = RValue::getAggregate(Local);
else
Arg = RValue::get(EmitLoadOfScalar(Local, false, ArgType));
}
DelegateArgs.push_back(std::make_pair(Arg, ArgType));
}
EmitCall(CGM.getTypes().getFunctionInfo(Ctor, CtorType),
CGM.GetAddrOfCXXConstructor(Ctor, CtorType),
ReturnValueSlot(), DelegateArgs, Ctor);
}
void CodeGenFunction::EmitCXXDestructorCall(const CXXDestructorDecl *DD,
CXXDtorType Type,
llvm::Value *This) {

View File

@ -789,6 +789,9 @@ public:
const CXXRecordDecl *BaseClassDecl,
QualType Ty);
void EmitDelegateCXXConstructorCall(const CXXConstructorDecl *Ctor,
CXXCtorType CtorType,
const FunctionArgList &Args);
void EmitCXXConstructorCall(const CXXConstructorDecl *D, CXXCtorType Type,
llvm::Value *This,
CallExpr::const_arg_iterator ArgBeg,

View File

@ -1195,28 +1195,8 @@ static void ReplaceUsesOfNonProtoTypeWithRealFunction(llvm::GlobalValue *Old,
void CodeGenModule::EmitGlobalFunctionDefinition(GlobalDecl GD) {
const llvm::FunctionType *Ty;
const FunctionDecl *D = cast<FunctionDecl>(GD.getDecl());
if (const CXXMethodDecl *MD = dyn_cast<CXXMethodDecl>(D)) {
bool isVariadic = D->getType()->getAs<FunctionProtoType>()->isVariadic();
Ty = getTypes().GetFunctionType(getTypes().getFunctionInfo(MD), isVariadic);
} else {
Ty = cast<llvm::FunctionType>(getTypes().ConvertType(D->getType()));
// As a special case, make sure that definitions of K&R function
// "type foo()" aren't declared as varargs (which forces the backend
// to do unnecessary work).
if (D->getType()->isFunctionNoProtoType()) {
assert(Ty->isVarArg() && "Didn't lower type as expected");
// Due to stret, the lowered function could have arguments.
// Just create the same type as was lowered by ConvertType
// but strip off the varargs bit.
std::vector<const llvm::Type*> Args(Ty->param_begin(), Ty->param_end());
Ty = llvm::FunctionType::get(Ty->getReturnType(), Args, false);
}
}
const llvm::FunctionType *Ty = getTypes().GetFunctionType(GD);
// Get or create the prototype for the function.
llvm::Constant *Entry = GetAddrOfFunction(GD, Ty);

View File

@ -485,6 +485,7 @@ private:
// C++ related functions.
bool TryEmitDefinitionAsAlias(GlobalDecl Alias, GlobalDecl Target);
bool TryEmitBaseDestructorAsAlias(const CXXDestructorDecl *D);
void EmitNamespace(const NamespaceDecl *D);
void EmitLinkageSpec(const LinkageSpecDecl *D);

View File

@ -168,6 +168,8 @@ public:
const llvm::FunctionType *GetFunctionType(const CGFunctionInfo &Info,
bool IsVariadic);
const llvm::FunctionType *GetFunctionType(GlobalDecl GD);
/// GetFunctionTypeForVtable - Get the LLVM function type for use in a vtable,
/// given a CXXMethodDecl. If the method to has an incomplete return type,

View File

@ -0,0 +1,94 @@
// RUN: %clang_cc1 -triple x86_64-apple-darwin10 %s -emit-llvm -o - | FileCheck %s
struct Member { int x; Member(); Member(int); Member(const Member &); };
struct VBase { int x; VBase(); VBase(int); VBase(const VBase &); };
struct ValueClass {
ValueClass(int x, int y) : x(x), y(y) {}
int x;
int y;
}; // subject to ABI trickery
/* Test basic functionality. */
class A {
A(struct Undeclared &);
A(ValueClass);
Member mem;
};
A::A(struct Undeclared &ref) : mem(0) {}
// Check that delegation works.
// CHECK: define void @_ZN1AC1ER10Undeclared(
// CHECK: call void @_ZN1AC2ER10Undeclared(
// CHECK: define void @_ZN1AC2ER10Undeclared(
// CHECK: call void @_ZN6MemberC1Ei(
A::A(ValueClass v) : mem(v.y - v.x) {}
// CHECK: define void @_ZN1AC1E10ValueClass(
// CHECK: call void @_ZN1AC2E10ValueClass(
// CHECK: define void @_ZN1AC2E10ValueClass(
// CHECK: call void @_ZN6MemberC1Ei(
/* Test that things work for inheritance. */
struct B : A {
B(struct Undeclared &);
Member mem;
};
B::B(struct Undeclared &ref) : A(ref), mem(1) {}
// CHECK: define void @_ZN1BC1ER10Undeclared(
// CHECK: call void @_ZN1BC2ER10Undeclared(
// CHECK: define void @_ZN1BC2ER10Undeclared(
// CHECK: call void @_ZN1AC2ER10Undeclared(
// CHECK: call void @_ZN6MemberC1Ei(
/* Test that the delegation optimization is disabled for classes with
virtual bases (for now). This is necessary because a vbase
initializer could access one of the parameter variables by
reference. That's a solvable problem, but let's not solve it right
now. */
struct C : virtual A {
C(int);
Member mem;
};
C::C(int x) : A(ValueClass(x, x+1)), mem(x * x) {}
// CHECK: define void @_ZN1CC1Ei(
// CHECK: call void @_ZN10ValueClassC1Eii(
// CHECK: call void @_ZN1AC2E10ValueClass(
// CHECK: call void @_ZN6MemberC1Ei(
// CHECK: define void @_ZN1CC2Ei(
// CHECK: call void @_ZN6MemberC1Ei(
/* Test that the delegation optimization is disabled for varargs
constructors. */
struct D : A {
D(int, ...);
Member mem;
};
D::D(int x, ...) : A(ValueClass(x, x+1)), mem(x*x) {}
// CHECK: define void @_ZN1DC1Eiz(
// CHECK: call void @_ZN10ValueClassC1Eii(
// CHECK: call void @_ZN1AC2E10ValueClass(
// CHECK: call void @_ZN6MemberC1Ei(
// CHECK: define void @_ZN1DC2Eiz(
// CHECK: call void @_ZN10ValueClassC1Eii(
// CHECK: call void @_ZN1AC2E10ValueClass(
// CHECK: call void @_ZN6MemberC1Ei(

View File

@ -43,11 +43,7 @@ struct C {
};
// CHECK: define void @_ZN1CC1Ev(
// CHECK: call void @_ZN2A1C1Ev(
// CHECK: call void @_ZN2A2C1Ev(
// CHECK: call void @_ZN1BC1ERK2A1RK2A2(
// CHECK: call void @_ZN2A2D1Ev
// CHECK: call void @_ZN2A1D1Ev
// CHECK: call void @_ZN1CC2Ev(
// CHECK: define void @_ZN1CC2Ev(
// CHECK: call void @_ZN2A1C1Ev(

View File

@ -1,4 +1,11 @@
// RUN: %clang_cc1 %s -emit-llvm -o - -mconstructor-aliases | FileCheck %s
// CHECK: @_ZN5test01AD1Ev = alias {{.*}} @_ZN5test01AD2Ev
// CHECK: @_ZN5test11MD2Ev = alias {{.*}} @_ZN5test11AD2Ev
// CHECK: @_ZN5test11ND2Ev = alias {{.*}} @_ZN5test11AD2Ev
// CHECK: @_ZN5test11OD2Ev = alias {{.*}} @_ZN5test11AD2Ev
// CHECK: @_ZN5test11SD2Ev = alias bitcast {{.*}} @_ZN5test11AD2Ev
struct A {
int a;
@ -60,7 +67,7 @@ namespace test0 {
// The function-try-block won't suppress -mconstructor-aliases here.
A::~A() try { } catch (int i) {}
// CHECK: @_ZN5test01AD1Ev = alias {{.*}} @_ZN5test01AD2Ev
// complete destructor alias tested above
// CHECK: define void @_ZN5test01AD2Ev
// CHECK: invoke void @_ZN5test06MemberD1Ev
@ -89,3 +96,40 @@ namespace test0 {
// CHECK: invoke void @_ZN5test04BaseD2Ev
// CHECK: unwind label [[BASE_UNWIND:%[a-zA-Z0-9.]+]]
}
// Test base-class aliasing.
namespace test1 {
struct A { ~A(); char ***m; }; // non-trivial destructor
struct B { ~B(); }; // non-trivial destructor
struct Empty { }; // trivial destructor, empty
struct NonEmpty { int x; }; // trivial destructor, non-empty
struct M : A { ~M(); };
M::~M() {} // alias tested above
struct N : A, Empty { ~N(); };
N::~N() {} // alias tested above
struct O : Empty, A { ~O(); };
O::~O() {} // alias tested above
struct P : NonEmpty, A { ~P(); };
P::~P() {} // CHECK: define void @_ZN5test11PD2Ev
struct Q : A, B { ~Q(); };
Q::~Q() {} // CHECK: define void @_ZN5test11QD2Ev
struct R : A { ~R(); };
R::~R() { A a; } // CHECK: define void @_ZN5test11RD2Ev
struct S : A { ~S(); int x; };
S::~S() {} // alias tested above
struct T : A { ~T(); B x; };
T::~T() {} // CHECK: define void @_ZN5test11TD2Ev
// The VTT parameter prevents this. We could still make this work
// for calling conventions that are safe against extra parameters.
struct U : A, virtual B { ~U(); };
U::~U() {} // CHECK: define void @_ZN5test11UD2Ev
}

View File

@ -1,16 +1,25 @@
// RUN: %clang_cc1 -emit-llvm %s -o - -triple=x86_64-apple-darwin10 -mconstructor-aliases | FileCheck %s
struct Member {
~Member();
};
struct A {
virtual ~A();
};
struct B : A {
Member m;
virtual ~B();
};
// Complete dtor: just an alias because there are no virtual bases.
// CHECK: @_ZN1BD1Ev = alias {{.*}} @_ZN1BD2Ev
// (aliases from C)
// CHECK: @_ZN1CD1Ev = alias {{.*}} @_ZN1CD2Ev
// CHECK: @_ZN1CD2Ev = alias bitcast {{.*}} @_ZN1BD2Ev
// Deleting dtor: defers to the complete dtor.
// CHECK: define void @_ZN1BD0Ev
// CHECK: call void @_ZN1BD1Ev
@ -18,6 +27,22 @@ struct B : A {
// Base dtor: actually calls A's base dtor.
// CHECK: define void @_ZN1BD2Ev
// CHECK: call void @_ZN6MemberD1Ev
// CHECK: call void @_ZN1AD2Ev
B::~B() { }
struct C : B {
~C();
};
C::~C() { }
// Complete dtor: just an alias (checked above).
// Deleting dtor: defers to the complete dtor.
// CHECK: define void @_ZN1CD0Ev
// CHECK: call void @_ZN1CD1Ev
// CHECK: call void @_ZdlPv
// Base dtor: just an alias to B's base dtor.

View File

@ -42,13 +42,16 @@ struct B : Base {
void f() { B b; }
// CHECK: define linkonce_odr void @_ZN1BC1Ev(
// CHECK: call void @_ZN4BaseC2Ev(
// CHECK: store i8** getelementptr inbounds ([3 x i8*]* @_ZTV1B, i64 0, i64 2)
// CHECK: call void @_ZN5FieldC1Ev
// CHECK: ret void
// CHECK: call void @_ZN1BC2Ev(
// CHECK: define linkonce_odr void @_ZN1BD1Ev(
// CHECK: store i8** getelementptr inbounds ([3 x i8*]* @_ZTV1B, i64 0, i64 2)
// CHECK: call void @_ZN5FieldD1Ev(
// CHECK: call void @_ZN4BaseD2Ev(
// CHECK: ret void
// CHECK: define linkonce_odr void @_ZN1BC2Ev(
// CHECK: call void @_ZN4BaseC2Ev(
// CHECK: store i8** getelementptr inbounds ([3 x i8*]* @_ZTV1B, i64 0, i64 2)
// CHECK: call void @_ZN5FieldC1Ev
// CHECK: ret void