[AST] Only store the needed data in IfStmt

Only store the needed data in IfStmt. This cuts the size of IfStmt
by up to 3 pointers + 1 SourceLocation. The order of the children
is intentionally kept the same even though it would be more
convenient to put the optional trailing objects last. Additionally
use the newly available space in the bit-fields of Stmt to store
the location of the "if".

The result of this is that for the common case of an
if statement of the form:

if (some_cond)
  some_statement

the size of IfStmt is brought down to 8 bytes + 2 pointers,
instead of 8 bytes + 5 pointers + 2 SourceLocation.

Differential Revision: https://reviews.llvm.org/D53607

Reviewed By: rjmccall

llvm-svn: 345464
This commit is contained in:
Bruno Ricci 2018-10-27 21:12:20 +00:00
parent a5baf86744
commit b1cc94b2e5
10 changed files with 314 additions and 104 deletions

View File

@ -151,11 +151,25 @@ protected:
};
class IfStmtBitfields {
friend class ASTStmtReader;
friend class IfStmt;
unsigned : NumStmtBits;
/// True if this if statement is a constexpr if.
unsigned IsConstexpr : 1;
/// True if this if statement has storage for an else statement.
unsigned HasElse : 1;
/// True if this if statement has storage for a variable declaration.
unsigned HasVar : 1;
/// True if this if statement has storage for an init statement.
unsigned HasInit : 1;
/// The location of the "if".
SourceLocation IfLoc;
};
class SwitchStmtBitfields {
@ -1100,21 +1114,117 @@ public:
};
/// IfStmt - This represents an if/then/else.
class IfStmt : public Stmt {
enum { INIT, VAR, COND, THEN, ELSE, END_EXPR };
Stmt* SubExprs[END_EXPR];
class IfStmt final
: public Stmt,
private llvm::TrailingObjects<IfStmt, Stmt *, SourceLocation> {
friend TrailingObjects;
SourceLocation IfLoc;
SourceLocation ElseLoc;
// IfStmt is followed by several trailing objects, some of which optional.
// Note that it would be more convenient to put the optional trailing
// objects at then end but this would change the order of the children.
// The trailing objects are in order:
//
// * A "Stmt *" for the init statement.
// Present if and only if hasInitStorage().
//
// * A "Stmt *" for the condition variable.
// Present if and only if hasVarStorage(). This is in fact a "DeclStmt *".
//
// * A "Stmt *" for the condition.
// Always present. This is in fact a "Expr *".
//
// * A "Stmt *" for the then statement.
// Always present.
//
// * A "Stmt *" for the else statement.
// Present if and only if hasElseStorage().
//
// * A "SourceLocation" for the location of the "else".
// Present if and only if hasElseStorage().
enum { InitOffset = 0, ThenOffsetFromCond = 1, ElseOffsetFromCond = 2 };
enum { NumMandatoryStmtPtr = 2 };
unsigned numTrailingObjects(OverloadToken<Stmt *>) const {
return NumMandatoryStmtPtr + hasElseStorage() + hasVarStorage() +
hasInitStorage();
}
unsigned numTrailingObjects(OverloadToken<SourceLocation>) const {
return hasElseStorage();
}
unsigned initOffset() const { return InitOffset; }
unsigned varOffset() const { return InitOffset + hasInitStorage(); }
unsigned condOffset() const {
return InitOffset + hasInitStorage() + hasVarStorage();
}
unsigned thenOffset() const { return condOffset() + ThenOffsetFromCond; }
unsigned elseOffset() const { return condOffset() + ElseOffsetFromCond; }
/// Build an if/then/else statement.
IfStmt(const ASTContext &Ctx, SourceLocation IL, bool IsConstexpr, Stmt *Init,
VarDecl *Var, Expr *Cond, Stmt *Then, SourceLocation EL, Stmt *Else);
/// Build an empty if/then/else statement.
explicit IfStmt(EmptyShell Empty, bool HasElse, bool HasVar, bool HasInit);
public:
IfStmt(const ASTContext &C, SourceLocation IL,
bool IsConstexpr, Stmt *init, VarDecl *var, Expr *cond,
Stmt *then, SourceLocation EL = SourceLocation(),
Stmt *elsev = nullptr);
/// Create an IfStmt.
static IfStmt *Create(const ASTContext &Ctx, SourceLocation IL,
bool IsConstexpr, Stmt *Init, VarDecl *Var, Expr *Cond,
Stmt *Then, SourceLocation EL = SourceLocation(),
Stmt *Else = nullptr);
/// Build an empty if/then/else statement
explicit IfStmt(EmptyShell Empty) : Stmt(IfStmtClass, Empty) {}
/// Create an empty IfStmt optionally with storage for an else statement,
/// condition variable and init expression.
static IfStmt *CreateEmpty(const ASTContext &Ctx, bool HasElse, bool HasVar,
bool HasInit);
/// True if this IfStmt has the storage for an init statement.
bool hasInitStorage() const { return IfStmtBits.HasInit; }
/// True if this IfStmt has storage for a variable declaration.
bool hasVarStorage() const { return IfStmtBits.HasVar; }
/// True if this IfStmt has storage for an else statement.
bool hasElseStorage() const { return IfStmtBits.HasElse; }
Expr *getCond() {
return reinterpret_cast<Expr *>(getTrailingObjects<Stmt *>()[condOffset()]);
}
const Expr *getCond() const {
return reinterpret_cast<Expr *>(getTrailingObjects<Stmt *>()[condOffset()]);
}
void setCond(Expr *Cond) {
getTrailingObjects<Stmt *>()[condOffset()] = reinterpret_cast<Stmt *>(Cond);
}
Stmt *getThen() { return getTrailingObjects<Stmt *>()[thenOffset()]; }
const Stmt *getThen() const {
return getTrailingObjects<Stmt *>()[thenOffset()];
}
void setThen(Stmt *Then) {
getTrailingObjects<Stmt *>()[thenOffset()] = Then;
}
Stmt *getElse() {
return hasElseStorage() ? getTrailingObjects<Stmt *>()[elseOffset()]
: nullptr;
}
const Stmt *getElse() const {
return hasElseStorage() ? getTrailingObjects<Stmt *>()[elseOffset()]
: nullptr;
}
void setElse(Stmt *Else) {
assert(hasElseStorage() &&
"This if statement has no storage for an else statement!");
getTrailingObjects<Stmt *>()[elseOffset()] = Else;
}
/// Retrieve the variable declared in this "if" statement, if any.
///
@ -1124,52 +1234,77 @@ public:
/// printf("x is %d", x);
/// }
/// \endcode
VarDecl *getConditionVariable() const;
void setConditionVariable(const ASTContext &C, VarDecl *V);
VarDecl *getConditionVariable();
const VarDecl *getConditionVariable() const {
return const_cast<IfStmt *>(this)->getConditionVariable();
}
/// Set the condition variable for this if statement.
/// The if statement must have storage for the condition variable.
void setConditionVariable(const ASTContext &Ctx, VarDecl *V);
/// If this IfStmt has a condition variable, return the faux DeclStmt
/// associated with the creation of that condition variable.
const DeclStmt *getConditionVariableDeclStmt() const {
return reinterpret_cast<DeclStmt*>(SubExprs[VAR]);
DeclStmt *getConditionVariableDeclStmt() {
return hasVarStorage() ? static_cast<DeclStmt *>(
getTrailingObjects<Stmt *>()[varOffset()])
: nullptr;
}
Stmt *getInit() { return SubExprs[INIT]; }
const Stmt *getInit() const { return SubExprs[INIT]; }
void setInit(Stmt *S) { SubExprs[INIT] = S; }
const Expr *getCond() const { return reinterpret_cast<Expr*>(SubExprs[COND]);}
void setCond(Expr *E) { SubExprs[COND] = reinterpret_cast<Stmt *>(E); }
const Stmt *getThen() const { return SubExprs[THEN]; }
void setThen(Stmt *S) { SubExprs[THEN] = S; }
const Stmt *getElse() const { return SubExprs[ELSE]; }
void setElse(Stmt *S) { SubExprs[ELSE] = S; }
const DeclStmt *getConditionVariableDeclStmt() const {
return hasVarStorage() ? static_cast<DeclStmt *>(
getTrailingObjects<Stmt *>()[varOffset()])
: nullptr;
}
Expr *getCond() { return reinterpret_cast<Expr*>(SubExprs[COND]); }
Stmt *getThen() { return SubExprs[THEN]; }
Stmt *getElse() { return SubExprs[ELSE]; }
Stmt *getInit() {
return hasInitStorage() ? getTrailingObjects<Stmt *>()[initOffset()]
: nullptr;
}
SourceLocation getIfLoc() const { return IfLoc; }
void setIfLoc(SourceLocation L) { IfLoc = L; }
SourceLocation getElseLoc() const { return ElseLoc; }
void setElseLoc(SourceLocation L) { ElseLoc = L; }
const Stmt *getInit() const {
return hasInitStorage() ? getTrailingObjects<Stmt *>()[initOffset()]
: nullptr;
}
void setInit(Stmt *Init) {
assert(hasInitStorage() &&
"This if statement has no storage for an init statement!");
getTrailingObjects<Stmt *>()[initOffset()] = Init;
}
SourceLocation getIfLoc() const { return IfStmtBits.IfLoc; }
void setIfLoc(SourceLocation IfLoc) { IfStmtBits.IfLoc = IfLoc; }
SourceLocation getElseLoc() const {
return hasElseStorage() ? *getTrailingObjects<SourceLocation>()
: SourceLocation();
}
void setElseLoc(SourceLocation ElseLoc) {
assert(hasElseStorage() &&
"This if statement has no storage for an else statement!");
*getTrailingObjects<SourceLocation>() = ElseLoc;
}
bool isConstexpr() const { return IfStmtBits.IsConstexpr; }
void setConstexpr(bool C) { IfStmtBits.IsConstexpr = C; }
bool isObjCAvailabilityCheck() const;
SourceLocation getBeginLoc() const LLVM_READONLY { return IfLoc; }
SourceLocation getBeginLoc() const { return getIfLoc(); }
SourceLocation getEndLoc() const LLVM_READONLY {
if (SubExprs[ELSE])
return SubExprs[ELSE]->getEndLoc();
else
return SubExprs[THEN]->getEndLoc();
if (getElse())
return getElse()->getEndLoc();
return getThen()->getEndLoc();
}
// Iterators over subexpressions. The iterators will include iterating
// over the initialization expression referenced by the condition variable.
child_range children() {
return child_range(&SubExprs[0], &SubExprs[0]+END_EXPR);
return child_range(getTrailingObjects<Stmt *>(),
getTrailingObjects<Stmt *>() +
numTrailingObjects(OverloadToken<Stmt *>()));
}
static bool classof(const Stmt *T) {

View File

@ -511,6 +511,7 @@ namespace {
void VisitStmt(const Stmt *Node);
void VisitDeclStmt(const DeclStmt *Node);
void VisitAttributedStmt(const AttributedStmt *Node);
void VisitIfStmt(const IfStmt *Node);
void VisitLabelStmt(const LabelStmt *Node);
void VisitGotoStmt(const GotoStmt *Node);
void VisitCXXCatchStmt(const CXXCatchStmt *Node);
@ -2020,6 +2021,16 @@ void ASTDumper::VisitAttributedStmt(const AttributedStmt *Node) {
dumpAttr(*I);
}
void ASTDumper::VisitIfStmt(const IfStmt *Node) {
VisitStmt(Node);
if (Node->hasInitStorage())
OS << " has_init";
if (Node->hasVarStorage())
OS << " has_var";
if (Node->hasElseStorage())
OS << " has_else";
}
void ASTDumper::VisitLabelStmt(const LabelStmt *Node) {
VisitStmt(Node);
OS << " '" << Node->getName() << "'";

View File

@ -5772,10 +5772,9 @@ ExpectedStmt ASTNodeImporter::VisitIfStmt(IfStmt *S) {
ToIfLoc, ToInit, ToConditionVariable, ToCond, ToThen, ToElseLoc, ToElse) =
*Imp;
return new (Importer.getToContext()) IfStmt(
Importer.getToContext(),
ToIfLoc, S->isConstexpr(), ToInit, ToConditionVariable, ToCond,
ToThen, ToElseLoc, ToElse);
return IfStmt::Create(Importer.getToContext(), ToIfLoc, S->isConstexpr(),
ToInit, ToConditionVariable, ToCond, ToThen, ToElseLoc,
ToElse);
}
ExpectedStmt ASTNodeImporter::VisitSwitchStmt(SwitchStmt *S) {

View File

@ -799,39 +799,86 @@ void MSAsmStmt::initialize(const ASTContext &C, StringRef asmstr,
});
}
IfStmt::IfStmt(const ASTContext &C, SourceLocation IL, bool IsConstexpr,
Stmt *init, VarDecl *var, Expr *cond, Stmt *then,
SourceLocation EL, Stmt *elsev)
: Stmt(IfStmtClass), IfLoc(IL), ElseLoc(EL) {
IfStmt::IfStmt(const ASTContext &Ctx, SourceLocation IL, bool IsConstexpr,
Stmt *Init, VarDecl *Var, Expr *Cond, Stmt *Then,
SourceLocation EL, Stmt *Else)
: Stmt(IfStmtClass) {
bool HasElse = Else != nullptr;
bool HasVar = Var != nullptr;
bool HasInit = Init != nullptr;
IfStmtBits.HasElse = HasElse;
IfStmtBits.HasVar = HasVar;
IfStmtBits.HasInit = HasInit;
setConstexpr(IsConstexpr);
setConditionVariable(C, var);
SubExprs[INIT] = init;
SubExprs[COND] = cond;
SubExprs[THEN] = then;
SubExprs[ELSE] = elsev;
setCond(Cond);
setThen(Then);
if (HasElse)
setElse(Else);
if (HasVar)
setConditionVariable(Ctx, Var);
if (HasInit)
setInit(Init);
setIfLoc(IL);
if (HasElse)
setElseLoc(EL);
}
VarDecl *IfStmt::getConditionVariable() const {
if (!SubExprs[VAR])
return nullptr;
IfStmt::IfStmt(EmptyShell Empty, bool HasElse, bool HasVar, bool HasInit)
: Stmt(IfStmtClass, Empty) {
IfStmtBits.HasElse = HasElse;
IfStmtBits.HasVar = HasVar;
IfStmtBits.HasInit = HasInit;
}
auto *DS = cast<DeclStmt>(SubExprs[VAR]);
IfStmt *IfStmt::Create(const ASTContext &Ctx, SourceLocation IL,
bool IsConstexpr, Stmt *Init, VarDecl *Var, Expr *Cond,
Stmt *Then, SourceLocation EL, Stmt *Else) {
bool HasElse = Else != nullptr;
bool HasVar = Var != nullptr;
bool HasInit = Init != nullptr;
void *Mem = Ctx.Allocate(
totalSizeToAlloc<Stmt *, SourceLocation>(
NumMandatoryStmtPtr + HasElse + HasVar + HasInit, HasElse),
alignof(IfStmt));
return new (Mem)
IfStmt(Ctx, IL, IsConstexpr, Init, Var, Cond, Then, EL, Else);
}
IfStmt *IfStmt::CreateEmpty(const ASTContext &Ctx, bool HasElse, bool HasVar,
bool HasInit) {
void *Mem = Ctx.Allocate(
totalSizeToAlloc<Stmt *, SourceLocation>(
NumMandatoryStmtPtr + HasElse + HasVar + HasInit, HasElse),
alignof(IfStmt));
return new (Mem) IfStmt(EmptyShell(), HasElse, HasVar, HasInit);
}
VarDecl *IfStmt::getConditionVariable() {
auto *DS = getConditionVariableDeclStmt();
if (!DS)
return nullptr;
return cast<VarDecl>(DS->getSingleDecl());
}
void IfStmt::setConditionVariable(const ASTContext &C, VarDecl *V) {
void IfStmt::setConditionVariable(const ASTContext &Ctx, VarDecl *V) {
assert(hasVarStorage() &&
"This if statement has no storage for a condition variable!");
if (!V) {
SubExprs[VAR] = nullptr;
getTrailingObjects<Stmt *>()[varOffset()] = nullptr;
return;
}
SourceRange VarRange = V->getSourceRange();
SubExprs[VAR] = new (C) DeclStmt(DeclGroupRef(V), VarRange.getBegin(),
VarRange.getEnd());
getTrailingObjects<Stmt *>()[varOffset()] = new (Ctx)
DeclStmt(DeclGroupRef(V), VarRange.getBegin(), VarRange.getEnd());
}
bool IfStmt::isObjCAvailabilityCheck() const {
return isa<ObjCAvailabilityCheckExpr>(SubExprs[COND]);
return isa<ObjCAvailabilityCheckExpr>(getCond());
}
ForStmt::ForStmt(const ASTContext &C, Stmt *Init, Expr *Cond, VarDecl *condVar,

View File

@ -464,13 +464,13 @@ static Stmt *create_call_once(ASTContext &C, const FunctionDecl *D) {
Deref, M.makeIntegralCast(M.makeIntegerLiteral(1, C.IntTy), DerefType),
DerefType);
IfStmt *Out = new (C)
IfStmt(C, SourceLocation(),
/* IsConstexpr=*/ false,
/* init=*/ nullptr,
/* var=*/ nullptr,
/* cond=*/ FlagCheck,
/* then=*/ M.makeCompound({CallbackCall, FlagAssignment}));
auto *Out =
IfStmt::Create(C, SourceLocation(),
/* IsConstexpr=*/false,
/* init=*/nullptr,
/* var=*/nullptr,
/* cond=*/FlagCheck,
/* then=*/M.makeCompound({CallbackCall, FlagAssignment}));
return Out;
}
@ -549,12 +549,12 @@ static Stmt *create_dispatch_once(ASTContext &C, const FunctionDecl *D) {
Expr *GuardCondition = M.makeComparison(LValToRval, DoneValue, BO_NE);
// (5) Create the 'if' statement.
IfStmt *If = new (C) IfStmt(C, SourceLocation(),
/* IsConstexpr=*/ false,
/* init=*/ nullptr,
/* var=*/ nullptr,
/* cond=*/ GuardCondition,
/* then=*/ CS);
auto *If = IfStmt::Create(C, SourceLocation(),
/* IsConstexpr=*/false,
/* init=*/nullptr,
/* var=*/nullptr,
/* cond=*/GuardCondition,
/* then=*/CS);
return If;
}
@ -657,8 +657,11 @@ static Stmt *create_OSAtomicCompareAndSwap(ASTContext &C, const FunctionDecl *D)
Stmt *Else = M.makeReturn(RetVal);
/// Construct the If.
Stmt *If = new (C) IfStmt(C, SourceLocation(), false, nullptr, nullptr,
Comparison, Body, SourceLocation(), Else);
auto *If = IfStmt::Create(C, SourceLocation(),
/* IsConstexpr=*/false,
/* init=*/nullptr,
/* var=*/nullptr, Comparison, Body,
SourceLocation(), Else);
return If;
}

View File

@ -577,9 +577,8 @@ StmtResult Sema::BuildIfStmt(SourceLocation IfLoc, bool IsConstexpr,
DiagnoseUnusedExprResult(thenStmt);
DiagnoseUnusedExprResult(elseStmt);
return new (Context)
IfStmt(Context, IfLoc, IsConstexpr, InitStmt, Cond.get().first,
Cond.get().second, thenStmt, ElseLoc, elseStmt);
return IfStmt::Create(Context, IfLoc, IsConstexpr, InitStmt, Cond.get().first,
Cond.get().second, thenStmt, ElseLoc, elseStmt);
}
namespace {

View File

@ -215,14 +215,24 @@ void ASTStmtReader::VisitAttributedStmt(AttributedStmt *S) {
void ASTStmtReader::VisitIfStmt(IfStmt *S) {
VisitStmt(S);
S->setConstexpr(Record.readInt());
S->setInit(Record.readSubStmt());
S->setConditionVariable(Record.getContext(), ReadDeclAs<VarDecl>());
bool HasElse = Record.readInt();
bool HasVar = Record.readInt();
bool HasInit = Record.readInt();
S->setCond(Record.readSubExpr());
S->setThen(Record.readSubStmt());
S->setElse(Record.readSubStmt());
if (HasElse)
S->setElse(Record.readSubStmt());
if (HasVar)
S->setConditionVariable(Record.getContext(), ReadDeclAs<VarDecl>());
if (HasInit)
S->setInit(Record.readSubStmt());
S->setIfLoc(ReadSourceLocation());
S->setElseLoc(ReadSourceLocation());
if (HasElse)
S->setElseLoc(ReadSourceLocation());
}
void ASTStmtReader::VisitSwitchStmt(SwitchStmt *S) {
@ -2285,7 +2295,11 @@ Stmt *ASTReader::ReadStmtFromStream(ModuleFile &F) {
break;
case STMT_IF:
S = new (Context) IfStmt(Empty);
S = IfStmt::CreateEmpty(
Context,
/* HasElse=*/Record[ASTStmtReader::NumStmtFields + 1],
/* HasVar=*/Record[ASTStmtReader::NumStmtFields + 2],
/* HasInit=*/Record[ASTStmtReader::NumStmtFields + 3]);
break;
case STMT_SWITCH:

View File

@ -128,14 +128,29 @@ void ASTStmtWriter::VisitAttributedStmt(AttributedStmt *S) {
void ASTStmtWriter::VisitIfStmt(IfStmt *S) {
VisitStmt(S);
bool HasElse = S->getElse() != nullptr;
bool HasVar = S->getConditionVariableDeclStmt() != nullptr;
bool HasInit = S->getInit() != nullptr;
Record.push_back(S->isConstexpr());
Record.AddStmt(S->getInit());
Record.AddDeclRef(S->getConditionVariable());
Record.push_back(HasElse);
Record.push_back(HasVar);
Record.push_back(HasInit);
Record.AddStmt(S->getCond());
Record.AddStmt(S->getThen());
Record.AddStmt(S->getElse());
if (HasElse)
Record.AddStmt(S->getElse());
if (HasVar)
Record.AddDeclRef(S->getConditionVariable());
if (HasInit)
Record.AddStmt(S->getInit());
Record.AddSourceLocation(S->getIfLoc());
Record.AddSourceLocation(S->getElseLoc());
if (HasElse)
Record.AddSourceLocation(S->getElseLoc());
Code = serialization::STMT_IF;
}

View File

@ -1,14 +1,10 @@
// RUN: clang-import-test -dump-ast -import %S/Inputs/F.cpp -expression %s | FileCheck %s
// CHECK: IfStmt
// CHECK-NEXT: <<NULL>>
// CHECK-NEXT: <<NULL>>
// CHECK-NEXT: CXXBoolLiteralExpr
// CHECK-NEXT: ReturnStmt
// CHECK-NEXT: <<NULL>>
// CHECK: IfStmt
// CHECK-NEXT: <<NULL>>
// CHECK-NEXT: DeclStmt
// CHECK-NEXT: VarDecl
// CHECK-NEXT: IntegerLiteral
@ -16,26 +12,19 @@
// CHECK-NEXT: ImplicitCastExpr
// CHECK-NEXT: DeclRefExpr
// CHECK-NEXT: ReturnStmt
// CHECK-NEXT: <<NULL>>
// CHECK: IfStmt
// CHECK-NEXT: DeclStmt
// CHECK-NEXT: VarDecl
// CHECK-NEXT: <<NULL>>
// CHECK-NEXT: CXXBoolLiteralExpr
// CHECK-NEXT: ReturnStmt
// CHECK-NEXT: <<NULL>>
// CHECK: IfStmt
// CHECK-NEXT: <<NULL>>
// CHECK-NEXT: <<NULL>>
// CHECK-NEXT: CXXBoolLiteralExpr
// CHECK-NEXT: ReturnStmt
// CHECK-NEXT: ReturnStmt
// CHECK: IfStmt
// CHECK-NEXT: <<NULL>>
// CHECK-NEXT: <<NULL>>
// CHECK-NEXT: CXXBoolLiteralExpr
// CHECK-NEXT: CompoundStmt
// CHECK-NEXT: ReturnStmt

View File

@ -33,8 +33,6 @@ int g(int i) {
// CHECK-NEXT: |-ParmVarDecl
// CHECK-NEXT: `-CompoundStmt
// CHECK-NEXT: `-IfStmt {{.*}} <line:25:3, line:28:12>
// CHECK-NEXT: |-<<<NULL>>>
// CHECK-NEXT: |-<<<NULL>>>
// CHECK-NEXT: |-OpaqueValueExpr {{.*}} <<invalid sloc>> 'bool'
// CHECK-NEXT: |-ReturnStmt {{.*}} <line:26:5, col:12>
// CHECK-NEXT: | `-IntegerLiteral {{.*}} <col:12> 'int' 4
@ -50,15 +48,15 @@ double Str::foo1(double, invalid_type)
{ return 45; }
}
// CHECK: NamespaceDecl {{.*}} <{{.*}}> {{.*}} TestInvalidFunctionDecl
// CHECK-NEXT: |-CXXRecordDecl {{.*}} <line:46:1, line:48:1> line:46:8 struct Str definition
// CHECK-NEXT: |-CXXRecordDecl {{.*}} <line:44:1, line:46:1> line:44:8 struct Str definition
// CHECK: | |-CXXRecordDecl {{.*}} <col:1, col:8> col:8 implicit struct Str
// CHECK-NEXT: | `-CXXMethodDecl {{.*}} <line:47:4, col:36> col:11 invalid foo1 'double (double, int)'
// CHECK-NEXT: | `-CXXMethodDecl {{.*}} <line:45:4, col:36> col:11 invalid foo1 'double (double, int)'
// CHECK-NEXT: | |-ParmVarDecl {{.*}} <col:16> col:22 'double'
// CHECK-NEXT: | `-ParmVarDecl {{.*}} <col:24, <invalid sloc>> col:36 invalid 'int'
// CHECK-NEXT: `-CXXMethodDecl {{.*}} parent {{.*}} <line:49:1, line:50:14> line:49:13 invalid foo1 'double (double, int)'
// CHECK-NEXT: `-CXXMethodDecl {{.*}} parent {{.*}} <line:47:1, line:48:14> line:47:13 invalid foo1 'double (double, int)'
// CHECK-NEXT: |-ParmVarDecl {{.*}} <col:18> col:24 'double'
// CHECK-NEXT: |-ParmVarDecl {{.*}} <col:26, <invalid sloc>> col:38 invalid 'int'
// CHECK-NEXT: `-CompoundStmt {{.*}} <line:50:1, col:14>
// CHECK-NEXT: `-CompoundStmt {{.*}} <line:48:1, col:14>
// CHECK-NEXT: `-ReturnStmt {{.*}} <col:3, col:10>
// CHECK-NEXT: `-ImplicitCastExpr {{.*}} <col:10> 'double' <IntegralToFloating>
// CHECK-NEXT: `-IntegerLiteral {{.*}} <col:10> 'int' 45