diff --git a/clang/include/clang/AST/ASTImporter.h b/clang/include/clang/AST/ASTImporter.h --- a/clang/include/clang/AST/ASTImporter.h +++ b/clang/include/clang/AST/ASTImporter.h @@ -16,6 +16,7 @@ #include "clang/AST/DeclBase.h" #include "clang/AST/DeclarationName.h" +#include "clang/AST/ExprCXX.h" #include "clang/AST/NestedNameSpecifier.h" #include "clang/AST/TemplateName.h" #include "clang/AST/Type.h" @@ -349,6 +350,10 @@ return ToOrErr.takeError(); } + /// Import cleanup objects owned by ExprWithCleanup. + llvm::Expected + Import(ExprWithCleanups::CleanupObject From); + /// Import the given type from the "from" context into the "to" /// context. A null type is imported as a null type (no error). /// diff --git a/clang/include/clang/AST/ExprCXX.h b/clang/include/clang/AST/ExprCXX.h --- a/clang/include/clang/AST/ExprCXX.h +++ b/clang/include/clang/AST/ExprCXX.h @@ -3321,13 +3321,15 @@ /// literal is the extent of the enclosing scope. class ExprWithCleanups final : public FullExpr, - private llvm::TrailingObjects { + private llvm::TrailingObjects< + ExprWithCleanups, + llvm::PointerUnion> { public: /// The type of objects that are kept in the cleanup. - /// It's useful to remember the set of blocks; we could also - /// remember the set of temporaries, but there's currently - /// no need. - using CleanupObject = BlockDecl *; + /// It's useful to remember the set of blocks and block-scoped compound + /// literals; we could also remember the set of temporaries, but there's + /// currently no need. + using CleanupObject = llvm::PointerUnion; private: friend class ASTStmtReader; diff --git a/clang/include/clang/AST/TextNodeDumper.h b/clang/include/clang/AST/TextNodeDumper.h --- a/clang/include/clang/AST/TextNodeDumper.h +++ b/clang/include/clang/AST/TextNodeDumper.h @@ -184,6 +184,7 @@ void dumpBareDeclRef(const Decl *D); void dumpName(const NamedDecl *ND); void dumpAccessSpecifier(AccessSpecifier AS); + void dumpCleanupObject(const ExprWithCleanups::CleanupObject &C); void dumpDeclRef(const Decl *D, StringRef Label = {}); diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -5543,6 +5543,8 @@ def note_enters_block_captures_non_trivial_c_struct : Note< "jump enters lifetime of block which captures a C struct that is non-trivial " "to destroy">; +def note_enters_compound_literal_scope : Note< + "jump enters lifetime of a compound literal that is non-trivial to destruct">; def note_exits_cleanup : Note< "jump exits scope of variable with __attribute__((cleanup))">; @@ -5586,6 +5588,8 @@ def note_exits_block_captures_non_trivial_c_struct : Note< "jump exits lifetime of block which captures a C struct that is non-trivial " "to destroy">; +def note_exits_compound_literal_scope : Note< + "jump exits lifetime of a compound literal that is non-trivial to destruct">; def err_func_returning_qualified_void : ExtWarn< "function cannot return qualified void type %0">, diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -613,9 +613,8 @@ CleanupInfo Cleanup; /// ExprCleanupObjects - This is the stack of objects requiring - /// cleanup that are created by the current full expression. The - /// element type here is ExprWithCleanups::Object. - SmallVector ExprCleanupObjects; + /// cleanup that are created by the current full expression. + SmallVector ExprCleanupObjects; /// Store a set of either DeclRefExprs or MemberExprs that contain a reference /// to a variable (constant) that may or may not be odr-used in this Expr, and diff --git a/clang/include/clang/Serialization/ASTBitCodes.h b/clang/include/clang/Serialization/ASTBitCodes.h --- a/clang/include/clang/Serialization/ASTBitCodes.h +++ b/clang/include/clang/Serialization/ASTBitCodes.h @@ -1900,6 +1900,9 @@ CTOR_INITIALIZER_INDIRECT_MEMBER }; + /// Kinds of cleanup objects owned by ExprWithCleanups. + enum CleanupObjectKind { COK_Block, COK_CompoundLiteral }; + /// Describes the redeclarations of a declaration. struct LocalRedeclarationsInfo { // The ID of the first declaration diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp --- a/clang/lib/AST/ASTImporter.cpp +++ b/clang/lib/AST/ASTImporter.cpp @@ -7910,6 +7910,18 @@ MapImported(FromD, ToD); } +llvm::Expected +ASTImporter::Import(ExprWithCleanups::CleanupObject From) { + if (auto *CLE = From.dyn_cast()) { + if (Expected R = Import(CLE)) + return ExprWithCleanups::CleanupObject(cast(*R)); + } + + // FIXME: Handle BlockDecl when we implement importing BlockExpr in + // ASTNodeImporter. + return make_error(ImportError::UnsupportedConstruct); +} + Expected ASTImporter::Import(QualType FromT) { if (FromT.isNull()) return QualType{}; diff --git a/clang/lib/AST/JSONNodeDumper.cpp b/clang/lib/AST/JSONNodeDumper.cpp --- a/clang/lib/AST/JSONNodeDumper.cpp +++ b/clang/lib/AST/JSONNodeDumper.cpp @@ -1334,7 +1334,16 @@ if (EWC->getNumObjects()) { JOS.attributeArray("cleanups", [this, EWC] { for (const ExprWithCleanups::CleanupObject &CO : EWC->getObjects()) - JOS.value(createBareDeclRef(CO)); + if (auto *BD = CO.dyn_cast()) { + JOS.value(createBareDeclRef(BD)); + } else if (auto *CLE = CO.dyn_cast()) { + llvm::json::Object Obj; + Obj["id"] = createPointerRepresentation(CLE); + Obj["kind"] = CLE->getStmtClassName(); + JOS.value(std::move(Obj)); + } else { + llvm_unreachable("unexpected cleanup object type"); + } }); } } diff --git a/clang/lib/AST/TextNodeDumper.cpp b/clang/lib/AST/TextNodeDumper.cpp --- a/clang/lib/AST/TextNodeDumper.cpp +++ b/clang/lib/AST/TextNodeDumper.cpp @@ -448,6 +448,23 @@ } } +void TextNodeDumper::dumpCleanupObject( + const ExprWithCleanups::CleanupObject &C) { + if (auto *BD = C.dyn_cast()) + dumpDeclRef(BD, "cleanup"); + else if (auto *CLE = C.dyn_cast()) + AddChild([=] { + OS << "cleanup "; + { + ColorScope Color(OS, ShowColors, StmtColor); + OS << CLE->getStmtClassName(); + } + dumpPointer(CLE); + }); + else + llvm_unreachable("unexpected cleanup type"); +} + void TextNodeDumper::dumpDeclRef(const Decl *D, StringRef Label) { if (!D) return; @@ -950,7 +967,7 @@ void TextNodeDumper::VisitExprWithCleanups(const ExprWithCleanups *Node) { for (unsigned i = 0, e = Node->getNumObjects(); i != e; ++i) - dumpDeclRef(Node->getObject(i), "cleanup"); + dumpCleanupObject(Node->getObject(i)); } void TextNodeDumper::VisitSizeOfPackExpr(const SizeOfPackExpr *Node) { diff --git a/clang/lib/CodeGen/CGBlocks.cpp b/clang/lib/CodeGen/CGBlocks.cpp --- a/clang/lib/CodeGen/CGBlocks.cpp +++ b/clang/lib/CodeGen/CGBlocks.cpp @@ -860,13 +860,13 @@ } /// Enter a full-expression with a non-trivial number of objects to -/// clean up. This is in this file because, at the moment, the only -/// kind of cleanup object is a BlockDecl*. +/// clean up. void CodeGenFunction::enterNonTrivialFullExpression(const FullExpr *E) { if (const auto EWC = dyn_cast(E)) { assert(EWC->getNumObjects() != 0); for (const ExprWithCleanups::CleanupObject &C : EWC->getObjects()) - enterBlockScope(*this, C); + if (auto *BD = C.dyn_cast()) + enterBlockScope(*this, BD); } } diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp --- a/clang/lib/CodeGen/CGExpr.cpp +++ b/clang/lib/CodeGen/CGExpr.cpp @@ -4268,6 +4268,14 @@ EmitAnyExprToMem(InitExpr, DeclPtr, E->getType().getQualifiers(), /*Init*/ true); + // Block-scope compound literals are destroyed at the end of the enclosing + // scope in C. + if (!getLangOpts().CPlusPlus) + if (QualType::DestructionKind DtorKind = E->getType().isDestructedType()) + pushLifetimeExtendedDestroy(getCleanupKind(DtorKind), DeclPtr, + E->getType(), getDestroyer(DtorKind), + DtorKind & EHCleanup); + return Result; } diff --git a/clang/lib/CodeGen/CGExprAgg.cpp b/clang/lib/CodeGen/CGExprAgg.cpp --- a/clang/lib/CodeGen/CGExprAgg.cpp +++ b/clang/lib/CodeGen/CGExprAgg.cpp @@ -659,7 +659,21 @@ } AggValueSlot Slot = EnsureSlot(E->getType()); + + // Block-scope compound literals are destroyed at the end of the enclosing + // scope in C. + bool Destruct = + !CGF.getLangOpts().CPlusPlus && !Slot.isExternallyDestructed(); + if (Destruct) + Slot.setExternallyDestructed(); + CGF.EmitAggExpr(E->getInitializer(), Slot); + + if (Destruct) + if (QualType::DestructionKind DtorKind = E->getType().isDestructedType()) + CGF.pushLifetimeExtendedDestroy( + CGF.getCleanupKind(DtorKind), Slot.getAddress(), E->getType(), + CGF.getDestroyer(DtorKind), DtorKind & EHCleanup); } /// Attempt to look through various unimportant expressions to find a diff --git a/clang/lib/CodeGen/CGExprScalar.cpp b/clang/lib/CodeGen/CGExprScalar.cpp --- a/clang/lib/CodeGen/CGExprScalar.cpp +++ b/clang/lib/CodeGen/CGExprScalar.cpp @@ -556,6 +556,11 @@ Value *VisitMemberExpr(MemberExpr *E); Value *VisitExtVectorElementExpr(Expr *E) { return EmitLoadOfLValue(E); } Value *VisitCompoundLiteralExpr(CompoundLiteralExpr *E) { + // Strictly speaking, we shouldn't be calling EmitLoadOfLValue, which + // transitively calls EmitCompoundLiteralLValue, here in C++ since compound + // literals aren't l-values in C++. We do so simply because that's the + // cleanest way to handle compound literals in C++. + // See the discussion here: https://reviews.llvm.org/D64464 return EmitLoadOfLValue(E); } diff --git a/clang/lib/Sema/JumpDiagnostics.cpp b/clang/lib/Sema/JumpDiagnostics.cpp --- a/clang/lib/Sema/JumpDiagnostics.cpp +++ b/clang/lib/Sema/JumpDiagnostics.cpp @@ -75,6 +75,7 @@ void BuildScopeInformation(Decl *D, unsigned &ParentScope); void BuildScopeInformation(VarDecl *D, const BlockDecl *BDecl, unsigned &ParentScope); + void BuildScopeInformation(CompoundLiteralExpr *CLE, unsigned &ParentScope); void BuildScopeInformation(Stmt *S, unsigned &origParentScope); void VerifyJumps(); @@ -276,6 +277,16 @@ } } +/// Build scope information for compound literals of C struct types that are +/// non-trivial to destruct. +void JumpScopeChecker::BuildScopeInformation(CompoundLiteralExpr *CLE, + unsigned &ParentScope) { + unsigned InDiag = diag::note_enters_compound_literal_scope; + unsigned OutDiag = diag::note_exits_compound_literal_scope; + Scopes.push_back(GotoScope(ParentScope, InDiag, OutDiag, CLE->getExprLoc())); + ParentScope = Scopes.size() - 1; +} + /// BuildScopeInformation - The statements from CI to CE are known to form a /// coherent VLA scope with a specified parent node. Walk through the /// statements, adding any labels or gotos to LabelAndGotoScopes and recursively @@ -529,11 +540,15 @@ // implementable but a lot of work which we haven't felt up to doing. ExprWithCleanups *EWC = cast(S); for (unsigned i = 0, e = EWC->getNumObjects(); i != e; ++i) { - const BlockDecl *BDecl = EWC->getObject(i); - for (const auto &CI : BDecl->captures()) { - VarDecl *variable = CI.getVariable(); - BuildScopeInformation(variable, BDecl, origParentScope); - } + if (auto *BDecl = EWC->getObject(i).dyn_cast()) + for (const auto &CI : BDecl->captures()) { + VarDecl *variable = CI.getVariable(); + BuildScopeInformation(variable, BDecl, origParentScope); + } + else if (auto *CLE = EWC->getObject(i).dyn_cast()) + BuildScopeInformation(CLE, origParentScope); + else + llvm_unreachable("unexpected cleanup object type"); } break; } diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -6251,14 +6251,24 @@ return ExprError(); } - // Compound literals that have automatic storage duration are destroyed at - // the end of the scope. Emit diagnostics if it is or contains a C union type - // that is non-trivial to destruct. - if (!isFileScope) + if (!isFileScope && !getLangOpts().CPlusPlus) { + // Compound literals that have automatic storage duration are destroyed at + // the end of the scope in C; in C++, they're just temporaries. + + // Emit diagnostics if it is or contains a C union type that is non-trivial + // to destruct. if (E->getType().hasNonTrivialToPrimitiveDestructCUnion()) checkNonTrivialCUnion(E->getType(), E->getExprLoc(), NTCUC_CompoundLiteral, NTCUK_Destruct); + // Diagnose jumps that enter or exit the lifetime of the compound literal. + if (literalType.isDestructedType()) { + Cleanup.setExprNeedsCleanups(true); + ExprCleanupObjects.push_back(E); + getCurFunction()->setHasBranchProtectedScope(); + } + } + if (E->getType().hasNonTrivialToPrimitiveDefaultInitializeCUnion() || E->getType().hasNonTrivialToPrimitiveCopyCUnion()) checkNonTrivialCUnionInInitializer(E->getInitializer(), diff --git a/clang/lib/Serialization/ASTReaderStmt.cpp b/clang/lib/Serialization/ASTReaderStmt.cpp --- a/clang/lib/Serialization/ASTReaderStmt.cpp +++ b/clang/lib/Serialization/ASTReaderStmt.cpp @@ -1819,9 +1819,17 @@ unsigned NumObjects = Record.readInt(); assert(NumObjects == E->getNumObjects()); - for (unsigned i = 0; i != NumObjects; ++i) - E->getTrailingObjects()[i] = - readDeclAs(); + for (unsigned i = 0; i != NumObjects; ++i) { + unsigned CleanupKind = Record.readInt(); + ExprWithCleanups::CleanupObject Obj; + if (CleanupKind == COK_Block) + Obj = readDeclAs(); + else if (CleanupKind == COK_CompoundLiteral) + Obj = cast(Record.readSubExpr()); + else + llvm_unreachable("unexpected cleanup object type"); + E->getTrailingObjects()[i] = Obj; + } E->ExprWithCleanupsBits.CleanupsHaveSideEffects = Record.readInt(); E->SubExpr = Record.readSubExpr(); diff --git a/clang/lib/Serialization/ASTWriterStmt.cpp b/clang/lib/Serialization/ASTWriterStmt.cpp --- a/clang/lib/Serialization/ASTWriterStmt.cpp +++ b/clang/lib/Serialization/ASTWriterStmt.cpp @@ -1721,8 +1721,15 @@ void ASTStmtWriter::VisitExprWithCleanups(ExprWithCleanups *E) { VisitExpr(E); Record.push_back(E->getNumObjects()); - for (unsigned i = 0, e = E->getNumObjects(); i != e; ++i) - Record.AddDeclRef(E->getObject(i)); + for (auto &Obj : E->getObjects()) { + if (auto *BD = Obj.dyn_cast()) { + Record.push_back(serialization::COK_Block); + Record.AddDeclRef(BD); + } else if (auto *CLE = Obj.dyn_cast()) { + Record.push_back(serialization::COK_CompoundLiteral); + Record.AddStmt(CLE); + } + } Record.push_back(E->cleanupsHaveSideEffects()); Record.AddStmt(E->getSubExpr()); diff --git a/clang/test/AST/ast-dump-objc-arc-json.m b/clang/test/AST/ast-dump-objc-arc-json.m new file mode 100644 --- /dev/null +++ b/clang/test/AST/ast-dump-objc-arc-json.m @@ -0,0 +1,36 @@ +// RUN: %clang_cc1 -triple x86_64-apple-darwin10 -x objective-c -fobjc-arc -ast-dump=json -ast-dump-filter Test %s | FileCheck %s + +typedef struct { + id f; +} S; + +id TestCompoundLiteral(id a) { + return ((S){ .f = a }).f; +} + +// CHECK: "kind": "ExprWithCleanups", +// CHECK-NEXT: "range": { +// CHECK-NEXT: "begin": { +// CHECK-NEXT: "offset": 202, +// CHECK-NEXT: "col": 10, +// CHECK-NEXT: "tokLen": 1 +// CHECK-NEXT: }, +// CHECK-NEXT: "end": { +// CHECK-NEXT: "offset": 218, +// CHECK-NEXT: "col": 26, +// CHECK-NEXT: "tokLen": 1 +// CHECK-NEXT: } +// CHECK-NEXT: }, +// CHECK-NEXT: "type": { +// CHECK-NEXT: "desugaredQualType": "id", +// CHECK-NEXT: "qualType": "id", +// CHECK-NEXT: "typeAliasDeclId": "0x{{.*}}" +// CHECK-NEXT: }, +// CHECK-NEXT: "valueCategory": "rvalue", +// CHECK-NEXT: "cleanupsHaveSideEffects": true, +// CHECK-NEXT: "cleanups": [ +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "CompoundLiteralExpr" +// CHECK-NEXT: } +// CHECK-NEXT: ], diff --git a/clang/test/AST/ast-dump-stmt.m b/clang/test/AST/ast-dump-stmt.m --- a/clang/test/AST/ast-dump-stmt.m +++ b/clang/test/AST/ast-dump-stmt.m @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -Wno-unused -fblocks -fobjc-exceptions -ast-dump -ast-dump-filter Test %s | FileCheck -strict-whitespace %s +// RUN: %clang_cc1 -Wno-unused -fobjc-arc -fblocks -fobjc-exceptions -ast-dump -ast-dump-filter Test %s | FileCheck -strict-whitespace %s void TestBlockExpr(int x) { ^{ x; }; @@ -34,3 +34,16 @@ // CHECK-NEXT: ObjCAtCatchStmt{{.*}} catch all // CHECK-NEXT: CompoundStmt // CHECK-NEXT: ObjCAtFinallyStmt + +typedef struct { + id f; +} S; + +id TestCompoundLiteral(id a) { + return ((S){ .f = a }).f; +} + +// CHECK: FunctionDecl{{.*}}TestCompoundLiteral +// CHECK: ExprWithCleanups +// CHECK-NEXT: cleanup CompoundLiteralExpr +// CHECK: CompoundLiteralExpr{{.*}}'S':'S' lvalue diff --git a/clang/test/CodeGenObjC/arc-ternary-op.m b/clang/test/CodeGenObjC/arc-ternary-op.m --- a/clang/test/CodeGenObjC/arc-ternary-op.m +++ b/clang/test/CodeGenObjC/arc-ternary-op.m @@ -1,5 +1,7 @@ // RUN: %clang_cc1 -triple x86_64-apple-darwin10 -emit-llvm -fblocks -fobjc-arc -fobjc-runtime-has-weak -O2 -disable-llvm-passes -o - %s | FileCheck %s +id g0, g1; + void test0(_Bool cond) { id test0_helper(void) __attribute__((ns_returns_retained)); @@ -147,4 +149,58 @@ // CHECK: call void @llvm.objc.release(i8* [[RESULT]]) } +void test3(int cond) { + __strong id *p = cond ? (__strong id[]){g0, g1} : (__strong id[]){g1, g0}; + test2(cond); + + // CHECK: define void @test3( + // CHECK: %[[P:.*]] = alloca i8**, align 8 + // CHECK: %[[_COMPOUNDLITERAL:.*]] = alloca [2 x i8*], align 8 + // CHECK: %[[CLEANUP_COND:.*]] = alloca i1, align 1 + // CHECK: %[[_COMPOUNDLITERAL1:.*]] = alloca [2 x i8*], align 8 + // CHECK: %[[CLEANUP_COND4:.*]] = alloca i1, align 1 + + // CHECK: %[[ARRAYINIT_BEGIN:.*]] = getelementptr inbounds [2 x i8*], [2 x i8*]* %[[_COMPOUNDLITERAL]], i64 0, i64 0 + // CHECK: %[[V2:.*]] = load i8*, i8** @g0, align 8 + // CHECK: %[[V3:.*]] = call i8* @llvm.objc.retain(i8* %[[V2]]) + // CHECK: store i8* %[[V3]], i8** %[[ARRAYINIT_BEGIN]], align 8 + // CHECK: %[[ARRAYINIT_ELEMENT:.*]] = getelementptr inbounds i8*, i8** %[[ARRAYINIT_BEGIN]], i64 1 + // CHECK: %[[V4:.*]] = load i8*, i8** @g1, align 8 + // CHECK: %[[V5:.*]] = call i8* @llvm.objc.retain(i8* %[[V4]]) + // CHECK: store i8* %[[V5]], i8** %[[ARRAYINIT_ELEMENT]], align 8 + // CHECK: store i1 true, i1* %[[CLEANUP_COND]], align 1 + // CHECK: %[[ARRAYDECAY:.*]] = getelementptr inbounds [2 x i8*], [2 x i8*]* %[[_COMPOUNDLITERAL]], i64 0, i64 0 + + // CHECK: %[[ARRAYINIT_BEGIN2:.*]] = getelementptr inbounds [2 x i8*], [2 x i8*]* %[[_COMPOUNDLITERAL1]], i64 0, i64 0 + // CHECK: %[[V6:.*]] = load i8*, i8** @g1, align 8 + // CHECK: %[[V7:.*]] = call i8* @llvm.objc.retain(i8* %[[V6]]) + // CHECK: store i8* %[[V7]], i8** %[[ARRAYINIT_BEGIN2]], align 8 + // CHECK: %[[ARRAYINIT_ELEMENT3:.*]] = getelementptr inbounds i8*, i8** %[[ARRAYINIT_BEGIN2]], i64 1 + // CHECK: %[[V8:.*]] = load i8*, i8** @g0, align 8 + // CHECK: %[[V9:.*]] = call i8* @llvm.objc.retain(i8* %[[V8]]) + // CHECK: store i8* %[[V9]], i8** %[[ARRAYINIT_ELEMENT3]], align 8 + // CHECK: store i1 true, i1* %[[CLEANUP_COND4]], align 1 + // CHECK: %[[ARRAYDECAY5:.*]] = getelementptr inbounds [2 x i8*], [2 x i8*]* %[[_COMPOUNDLITERAL1]], i64 0, i64 0 + + // CHECK: %[[COND6:.*]] = phi i8** [ %[[ARRAYDECAY]], %{{.*}} ], [ %[[ARRAYDECAY5]], %{{.*}} ] + // CHECK: store i8** %[[COND6]], i8*** %[[P]], align 8 + // CHECK: call void @test2( + + // CHECK: %[[ARRAY_BEGIN:.*]] = getelementptr inbounds [2 x i8*], [2 x i8*]* %[[_COMPOUNDLITERAL1]], i32 0, i32 0 + // CHECK: %[[V11:.*]] = getelementptr inbounds i8*, i8** %[[ARRAY_BEGIN]], i64 2 + + // CHECK: %[[ARRAYDESTROY_ELEMENTPAST:.*]] = phi i8** [ %[[V11]], %{{.*}} ], [ %[[ARRAYDESTROY_ELEMENT:.*]], %{{.*}} ] + // CHECK: %[[ARRAYDESTROY_ELEMENT]] = getelementptr inbounds i8*, i8** %[[ARRAYDESTROY_ELEMENTPAST]], i64 -1 + // CHECK: %[[V12:.*]] = load i8*, i8** %[[ARRAYDESTROY_ELEMENT]], align 8 + // CHECK: call void @llvm.objc.release(i8* %[[V12]]) + + // CHECK: %[[ARRAY_BEGIN10:.*]] = getelementptr inbounds [2 x i8*], [2 x i8*]* %[[_COMPOUNDLITERAL]], i32 0, i32 0 + // CHECK: %[[V13:.*]] = getelementptr inbounds i8*, i8** %[[ARRAY_BEGIN10]], i64 2 + + // CHECK: %[[ARRAYDESTROY_ELEMENTPAST12:.*]] = phi i8** [ %[[V13]], %{{.*}} ], [ %[[ARRAYDESTROY_ELEMENT13:.*]], %{{.*}} ] + // CHECK: %[[ARRAYDESTROY_ELEMENT13]] = getelementptr inbounds i8*, i8** %[[ARRAYDESTROY_ELEMENTPAST12]], i64 -1 + // CHECK: %[[V14:.*]] = load i8*, i8** %[[ARRAYDESTROY_ELEMENT13]], align 8 + // CHECK: call void @llvm.objc.release(i8* %[[V14]]) +} + // CHECK: attributes [[NUW]] = { nounwind } diff --git a/clang/test/CodeGenObjC/arc.m b/clang/test/CodeGenObjC/arc.m --- a/clang/test/CodeGenObjC/arc.m +++ b/clang/test/CodeGenObjC/arc.m @@ -1557,6 +1557,43 @@ getAggDtor(); } +// Check that no extra release calls are emitted to detruct the compond literal. + +// CHECK: define void @test72(i8* %[[A:.*]], i8* %[[B:.*]]) +// CHECK: %[[A_ADDR:.*]] = alloca i8*, align 8 +// CHECK: %[[B_ADDR:.*]] = alloca i8*, align 8 +// CHECK: %[[T:.*]] = alloca [2 x i8*], align 16 +// CHECK: %[[V0:.*]] = call i8* @llvm.objc.retain(i8* %[[A]]) +// CHECK: %[[V1:.*]] = call i8* @llvm.objc.retain(i8* %[[B]]) #2 +// CHECK: %[[ARRAYINIT_BEGIN:.*]] = getelementptr inbounds [2 x i8*], [2 x i8*]* %[[T]], i64 0, i64 0 +// CHECK: %[[V3:.*]] = load i8*, i8** %[[A_ADDR]], align 8, !tbaa !7 +// CHECK: %[[V4:.*]] = call i8* @llvm.objc.retain(i8* %[[V3]]) #2 +// CHECK: store i8* %[[V4]], i8** %[[ARRAYINIT_BEGIN]], align 8, !tbaa !7 +// CHECK: %[[ARRAYINIT_ELEMENT:.*]] = getelementptr inbounds i8*, i8** %[[ARRAYINIT_BEGIN]], i64 1 +// CHECK: %[[V5:.*]] = load i8*, i8** %[[B_ADDR]], align 8, !tbaa !7 +// CHECK: %[[V6:.*]] = call i8* @llvm.objc.retain(i8* %[[V5]]) #2 +// CHECK: store i8* %[[V6]], i8** %[[ARRAYINIT_ELEMENT]], align 8, !tbaa !7 +// CHECK: %[[ARRAY_BEGIN:.*]] = getelementptr inbounds [2 x i8*], [2 x i8*]* %[[T]], i32 0, i32 0 +// CHECK: %[[V7:.*]] = getelementptr inbounds i8*, i8** %[[ARRAY_BEGIN]], i64 2 + +// CHECK-NOT: call void @llvm.objc.release + +// CHECK: %[[ARRAYDESTROY_ELEMENTPAST:.*]] = phi i8** [ %[[V7]], %{{.*}} ], [ %[[ARRAYDESTROY_ELEMENT:.*]], %{{.*}} ] +// CHECK: %[[ARRAYDESTROY_ELEMENT]] = getelementptr inbounds i8*, i8** %[[ARRAYDESTROY_ELEMENTPAST]], i64 -1 +// CHECK: %[[V8:.*]] = load i8*, i8** %[[ARRAYDESTROY_ELEMENT]], align 8 +// CHECK: call void @llvm.objc.release(i8* %[[V8]]) #2, !clang.imprecise_release !10 + +// CHECK-NOT: call void @llvm.objc.release + +// CHECK: %[[V10:.*]] = load i8*, i8** %[[B_ADDR]], align 8 +// CHECK: call void @llvm.objc.release(i8* %[[V10]]) #2, !clang.imprecise_release !10 +// CHECK: %[[V11:.*]] = load i8*, i8** %[[A_ADDR]], align 8 +// CHECK: call void @llvm.objc.release(i8* %[[V11]]) #2, !clang.imprecise_release !10 + +void test72(id a, id b) { + __strong id t[] = (__strong id[]){a, b}; +} + // ARC-ALIEN: attributes [[NLB]] = { nonlazybind } // ARC-NATIVE: attributes [[NLB]] = { nonlazybind } // CHECK: attributes [[NUW]] = { nounwind } diff --git a/clang/test/CodeGenObjC/strong-in-c-struct.m b/clang/test/CodeGenObjC/strong-in-c-struct.m --- a/clang/test/CodeGenObjC/strong-in-c-struct.m +++ b/clang/test/CodeGenObjC/strong-in-c-struct.m @@ -709,4 +709,103 @@ VolatileArray t = *a; } +// CHECK: define void @test_compound_literal0( +// CHECK: %[[P:.*]] = alloca %[[STRUCT_STRONGSMALL]]*, align 8 +// CHECK: %[[_COMPOUNDLITERAL:.*]] = alloca %[[STRUCT_STRONGSMALL]], align 8 +// CHECK: %[[CLEANUP_COND:.*]] = alloca i1, align 1 +// CHECK: %[[_COMPOUNDLITERAL1:.*]] = alloca %[[STRUCT_STRONGSMALL]], align 8 +// CHECK: %[[CLEANUP_COND4:.*]] = alloca i1, align 1 + +// CHECK: %[[I:.*]] = getelementptr inbounds %[[STRUCT_STRONGSMALL]], %[[STRUCT_STRONGSMALL]]* %[[_COMPOUNDLITERAL]], i32 0, i32 0 +// CHECK: store i32 1, i32* %[[I]], align 8 +// CHECK: %[[F1:.*]] = getelementptr inbounds %[[STRUCT_STRONGSMALL]], %[[STRUCT_STRONGSMALL]]* %[[_COMPOUNDLITERAL]], i32 0, i32 1 +// CHECK: store i8* null, i8** %[[F1]], align 8 +// CHECK: store i1 true, i1* %[[CLEANUP_COND]], align 1 + +// CHECK: %[[I2:.*]] = getelementptr inbounds %[[STRUCT_STRONGSMALL]], %[[STRUCT_STRONGSMALL]]* %[[_COMPOUNDLITERAL1]], i32 0, i32 0 +// CHECK: store i32 2, i32* %[[I2]], align 8 +// CHECK: %[[F13:.*]] = getelementptr inbounds %[[STRUCT_STRONGSMALL]], %[[STRUCT_STRONGSMALL]]* %[[_COMPOUNDLITERAL1]], i32 0, i32 1 +// CHECK: store i8* null, i8** %[[F13]], align 8 +// CHECK: store i1 true, i1* %[[CLEANUP_COND4]], align 1 + +// CHECK: %[[COND:.*]] = phi %[[STRUCT_STRONGSMALL]]* [ %[[_COMPOUNDLITERAL]], %{{.*}} ], [ %[[_COMPOUNDLITERAL1]], %{{.*}} ] +// CHECK: store %[[STRUCT_STRONGSMALL]]* %[[COND]], %[[STRUCT_STRONGSMALL]]** %[[P]], align 8 +// CHECK: call void @func( + +// CHECK: %[[V1:.*]] = bitcast %[[STRUCT_STRONGSMALL]]* %[[_COMPOUNDLITERAL1]] to i8** +// CHECK: call void @__destructor_8_s8(i8** %[[V1]]) + +// CHECK: %[[V2:.*]] = bitcast %[[STRUCT_STRONGSMALL]]* %[[_COMPOUNDLITERAL]] to i8** +// CHECK: call void @__destructor_8_s8(i8** %[[V2]]) + +void test_compound_literal0(int c) { + StrongSmall *p = c ? &(StrongSmall){ 1, 0 } : &(StrongSmall){ 2, 0 }; + func(0); +} + +// Check that there is only one destructor call, which destructs 't'. + +// CHECK: define void @test_compound_literal1( +// CHECK: %[[T:.*]] = alloca %[[STRUCT_STRONGSMALL]], align 8 + +// CHECK: %[[I:.*]] = getelementptr inbounds %[[STRUCT_STRONGSMALL]], %[[STRUCT_STRONGSMALL]]* %[[T]], i32 0, i32 0 +// CHECK: store i32 1, i32* %[[I]], align 8 +// CHECK: %[[F1:.*]] = getelementptr inbounds %[[STRUCT_STRONGSMALL]], %[[STRUCT_STRONGSMALL]]* %[[T]], i32 0, i32 1 +// CHECK: store i8* null, i8** %[[F1]], align 8 + +// CHECK: %[[I1:.*]] = getelementptr inbounds %[[STRUCT_STRONGSMALL]], %[[STRUCT_STRONGSMALL]]* %[[T]], i32 0, i32 0 +// CHECK: store i32 2, i32* %[[I1]], align 8 +// CHECK: %[[F12:.*]] = getelementptr inbounds %[[STRUCT_STRONGSMALL]], %[[STRUCT_STRONGSMALL]]* %[[T]], i32 0, i32 1 +// CHECK: store i8* null, i8** %[[F12]], align 8 + +// CHECK: call void @func( +// CHECK-NOT: call void +// CHECK: %[[V1:.*]] = bitcast %[[STRUCT_STRONGSMALL]]* %[[T]] to i8** +// CHECK: call void @__destructor_8_s8(i8** %[[V1]]) +// CHECK-NOT: call void + +void test_compound_literal1(int c) { + StrongSmall t = c ? (StrongSmall){ 1, 0 } : (StrongSmall){ 2, 0 }; + func(0); +} + +// CHECK: define void @test_compound_literal2( +// CHECK: %[[P_ADDR:.*]] = alloca %[[STRUCT_STRONGSMALL]]*, align 8 +// CHECK: %[[_COMPOUNDLITERAL:.*]] = alloca %[[STRUCT_STRONGSMALL]], align 8 +// CHECK: %[[CLEANUP_COND:.*]] = alloca i1, align 1 +// CHECK: %[[_COMPOUNDLITERAL1:.*]] = alloca %[[STRUCT_STRONGSMALL]], align 8 +// CHECK: %[[CLEANUP_COND4:.*]] = alloca i1, align 1 +// CHECK: %[[V0:.*]] = load %[[STRUCT_STRONGSMALL]]*, %[[STRUCT_STRONGSMALL]]** %[[P_ADDR]], align 8 + +// CHECK: %[[I:.*]] = getelementptr inbounds %[[STRUCT_STRONGSMALL]], %[[STRUCT_STRONGSMALL]]* %[[_COMPOUNDLITERAL]], i32 0, i32 0 +// CHECK: store i32 1, i32* %[[I]], align 8 +// CHECK: %[[F1:.*]] = getelementptr inbounds %[[STRUCT_STRONGSMALL]], %[[STRUCT_STRONGSMALL]]* %[[_COMPOUNDLITERAL]], i32 0, i32 1 +// CHECK: store i8* null, i8** %[[F1]], align 8 +// CHECK: store i1 true, i1* %[[CLEANUP_COND]], align 1 +// CHECK: %[[V2:.*]] = bitcast %[[STRUCT_STRONGSMALL]]* %[[V0]] to i8** +// CHECK: %[[V3:.*]] = bitcast %[[STRUCT_STRONGSMALL]]* %[[_COMPOUNDLITERAL]] to i8** +// CHECK: call void @__copy_assignment_8_8_t0w4_s8(i8** %[[V2]], i8** %[[V3]]) + +// CHECK: %[[I2:.*]] = getelementptr inbounds %[[STRUCT_STRONGSMALL]], %[[STRUCT_STRONGSMALL]]* %[[_COMPOUNDLITERAL1]], i32 0, i32 0 +// CHECK: store i32 2, i32* %[[I2]], align 8 +// CHECK: %[[F13:.*]] = getelementptr inbounds %[[STRUCT_STRONGSMALL]], %[[STRUCT_STRONGSMALL]]* %[[_COMPOUNDLITERAL1]], i32 0, i32 1 +// CHECK: store i8* null, i8** %[[F13]], align 8 +// CHECK: store i1 true, i1* %[[CLEANUP_COND4]], align 1 +// CHECK: %[[V4:.*]] = bitcast %[[STRUCT_STRONGSMALL]]* %[[V0]] to i8** +// CHECK: %[[V5:.*]] = bitcast %[[STRUCT_STRONGSMALL]]* %[[_COMPOUNDLITERAL1]] to i8** +// CHECK: call void @__copy_assignment_8_8_t0w4_s8(i8** %[[V4]], i8** %[[V5]]) + +// CHECK: call void @func( + +// CHECK: %[[V6:.*]] = bitcast %[[STRUCT_STRONGSMALL]]* %[[_COMPOUNDLITERAL1]] to i8** +// CHECK: call void @__destructor_8_s8(i8** %[[V6]]) + +// CHECK: %[[V7:.*]] = bitcast %[[STRUCT_STRONGSMALL]]* %[[_COMPOUNDLITERAL]] to i8** +// CHECK: call void @__destructor_8_s8(i8** %[[V7]]) + +void test_compound_literal2(int c, StrongSmall *p) { + *p = c ? (StrongSmall){ 1, 0 } : (StrongSmall){ 2, 0 }; + func(0); +} + #endif /* USESTRUCT */ diff --git a/clang/test/Import/objc-arc/Inputs/cleanup-objects.m b/clang/test/Import/objc-arc/Inputs/cleanup-objects.m new file mode 100644 --- /dev/null +++ b/clang/test/Import/objc-arc/Inputs/cleanup-objects.m @@ -0,0 +1,10 @@ +typedef struct { + id x; +} S; + +id getObj(int c, id a) { + // Commenting out the following line because AST importer crashes when trying + // to import a BlockExpr. + // return c ? ^{ return a; }() : ((S){ .x = a }).x; + return ((S){ .x = a }).x; +} diff --git a/clang/test/Import/objc-arc/test-cleanup-object.m b/clang/test/Import/objc-arc/test-cleanup-object.m new file mode 100644 --- /dev/null +++ b/clang/test/Import/objc-arc/test-cleanup-object.m @@ -0,0 +1,10 @@ +// RUN: clang-import-test -x objective-c -objc-arc -import %S/Inputs/cleanup-objects.m -dump-ast -expression %s | FileCheck %s + +// CHECK: FunctionDecl {{.*}} getObj ' +// CHECK: ExprWithCleanups +// CHECK-NEXT: cleanup CompoundLiteralExpr + +void test(int c, id a) { + (void)getObj(c, a); +} + diff --git a/clang/test/PCH/non-trivial-c-compound-literal.m b/clang/test/PCH/non-trivial-c-compound-literal.m new file mode 100644 --- /dev/null +++ b/clang/test/PCH/non-trivial-c-compound-literal.m @@ -0,0 +1,29 @@ +// RUN: %clang_cc1 -triple x86_64-apple-darwin10 -x objective-c -fobjc-arc -emit-pch -o %t %s +// RUN: %clang_cc1 -triple x86_64-apple-darwin10 -x objective-c -fobjc-arc -include-pch %t -emit-llvm -o - %s | FileCheck %s + +#ifndef HEADER +#define HEADER + +typedef struct { + id f; +} S; + +static inline id getObj(id a) { + S *p = &(S){ .f = a }; + return p->f; +} + +#else + +// CHECK: %[[STRUCT_S:.*]] = type { i8* } + +// CHECK: define internal i8* @getObj( +// CHECK: %[[_COMPOUNDLITERAL:.*]] = alloca %[[STRUCT_S]], +// CHECK: %[[V5:.*]] = bitcast %[[STRUCT_S]]* %[[_COMPOUNDLITERAL]] to i8** +// CHECK: call void @__destructor_8_s0(i8** %[[V5]]) + +id test(id a) { + return getObj(a); +} + +#endif diff --git a/clang/test/SemaObjC/strong-in-c-struct.m b/clang/test/SemaObjC/strong-in-c-struct.m --- a/clang/test/SemaObjC/strong-in-c-struct.m +++ b/clang/test/SemaObjC/strong-in-c-struct.m @@ -54,3 +54,21 @@ func(^{ func2(x); }); goto *ips; // expected-error {{cannot jump}} } + +void test_compound_literal0(int cond, id x) { + switch (cond) { + case 0: + (void)(Strong){ .a = x }; // expected-note {{jump enters lifetime of a compound literal that is non-trivial to destruct}} + break; + default: // expected-error {{cannot jump from switch statement to this case label}} + break; + } +} + +void test_compound_literal1(id x) { + static void *ips[] = { &&L0 }; +L0: // expected-note {{possible target of indirect goto}} + ; + (void)(Strong){ .a = x }; // expected-note {{jump exits lifetime of a compound literal that is non-trivial to destruct}} + goto *ips; // expected-error {{cannot jump}} +} diff --git a/clang/tools/clang-import-test/clang-import-test.cpp b/clang/tools/clang-import-test/clang-import-test.cpp --- a/clang/tools/clang-import-test/clang-import-test.cpp +++ b/clang/tools/clang-import-test/clang-import-test.cpp @@ -64,6 +64,9 @@ llvm::cl::desc("The language to parse (default: c++)"), llvm::cl::init("c++")); +static llvm::cl::opt ObjCARC("objc-arc", llvm::cl::init(false), + llvm::cl::desc("Emable ObjC ARC")); + static llvm::cl::opt DumpAST("dump-ast", llvm::cl::init(false), llvm::cl::desc("Dump combined AST")); @@ -183,6 +186,8 @@ Inv->getLangOpts()->ObjC = 1; } } + Inv->getLangOpts()->ObjCAutoRefCount = ObjCARC; + Inv->getLangOpts()->Bool = true; Inv->getLangOpts()->WChar = true; Inv->getLangOpts()->Blocks = true;