Author: Morris Hafner Date: 2026-10-24T13:40:08+02:00 New Revision: 04b14262efc4b0c31c5571f5cd354e5b413d6f4c
URL: https://github.com/llvm/llvm-project/commit/04b14262efc4b0c31c5571f5cd354e5b413d6f4c DIFF: https://github.com/llvm/llvm-project/commit/04b14262efc4b0c31c5571f5cd354e5b413d6f4c.diff LOG: [CIR] Fix temporary cleanup boundaries for asm and coroutines (#234664) - Destroy asm operand temporaries at the end of the asm statement. - Defer resume-expression temporary cleanups to the end of the enclosing full-expression - Clean up array-element default-argument temporaries before constructing the next element, preserving lifetime extension. - Keep conditional cleanup scopes open until the end of their owning full-expression. Note that this overlaps with #234303. I will rebase either depending on which one gets merged first. Assisted-By: Claude Opus 5.5 Added: Modified: clang/include/clang/CIR/MissingFeatures.h clang/lib/CIR/CodeGen/CIRGenAsm.cpp clang/lib/CIR/CodeGen/CIRGenClass.cpp clang/lib/CIR/CodeGen/CIRGenCleanup.cpp clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp clang/lib/CIR/CodeGen/CIRGenDecl.cpp clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp clang/lib/CIR/CodeGen/CIRGenFunction.h clang/test/CIR/CodeGen/array-ctor.cpp clang/test/CIR/CodeGen/cleanup-conditional-eh.cpp clang/test/CIR/CodeGen/cleanup-conditional-with-wrapper.cpp clang/test/CIR/CodeGen/cleanup-conditional.cpp clang/test/CIR/CodeGen/cleanup-scope-tmp.cpp clang/test/CIR/CodeGenCoroutines/coro-agg.cpp Removed: ################################################################################ diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h index 7beb06541f7b64..ed7c2b9cfc92a1 100644 --- a/clang/include/clang/CIR/MissingFeatures.h +++ b/clang/include/clang/CIR/MissingFeatures.h @@ -119,7 +119,6 @@ struct MissingFeatures { static bool noUniqueAddressLayout() { return false; } // Coroutines - static bool coroAwaitFullExprCleanups() { return false; } static bool coroOutsideFrameMD() { return false; } static bool coroGetRetObjManager() { return false; }; static bool coroUnwindRegion() { return false; }; diff --git a/clang/lib/CIR/CodeGen/CIRGenAsm.cpp b/clang/lib/CIR/CodeGen/CIRGenAsm.cpp index 31b5ff07ecf641..c3c676bd742c12 100644 --- a/clang/lib/CIR/CodeGen/CIRGenAsm.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenAsm.cpp @@ -261,6 +261,8 @@ static void emitAsmStores(CIRGenFunction &cgf, const AsmStmt &s, } mlir::LogicalResult CIRGenFunction::emitAsmStmt(const AsmStmt &s) { + FullExprCleanupScope cleanups(*this, &s); + // Assemble the final asm string. std::string asmString = s.generateAsmString(getContext()); SourceLocation srcLoc = s.getAsmLoc(); diff --git a/clang/lib/CIR/CodeGen/CIRGenClass.cpp b/clang/lib/CIR/CodeGen/CIRGenClass.cpp index bcf301a0b5ae20..738f5464672435 100644 --- a/clang/lib/CIR/CodeGen/CIRGenClass.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenClass.cpp @@ -790,7 +790,8 @@ void CIRGenFunction::emitCXXAggrConstructorCall( // destruction of every temporary created in a default argument expression // is sequenced before the construction of the next array element, if any. { - RunCleanupsScope scope(*this); + FullExprCleanupScope scope(*this, e); + scope.deferLifetimeExtendedCleanups(); emitCXXConstructorCall(ctor, Ctor_Complete, /*ForVirtualBase=*/false, /*Delegating=*/false, currAVS, e); diff --git a/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp b/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp index 98fb9502ef0169..63e9dc24acbcfe 100644 --- a/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp @@ -20,7 +20,7 @@ #include "CIRGenFunction.h" #include "clang/AST/RecursiveASTVisitor.h" -#include "clang/CIR/MissingFeatures.h" +#include "llvm/Support/SaveAndRestore.h" using namespace clang; using namespace clang::CIRGen; @@ -31,14 +31,39 @@ namespace { class ConditionalEvaluationFinder : public RecursiveASTVisitor<ConditionalEvaluationFinder> { bool foundConditional = false; + bool inResumeExpr = false; + + bool traverseSuspendExpr(CoroutineSuspendExpr *e) { + return TraverseStmt(e->getCommonExpr()) && traverseResumeExpr(e); + } + + /// Traverse the resume expression of \p e, except for the binding of its + /// result, which emitSuspendExpression destroys after the cir.await. + bool traverseResumeExpr(CoroutineSuspendExpr *e) { + Expr *resume = e->getResumeExpr(); + if (auto *bind = dyn_cast_if_present<CXXBindTemporaryExpr>(resume)) + resume = bind->getSubExpr(); + llvm::SaveAndRestore inResume(inResumeExpr, true); + return TraverseStmt(resume); + } public: + static bool needsCleanupScope(const Stmt *s) { + ConditionalEvaluationFinder finder; + finder.TraverseStmt(const_cast<Stmt *>(s)); + return finder.foundConditional; + } + + static bool resumeExprDefersCleanups(const CoroutineSuspendExpr &s) { + ConditionalEvaluationFinder finder; + finder.traverseResumeExpr(const_cast<CoroutineSuspendExpr *>(&s)); + return finder.foundConditional; + } + // Default arguments and default member initializers can contain conditional // temporaries whose cleanups belong to the enclosing full-expression. bool shouldVisitImplicitCode() const { return true; } - bool found() const { return foundConditional; } - bool VisitAbstractConditionalOperator(AbstractConditionalOperator *) { foundConditional = true; return false; @@ -74,19 +99,31 @@ class ConditionalEvaluationFinder bool TraverseBlockExpr(BlockExpr *) { return true; } bool TraverseStmtExpr(StmtExpr *) { return true; } - // Skip over the implicit call to await_resume(). This requires cleanup - // scopes for await full-expressions which don't exist yet. - bool TraverseCoawaitExpr(CoawaitExpr *e) { - assert(!cir::MissingFeatures::coroAwaitFullExprCleanups()); - return TraverseStmt(e->getOperand()); + bool TraverseGCCAsmStmt(GCCAsmStmt *s) { + for (Stmt *operand : s->children()) + if (!TraverseStmt(operand)) + return false; + return true; } - bool TraverseDependentCoawaitExpr(DependentCoawaitExpr *e) { - assert(!cir::MissingFeatures::coroAwaitFullExprCleanups()); - return TraverseStmt(e->getOperand()); + + // Traverse the common and resume expressions, whose temporaries belong to + // the enclosing full-expression. Ready and suspend have their own cleanup + // boundaries. The resume result's cleanup is handled separately. + bool TraverseCoawaitExpr(CoawaitExpr *e) { return traverseSuspendExpr(e); } + bool TraverseCoyieldExpr(CoyieldExpr *e) { return traverseSuspendExpr(e); } + + bool VisitCXXBindTemporaryExpr(CXXBindTemporaryExpr *) { + if (!inResumeExpr) + return true; + foundConditional = true; + return false; } - bool TraverseCoyieldExpr(CoyieldExpr *e) { - assert(!cir::MissingFeatures::coroAwaitFullExprCleanups()); - return TraverseStmt(e->getOperand()); + + bool VisitMaterializeTemporaryExpr(MaterializeTemporaryExpr *e) { + if (!inResumeExpr || !e->getType().isDestructedType()) + return true; + foundConditional = true; + return false; } }; } // namespace @@ -95,6 +132,10 @@ class ConditionalEvaluationFinder // CIRGenFunction cleanup related //===----------------------------------------------------------------------===// +bool CIRGenFunction::resumeExprDefersCleanups(const CoroutineSuspendExpr &s) { + return ConditionalEvaluationFinder::resumeExprDefersCleanups(s); +} + /// Emits all the code to cause the given temporary to be cleaned up. void CIRGenFunction::emitCXXTemporary(const CXXTemporary *temporary, QualType tempType, Address ptr) { @@ -143,16 +184,16 @@ void CIRGenFunction::initFullExprCleanupWithFlag(Address activeFlag) { } CIRGenFunction::FullExprCleanupScope::FullExprCleanupScope(CIRGenFunction &cgf, - const Expr *subExpr) + const Stmt *s) : cgf(cgf), cleanups(cgf), scope(nullptr), oldFullExprCleanupScope(cgf.currentFullExprCleanupScope), + oldOutermostConditional(cgf.outermostConditional), + oldHiddenOutermostConditional(cgf.hiddenOutermostConditional), deferredCleanupStackSize(cgf.deferredConditionalCleanupStack.size()), conditionalScopeDepth(cgf.conditionalCleanupScopes.size()) { - assert(subExpr && "ExprWithCleanups always has a sub-expression"); - ConditionalEvaluationFinder finder; - finder.TraverseStmt(const_cast<Expr *>(subExpr)); - if (finder.found()) { + assert(s && "expected an AST root for a cleanup boundary"); + if (ConditionalEvaluationFinder::needsCleanupScope(s)) { mlir::Location loc = cgf.builder.getUnknownLoc(); cir::CleanupKind cleanupKind = cgf.getLangOpts().Exceptions ? cir::CleanupKind::All @@ -169,6 +210,13 @@ CIRGenFunction::FullExprCleanupScope::FullExprCleanupScope(CIRGenFunction &cgf, // Conditional cleanup scopes are only opened while a full-expression scope // is active to close them. cgf.currentFullExprCleanupScope = scope; + + // This full-expression owns its cleanups, even inside a conditional branch. + // Hide the outer conditional to keep those cleanups here, but remember it + // for lifetime-extended temporaries whose cleanups belong to an outer scope. + if (cgf.outermostConditional) + cgf.hiddenOutermostConditional = cgf.outermostConditional; + cgf.outermostConditional = nullptr; } /// If the alloca that backs \p addr is currently nested inside the body @@ -303,6 +351,8 @@ void CIRGenFunction::FullExprCleanupScope::exit( exited = true; cgf.currentFullExprCleanupScope = oldFullExprCleanupScope; + cgf.outermostConditional = oldOutermostConditional; + cgf.hiddenOutermostConditional = oldHiddenOutermostConditional; size_t oldSize = deferredCleanupStackSize; @@ -599,7 +649,16 @@ static void setupCleanupBlockDeactivation(CIRGenFunction &cgf, builder.createFlagStore(loc, false, var.getPointer()); } -/// Deactive a cleanup that was created in an active state. +static bool enclosesOpenConditionalCleanupScope(CIRGenFunction &cgf, + EHCleanupScope &scope) { + cir::CleanupScopeOp op = scope.getCleanupScopeOp(); + return op && + llvm::any_of(cgf.conditionalCleanupScopes, [&](const auto &condScope) { + return op->isProperAncestor(condScope.scope); + }); +} + +/// Deactivate a cleanup that was created in an active state. void CIRGenFunction::deactivateCleanupBlock(EHScopeStack::stable_iterator c, mlir::Operation *dominatingIP) { assert(c != ehStack.stable_end() && "deactivating bottom of stack?"); @@ -607,9 +666,11 @@ void CIRGenFunction::deactivateCleanupBlock(EHScopeStack::stable_iterator c, assert(scope.isActive() && "double deactivation"); // If it's the top of the stack, just pop it, but do so only if it belongs - // to the current RunCleanupsScope. + // to the current RunCleanupsScope. If a conditional opened a cleanup scope + // keep it open till the end of the full-expression. if (c == ehStack.stable_begin() && - currentCleanupStackDepth.strictlyEncloses(c)) { + currentCleanupStackDepth.strictlyEncloses(c) && + !enclosesOpenConditionalCleanupScope(*this, scope)) { popCleanupBlock(/*forDeactivation=*/true); return; } diff --git a/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp b/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp index 538db6747cf0ba..f0421572773b11 100644 --- a/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp @@ -714,6 +714,22 @@ emitSuspendExpression(CIRGenFunction &cgf, CGCoroData &coro, // throw. const bool resumeInTry = isResumeExprInTryCatch(coro, kind, s); + // Resume-expression temporaries live until the enclosing full-expression + // ends. Defer their cleanups and guard them with construction flags, since + // destroying the coroutine while suspended skips their construction. + // The initial-suspend try/catch cleans up its temporaries in the try block. + std::optional<CIRGenFunction::ConditionalEvaluation> resumeEval; + const bool resumeDefersCleanups = + !resumeInTry && CIRGenFunction::resumeExprDefersCleanups(s); + if (resumeDefersCleanups && cgf.currentFullExprCleanupScope) + resumeEval.emplace(cgf, cgf.getLoc(s.getSourceRange())); + else if (resumeDefersCleanups) + cgf.cgm.errorNYI(s.getSourceRange(), + "resume expression cleanups without a full-expression " + "cleanup scope"); + [[maybe_unused]] EHScopeStack::stable_iterator ehDepth = + cgf.ehStack.stable_begin(); + // If the await_resume() result needs a destructor, take over its // destruction, unless the destination owns it. The try/catch path destroys // the result itself. @@ -766,6 +782,8 @@ emitSuspendExpression(CIRGenFunction &cgf, CGCoroData &coro, }, /*resumeBuilder=*/ [&](mlir::OpBuilder &b, mlir::Location loc) { + if (resumeEval) + resumeEval->beginEvaluation(); if (resumeInTry) { // we are basically just emitting: // resumeEh = false; @@ -796,24 +814,27 @@ emitSuspendExpression(CIRGenFunction &cgf, CGCoroData &coro, struct resumeEmitter final : CIRGenFunction::cxxTryBodyEmitter { const CXXTryStmt &tryStmt; + const Expr *resumeExpr; mlir::Location loc; mlir::Value resumeEHVar; - resumeEmitter(const CXXTryStmt &tryStmt, mlir::Location loc, - Address resumeEHVar) - : tryStmt(tryStmt), loc(loc), + resumeEmitter(const CXXTryStmt &tryStmt, const Expr *resumeExpr, + mlir::Location loc, Address resumeEHVar) + : tryStmt(tryStmt), resumeExpr(resumeExpr), loc(loc), resumeEHVar(resumeEHVar.getPointer()) {} mlir::LogicalResult operator()(CIRGenFunction &cgf) override { - CIRGenFunction::RunCleanupsScope resumeCleanups(cgf); + // Destroy resume temporaries in the try block before marking + // resumption successful, so destructor exceptions are caught too. + CIRGenFunction::FullExprCleanupScope fullExpr(cgf, resumeExpr); mlir::LogicalResult res = cgf.emitStmt(tryStmt.getTryBlock(), /*useCurrentScope=*/true); - resumeCleanups.forceCleanup(); + fullExpr.exit(); cgf.getBuilder().createFlagStore(loc, true, resumeEHVar); return res; } ~resumeEmitter() override = default; - } emitter{*tryStmt, mlirLoc, coro.resumeEHVar}; + } emitter{*tryStmt, s.getResumeExpr(), mlirLoc, coro.resumeEHVar}; awaitBuild = cgf.emitCXXTryStmt(*tryStmt, emitter); // We are not supposed to obtain the value from init suspend @@ -852,10 +873,17 @@ emitSuspendExpression(CIRGenFunction &cgf, CGCoroData &coro, } } + if (resumeEval) + resumeEval->endEvaluation(); + // Returns control back to parent. cir::YieldOp::create(builder, loc); }); + assert((cgf.ehStack.stable_begin() == ehDepth || + (resumeDefersCleanups && !resumeEval)) && + "a resume expression cleanup was pushed in the resume region"); + // Push the destructor right after the cir.await, where the result starts to // exist, and not around it: destroying the coroutine at the suspend point // would destroy the not yet constructed result. The cleanup runs at the end diff --git a/clang/lib/CIR/CodeGen/CIRGenDecl.cpp b/clang/lib/CIR/CodeGen/CIRGenDecl.cpp index 33c3a753cecd50..f74d7b643debb7 100644 --- a/clang/lib/CIR/CodeGen/CIRGenDecl.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenDecl.cpp @@ -1332,7 +1332,7 @@ void CIRGenFunction::pushDestroyAndDeferDeactivation( void CIRGenFunction::pushLifetimeExtendedDestroy( CleanupKind cleanupKind, Address addr, QualType type, Destroyer *destroyer, bool useEHCleanupForArray, const ValueDecl *extendingDecl) { - if (isInConditionalBranch()) { + if (isInConditionalBranch() || hiddenOutermostConditional) { cgm.errorNYI("conditional lifetime-extended destroy"); return; } diff --git a/clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp b/clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp index 7c5ce166edb059..ac186a5b58a38b 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp @@ -869,10 +869,18 @@ void AggExprEmitter::emitArrayInit(Address destPtr, cir::ArrayType arrayTy, CIRGenFunction::ArrayInitLoopExprScope loopExprScope( cgf, setArrayInitLoopExprScope, idx); - if (arrayFiller) + // C++17 [class.temporary]p5: + // "when a default constructor is called to initialize an element of + // an array with no corresponding initializer [...] the destruction + // of every temporary created in a default argument is sequenced + // before the construction of the next array element, if any." + if (arrayFiller) { + CIRGenFunction::FullExprCleanupScope scope(cgf, arrayFiller); + scope.deferLifetimeExtendedCleanups(); emitInitializationToLValue(arrayFiller, elementLV); - else + } else { emitNullInitializationToLValue(loc, elementLV); + } // Advance pointer and store them to temporary variable cir::ConstantOp one = builder.getConstInt( diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h index 218b97ecb40356..396c10ec9001a5 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.h +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h @@ -1361,6 +1361,12 @@ class CIRGenFunction : public CIRGenTypeCache { EHScopeStack::stable_iterator cleanupStackDepth, oldCleanupStackDepth; size_t lifetimeExtendedCleanupStackSize; CleanupDeactivationScope deactivateCleanups; + bool deferLifetimeExtended = false; + + size_t oldLifetimeExtendedSize() const { + return deferLifetimeExtended ? cgf.lifetimeExtendedCleanupStack.size() + : lifetimeExtendedCleanupStackSize; + } protected: bool performCleanup; @@ -1441,8 +1447,7 @@ class CIRGenFunction : public CIRGenTypeCache { // pop cir.cleanup.scope ops); they already dominate the post-scope // insertion point on their own. if (didSpillAny) { - cgf.popCleanupBlocks(cleanupStackDepth, - lifetimeExtendedCleanupStackSize); + cgf.popCleanupBlocks(cleanupStackDepth, oldLifetimeExtendedSize()); // Reload the spilled values now that all cleanup popping (and // promotion of any lifetime-extended cleanups onto the EH stack) is @@ -1453,8 +1458,8 @@ class CIRGenFunction : public CIRGenTypeCache { *valPtr = cgf.builder.createLoad(valPtr->getLoc(), addr); } } else { - cgf.popCleanupBlocks(cleanupStackDepth, - lifetimeExtendedCleanupStackSize, valuesToReload); + cgf.popCleanupBlocks(cleanupStackDepth, oldLifetimeExtendedSize(), + valuesToReload); } performCleanup = false; @@ -1479,7 +1484,7 @@ class CIRGenFunction : public CIRGenTypeCache { assert(performCleanup && "Already forced cleanup"); assert(deactivateCleanups.deactivated && "forceCleanupExceptLifetimeExtended() must be called first"); - cgf.popCleanupBlocks(cleanupStackDepth, lifetimeExtendedCleanupStackSize); + cgf.popCleanupBlocks(cleanupStackDepth, oldLifetimeExtendedSize()); performCleanup = false; cgf.currentCleanupStackDepth = oldCleanupStackDepth; } @@ -1489,25 +1494,43 @@ class CIRGenFunction : public CIRGenTypeCache { bool hasPendingCleanups() const { return cgf.ehStack.stable_begin() != cleanupStackDepth; } + + /// Leave the cleanups of lifetime-extended temporaries created in this + /// scope to the enclosing scope instead of pushing them onto the EH stack + /// when this scope ends. This is for a scope that ends in the body of a + /// loop, such as the construction of an array element: the cleanup scope + /// of such a temporary ends with that of the variable that extends its + /// lifetime, after the loop. + void deferLifetimeExtendedCleanups() { deferLifetimeExtended = true; } }; // Cleanup stack depth of the RunCleanupsScope that was pushed most recently. EHScopeStack::stable_iterator currentCleanupStackDepth = ehStack.stable_end(); + class ConditionalEvaluation; + class FullExprCleanupScope { CIRGenFunction &cgf; RunCleanupsScope cleanups; cir::CleanupScopeOp scope; cir::CleanupScopeOp oldFullExprCleanupScope; + ConditionalEvaluation *oldOutermostConditional; + ConditionalEvaluation *oldHiddenOutermostConditional; size_t deferredCleanupStackSize; size_t conditionalScopeDepth; bool exited = false; public: - FullExprCleanupScope(CIRGenFunction &cgf, const Expr *subExpr); + /// \p s is the AST root for one cleanup boundary: a full-expression, an + /// asm statement, or the construction of one array element. + FullExprCleanupScope(CIRGenFunction &cgf, const Stmt *s); void exit(ArrayRef<mlir::Value *> valuesToReload = {}); + void deferLifetimeExtendedCleanups() { + cleanups.deferLifetimeExtendedCleanups(); + } + ~FullExprCleanupScope() { if (!exited) exit(); @@ -1518,6 +1541,11 @@ class CIRGenFunction : public CIRGenTypeCache { void operator=(const FullExprCleanupScope &) = delete; }; + /// Whether the resume expression of \p s contains a construct that defers + /// cleanups to the end of the enclosing full-expression: a temporary with a + /// destructor, other than the result of await_resume(), or a conditional. + static bool resumeExprDefersCleanups(const CoroutineSuspendExpr &s); + /// Captures cleanups for variables whose lifetime ends in a region owned by /// an enclosing op rather than at the end of a cir.cleanup.scope, so that /// they can be emitted into that region later. Examples for uses are: @@ -2713,6 +2741,13 @@ class CIRGenFunction : public CIRGenTypeCache { // we know if a temporary should be destroyed conditionally. ConditionalEvaluation *outermostConditional = nullptr; + // The outermost conditional that a FullExprCleanupScope in a conditional + // branch hides from isInConditionalBranch(), if any. The cleanups of that + // full-expression run at its end, so they are not conditional, but a + // temporary whose lifetime is extended beyond it is still created + // conditionally. + ConditionalEvaluation *hiddenOutermostConditional = nullptr; + /// An RAII object to record that we're evaluating a statement /// expression. class StmtExprEvaluation { @@ -2722,15 +2757,19 @@ class CIRGenFunction : public CIRGenTypeCache { /// statement expression aren't conditional just because the /// StmtExpr is. ConditionalEvaluation *savedOutermostConditional; + ConditionalEvaluation *savedHiddenOutermostConditional; public: StmtExprEvaluation(CIRGenFunction &cgf) - : cgf(cgf), savedOutermostConditional(cgf.outermostConditional) { + : cgf(cgf), savedOutermostConditional(cgf.outermostConditional), + savedHiddenOutermostConditional(cgf.hiddenOutermostConditional) { cgf.outermostConditional = nullptr; + cgf.hiddenOutermostConditional = nullptr; } ~StmtExprEvaluation() { cgf.outermostConditional = savedOutermostConditional; + cgf.hiddenOutermostConditional = savedHiddenOutermostConditional; } }; diff --git a/clang/test/CIR/CodeGen/array-ctor.cpp b/clang/test/CIR/CodeGen/array-ctor.cpp index 0c2e9652bdccb6..872b2dda8e2cf7 100644 --- a/clang/test/CIR/CodeGen/array-ctor.cpp +++ b/clang/test/CIR/CodeGen/array-ctor.cpp @@ -1,10 +1,12 @@ -// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -Wno-unused-value -fclangir -emit-cir -mmlir --mlir-print-ir-before=cir-lowering-prepare %s -o - 2>&1 | FileCheck --check-prefixes=CIR-BEFORE-LPP %s -// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -Wno-unused-value -fclangir -emit-cir %s -o %t.cir -// RUN: FileCheck --input-file=%t.cir %s -check-prefix=CIR -// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -Wno-unused-value -fclangir -emit-llvm %s -o %t-cir.ll -// RUN: FileCheck --input-file=%t-cir.ll %s -check-prefix=LLVM -// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -Wno-unused-value -emit-llvm %s -o %t.ll -// RUN: FileCheck --input-file=%t.ll %s -check-prefix=OGCG +// RUN: %clang_cc1 -std=c++23 -triple x86_64-unknown-linux-gnu -fclangir -fcxx-exceptions -fexceptions -emit-cir %s -o %t.eh.cir +// RUN: FileCheck --input-file=%t.eh.cir %s --check-prefixes=TEMP,TEMP-EH +// RUN: %clang_cc1 -std=c++23 -triple x86_64-unknown-linux-gnu -Wno-unused-value -fclangir -emit-cir -mmlir --mlir-print-ir-before=cir-lowering-prepare %s -o - 2>&1 | FileCheck --check-prefixes=CIR-BEFORE-LPP %s +// RUN: %clang_cc1 -std=c++23 -triple x86_64-unknown-linux-gnu -Wno-unused-value -fclangir -emit-cir %s -o %t.cir +// RUN: FileCheck --input-file=%t.cir %s --check-prefixes=CIR,TEMP,TEMP-NOEH +// RUN: %clang_cc1 -std=c++23 -triple x86_64-unknown-linux-gnu -Wno-unused-value -fclangir -emit-llvm %s -o %t-cir.ll +// RUN: FileCheck --input-file=%t-cir.ll %s --check-prefixes=LLVM,TEMP-IR,TEMP-LLVM +// RUN: %clang_cc1 -std=c++23 -triple x86_64-unknown-linux-gnu -Wno-unused-value -emit-llvm %s -o %t.ll +// RUN: FileCheck --input-file=%t.ll %s --check-prefixes=OGCG,TEMP-IR struct S { S(); @@ -345,3 +347,182 @@ void Temp2InArray() { // OGCG-NEXT: call void @_ZN5Temp2D1Ev({{.*}}[[TMP]]) // OGCG: %[[DTOR_ELT:.*]] = getelementptr inbounds %struct.CausesTemp2, ptr %{{.*}}, i64 -1 // OGCG-NEXT: call void @_ZN11CausesTemp2D1Ev(ptr {{.*}}%[[DTOR_ELT]]) + +// Constructor default arguments are destroyed before the next array element. +// Filler and capture-copy loops have the same boundary. Lifetime-extended +// temporaries retain their enclosing cleanup ownership. + +struct ArgTemp { + ArgTemp(int); + ~ArgTemp(); +}; + +extern "C" bool flag(); +extern "C" int g(const ArgTemp &); +extern "C" void use(int); + +struct E { + E(const ArgTemp & = ArgTemp(1)); + ~E(); +}; + +struct F { + F(int = flag() ? g(ArgTemp(2)) : 0); + ~F(); +}; + +extern "C" int take(F *); + +// TEMP-LABEL: cir.func {{.*}} @new_in_conditional( +// TEMP: %[[TMP:.*]] = cir.alloca "ref.tmp0" +// TEMP: %[[FLAG:.*]] = cir.alloca "cleanup.cond" +// TEMP: cir.ternary( +// TEMP: cir.do { +// TEMP: cir.call @flag() +// TEMP: %[[FALSE:.*]] = cir.const #false +// TEMP-NEXT: cir.store %[[FALSE]], %[[FLAG]] +// TEMP: cir.ternary( +// TEMP: cir.call @_ZN7ArgTempC1Ei(%[[TMP]], +// TEMP-NEXT: %[[TRUE:.*]] = cir.const #true +// TEMP-NEXT: cir.store %[[TRUE]], %[[FLAG]] +// TEMP: cir.call @_ZN1FC1Ei( +// TEMP-NOEH: } cleanup normal { +// TEMP-EH: } cleanup all { +// TEMP: %[[LIVE:.*]] = cir.load {{.*}} %[[FLAG]] +// TEMP-NEXT: cir.if %[[LIVE]] { +// TEMP-NEXT: cir.call @_ZN7ArgTempD1Ev(%[[TMP]]) +// TEMP: } while { +// TEMP: cir.call @take( +// TEMP: cir.call @use( + +// TEMP-IR-LABEL: define {{.*}} void @new_in_conditional( +// TEMP-IR: call void @_ZN7ArgTempC1Ei(ptr {{.*}} %[[TMP:[^,]+]], i32 {{.*}} 2) +// TEMP-IR: call void @_ZN1FC1Ei( +// TEMP-IR: call void @_ZN7ArgTempD1Ev(ptr {{.*}} %[[TMP]]) +// TEMP-IR: call i32 @take( +// TEMP-IR: call void @use( + +extern "C" void new_in_conditional(bool b) { use(b ? take(new F[2]) : 0); } + +struct E3 { + E e[3]; +}; + +extern "C" int take3(const E3 &); + +// TEMP-LABEL: cir.func {{.*}} @filler_in_conditional( +// TEMP: %[[EXPLICIT:.*]] = cir.alloca "ref.tmp1" +// TEMP: cir.ternary( +// TEMP: cir.call @_ZN7ArgTempC1Ei(%[[EXPLICIT]], +// TEMP: cir.do { +// TEMP: cir.call @_ZN7ArgTempC1Ei(%[[TMP:[^,]+]], +// TEMP: cir.cleanup.scope { +// TEMP: cir.call @_ZN1EC1ERK7ArgTemp( +// TEMP-NOEH: } cleanup normal { +// TEMP-EH: } cleanup all { +// TEMP-NEXT: cir.call @_ZN7ArgTempD1Ev(%[[TMP]]) +// TEMP: } while { +// TEMP: cir.call @take3( +// TEMP: cir.call @use( +// TEMP: cir.call @_ZN2E3D1Ev( +// TEMP: cir.call @_ZN7ArgTempD1Ev(%[[EXPLICIT]]) + +// TEMP-IR-LABEL: define {{.*}} void @filler_in_conditional( +// TEMP-IR: call void @_ZN7ArgTempC1Ei(ptr {{.*}} %[[EXPLICIT:[^,]+]], i32 {{.*}} 5) +// TEMP-IR: call void @_ZN7ArgTempC1Ei(ptr {{.*}} %[[TMP:[^,]+]], i32 {{.*}} 1) +// TEMP-IR: call void @_ZN1EC1ERK7ArgTemp( +// TEMP-IR: call void @_ZN7ArgTempD1Ev(ptr {{.*}} %[[TMP]]) +// TEMP-IR: call i32 @take3( +// TEMP-IR: call void @use( +// TEMP-IR: call void @_ZN2E3D1Ev( +// TEMP-IR: call void @_ZN7ArgTempD1Ev(ptr {{.*}} %[[EXPLICIT]]) + +extern "C" void filler_in_conditional(bool b) { use(b ? take3(E3{{E(ArgTemp(5))}}) : 0); } + +struct C { + C(); + C(const C &, const ArgTemp & = ArgTemp(3)); + ~C(); +}; + +// TEMP-LABEL: cir.func {{.*}} @capture( +// TEMP: %[[TMP:.*]] = cir.alloca "ref.tmp0" +// TEMP: cir.do { +// TEMP: cir.call @_ZN7ArgTempC1Ei(%[[TMP]], +// TEMP: cir.cleanup.scope { +// TEMP: cir.call @_ZN1CC1ERKS_RK7ArgTemp( +// TEMP-NOEH: } cleanup normal { +// TEMP-EH: } cleanup all { +// TEMP-NEXT: cir.call @_ZN7ArgTempD1Ev(%[[TMP]]) +// TEMP: } while { + +// TEMP-IR-LABEL: define {{.*}} void @capture( +// TEMP-IR: call void @_ZN7ArgTempC1Ei(ptr {{.*}} %[[TMP:.*]], i32 {{.*}} 3) +// TEMP-IR: call void @_ZN1CC1ERKS_RK7ArgTemp( +// TEMP-IR: call void @_ZN7ArgTempD1Ev(ptr {{.*}} %[[TMP]]) + +extern "C" void capture() { + C arr[2]; + (void)[arr] {}; +} + +struct R { + const ArgTemp &s = ArgTemp(4); + int x; +}; + +extern "C" void use_r(R *); + +// TEMP-LABEL: cir.func {{.*}} @extended_member_filler( +// TEMP: %[[TMP:.*]] = cir.alloca "ref.tmp0" +// TEMP: cir.do { +// TEMP: cir.call @_ZN7ArgTempC1Ei(%[[TMP]], +// TEMP: } while { +// TEMP: cir.call @use_r( +// TEMP-NOEH: } cleanup normal { +// TEMP-EH: } cleanup all { +// TEMP-NEXT: cir.call @_ZN7ArgTempD1Ev(%[[TMP]]) + +// TEMP-LLVM-LABEL: define {{.*}} void @extended_member_filler( +// TEMP-LLVM: call void @_ZN7ArgTempC1Ei(ptr {{.*}} %[[TMP:.*]], i32 {{.*}} 4) +// TEMP-LLVM-NOT: call void @_ZN7ArgTempD1Ev +// TEMP-LLVM: call void @use_r( +// TEMP-LLVM: call void @_ZN7ArgTempD1Ev(ptr {{.*}} %[[TMP]]) + +extern "C" void extended_member_filler() { + R a[2] = {}; + use_r(a); +} + +struct Range { + int *begin(); + int *end(); +}; + +extern "C" Range take_range(E *); + +// TEMP-LABEL: cir.func {{.*}} @extended_default_arg( +// TEMP: %[[TMP:.*]] = cir.alloca "ref.tmp1" +// TEMP: cir.do { +// TEMP: cir.call @_ZN7ArgTempC1Ei(%[[TMP]], +// TEMP: cir.call @_ZN1EC1ERK7ArgTemp( +// TEMP: } while { +// TEMP: cir.call @take_range( +// TEMP: cir.for : cond { +// TEMP: cir.call @use( +// TEMP: } step { +// TEMP-NOEH: } cleanup normal { +// TEMP-EH: } cleanup all { +// TEMP-NEXT: cir.call @_ZN7ArgTempD1Ev(%[[TMP]]) + +// TEMP-LLVM-LABEL: define {{.*}} void @extended_default_arg( +// TEMP-LLVM: call void @_ZN7ArgTempC1Ei(ptr {{.*}} %[[TMP:.*]], i32 {{.*}} 1) +// TEMP-LLVM: call void @take_range( +// TEMP-LLVM-NOT: call void @_ZN7ArgTempD1Ev +// TEMP-LLVM: call void @use( +// TEMP-LLVM: call void @_ZN7ArgTempD1Ev(ptr {{.*}} %[[TMP]]) + +extern "C" void extended_default_arg() { + for (int i : take_range(new E[2])) + use(i); +} diff --git a/clang/test/CIR/CodeGen/cleanup-conditional-eh.cpp b/clang/test/CIR/CodeGen/cleanup-conditional-eh.cpp index 1423f087240d2f..f66dc5872d6d46 100644 --- a/clang/test/CIR/CodeGen/cleanup-conditional-eh.cpp +++ b/clang/test/CIR/CodeGen/cleanup-conditional-eh.cpp @@ -766,3 +766,338 @@ void test_flag_cleared_before_cond_cleanup() { // OGCG: call void @_ZN7PayloadD1Ev(ptr {{.*}} %[[AGG_TMP]]) // OGCG: call void @_ZN5GuardD1Ev(ptr {{.*}} %[[REF_TMP]]) // OGCG: resume { ptr, i32 } + +// The members that an aggregate initialization has constructed so far are +// destroyed when a later member initializer is left early, here by a return +// in a statement expression in an operand of a conditional operator. The +// member cleanups are deactivated at the end of the initialization, while the +// cleanup scope that the conditional operator opened for its operands is still +// open: the flag that guards the cleanup of the first member is cleared inside +// that scope. + +struct Obj { + char c; + Obj(char); + ~Obj(); +}; + +struct Pair { + Obj a; + Obj b; +}; + +Obj copy(const Obj &); +Obj make(bool); + +void struct_ternary(bool b, bool c) { + Pair p{Obj{'a'}, b ? ({ + if (c) + return; + Obj{'b'}; + }) + : Obj{'c'}}; +} + +// The cleanup scope of the conditional operator is empty and canonicalized +// away. +// +// CIR-LABEL: cir.func {{.*}} @_Z14struct_ternarybb( +// CIR: %[[P:.*]] = cir.alloca "p" +// CIR: %[[ACTIVE:.*]] = cir.alloca "cleanup.isactive" +// CIR: %[[A:.*]] = cir.get_member %[[P]][0] {name = "a"} +// CIR: cir.call @_ZN3ObjC1Ec(%[[A]], +// CIR-NEXT: %[[TRUE:.*]] = cir.const #true +// CIR-NEXT: cir.store %[[TRUE]], %[[ACTIVE]] +// CIR-NEXT: cir.cleanup.scope { +// CIR: cir.if +// CIR: cir.if +// CIR-NEXT: cir.return +// CIR: } else { +// CIR: cir.call @_ZN3ObjC1Ec( +// CIR-NEXT: } loc( +// CIR-NEXT: %[[FALSE:.*]] = cir.const #false +// CIR-NEXT: cir.store %[[FALSE]], %[[ACTIVE]] +// CIR-NEXT: cir.yield +// CIR-NEXT: } cleanup all { +// CIR-NEXT: %[[IS_ACTIVE:.*]] = cir.load {{.*}} %[[ACTIVE]] +// CIR-NEXT: cir.if %[[IS_ACTIVE]] { +// CIR-NEXT: cir.call @_ZN3ObjD1Ev(%[[A]]) +// CIR: cir.call @_ZN4PairD1Ev(%[[P]]) + +// A logical operator opens such a scope too. + +void logical_and(bool b, bool c) { + Pair p{Obj{'a'}, make(b && ({ + if (c) + return; + true; + }))}; +} + +// CIR-LABEL: cir.func {{.*}} @_Z11logical_andbb( +// CIR: %[[P:.*]] = cir.alloca "p" +// CIR: %[[ACTIVE:.*]] = cir.alloca "cleanup.isactive" +// CIR: %[[A:.*]] = cir.get_member %[[P]][0] {name = "a"} +// CIR: cir.store %{{.*}}, %[[ACTIVE]] +// CIR-NEXT: cir.cleanup.scope { +// CIR: cir.ternary +// CIR: cir.return +// CIR: cir.call @_Z4makeb( +// CIR-NEXT: %[[FALSE:.*]] = cir.const #false +// CIR-NEXT: cir.store %[[FALSE]], %[[ACTIVE]] +// CIR-NEXT: cir.yield +// CIR-NEXT: } cleanup all { +// CIR-NEXT: %[[IS_ACTIVE:.*]] = cir.load {{.*}} %[[ACTIVE]] +// CIR-NEXT: cir.if %[[IS_ACTIVE]] { +// CIR-NEXT: cir.call @_ZN3ObjD1Ev(%[[A]]) + +// The cleanup of an initialized member or init-capture, or the operator delete +// or partial-array cleanup of a new-expression, is deactivated once the object +// is initialized. +// If an operand of a later ?:, && or || in the same initializer created a +// temporary, the cleanup scope for that temporary is open in the body of the +// deactivated cleanup's scope. It has to stay open until the end of the +// full-expression, so the deactivated cleanup gets an active flag instead of +// ending its scope early. + +int h(const Obj &); +int g(); +void use(const Pair &, int); + +void aggregate(bool b) { + use(Pair{Obj{'a'}, b ? copy(Obj{'t'}) : Obj{'c'}}, g()); +} + +// CIR-LABEL: cir.func {{.*}} @_Z9aggregateb( +// CIR: %[[PAIR:.*]] = cir.alloca "ref.tmp0" {{.*}} : !cir.ptr<!rec_Pair> +// CIR: %[[TMP:.*]] = cir.alloca "ref.tmp1" {{.*}} : !cir.ptr<!rec_Obj> +// CIR: %[[ACTIVE:.*]] = cir.alloca "cleanup.isactive" {{.*}} : !cir.ptr<!cir.bool> +// CIR: %[[A:.*]] = cir.get_member %[[PAIR]][0] {name = "a"} +// CIR: cir.call @_ZN3ObjC1Ec(%[[A]], %{{.*}}) +// CIR-NEXT: %[[TRUE:.*]] = cir.const #true +// CIR-NEXT: cir.store %[[TRUE]], %[[ACTIVE]] : !cir.bool, !cir.ptr<!cir.bool> +// CIR-NEXT: cir.cleanup.scope { +// CIR: cir.cleanup.scope { +// CIR: cir.if %{{.*}} { +// CIR: } else { +// CIR-NEXT: %[[C:.*]] = cir.const #cir.int<99> : !s8i +// CIR-NEXT: cir.call @_ZN3ObjC1Ec(%{{.*}}, %[[C]]){{.*}} +// CIR-NEXT: } +// CIR-NEXT: %[[FALSE:.*]] = cir.const #false +// CIR-NEXT: cir.store %[[FALSE]], %[[ACTIVE]] : !cir.bool, !cir.ptr<!cir.bool> +// CIR-NEXT: cir.cleanup.scope { +// CIR-NEXT: %[[G:.*]] = cir.call @_Z1gv() +// CIR-NEXT: cir.call @_Z3useRK4Pairi(%[[PAIR]], %[[G]]) +// CIR-NEXT: cir.yield +// CIR-NEXT: } cleanup all { +// CIR-NEXT: cir.call @_ZN4PairD1Ev(%[[PAIR]]) +// CIR-NEXT: cir.yield +// CIR-NEXT: } +// CIR-NEXT: cir.yield +// CIR-NEXT: } cleanup all { +// CIR-NEXT: %[[IS_COND:.*]] = cir.load +// CIR-NEXT: cir.if %[[IS_COND]] { +// CIR-NEXT: cir.call @_ZN3ObjD1Ev(%[[TMP]]){{.*}} +// CIR-NEXT: } +// CIR-NEXT: cir.yield +// CIR-NEXT: } +// CIR-NEXT: cir.yield +// CIR-NEXT: } cleanup all { +// CIR-NEXT: %[[IS_ACTIVE:.*]] = cir.load {{.*}} %[[ACTIVE]] : !cir.ptr<!cir.bool>, !cir.bool +// CIR-NEXT: cir.if %[[IS_ACTIVE]] { +// CIR-NEXT: cir.call @_ZN3ObjD1Ev(%[[A]]) + +// LLVM-LABEL: define {{.*}} void @_Z9aggregateb( +// LLVM: call void @_ZN3ObjC1Ec(ptr {{.*}} %[[A:.*]], i8 {{.*}} 97) +// LLVM: invoke void @_ZN3ObjC1Ec(ptr {{.*}} %[[TMP:.*]], i8 {{.*}} 116) +// LLVM: invoke void @_Z4copyRK3Obj( +// LLVM: invoke {{.*}} i32 @_Z1gv() +// LLVM: invoke void @_Z3useRK4Pairi( +// LLVM: call void @_ZN4PairD1Ev( +// LLVM: call void @_ZN3ObjD1Ev(ptr {{.*}} %[[TMP]]) + +// OGCG-LABEL: define {{.*}} void @_Z9aggregateb( +// OGCG: call void @_ZN3ObjC1Ec(ptr {{.*}} %[[A:.*]], i8 {{.*}} 97) +// OGCG: invoke void @_ZN3ObjC1Ec(ptr {{.*}} %[[TMP:.*]], i8 {{.*}} 116) +// OGCG: invoke void @_Z4copyRK3Obj( +// OGCG: invoke {{.*}} i32 @_Z1gv() +// OGCG: invoke void @_Z3useRK4Pairi( +// OGCG: call void @_ZN4PairD1Ev( +// OGCG: call void @_ZN3ObjD1Ev(ptr {{.*}} %[[TMP]]) + +struct Agg { + Obj o; + int i; +}; + +void use_array(Agg *, int); + +void array_new(bool b) { + use_array(new Agg[2]{{Obj{'a'}, b ? h(Obj{'t'}) : 0}, {Obj{'c'}, 2}}, g()); +} + +// The member of the first element is deactivated with a flag, which keeps it +// on the EH stack above the partial-array cleanup until the full-expression +// ends. +// CIR-LABEL: cir.func {{.*}} @_Z9array_newb( +// CIR: %[[RESULT:.*]] = cir.alloca "__new_result" +// CIR: %[[TMP:.*]] = cir.alloca "ref.tmp0" {{.*}} : !cir.ptr<!rec_Obj> +// CIR: %[[COND:.*]] = cir.alloca "cleanup.cond" {{.*}} : !cir.ptr<!cir.bool> +// CIR: %[[ACTIVE:.*]] = cir.alloca "cleanup.isactive" {{.*}} : !cir.ptr<!cir.bool> +// CIR: %[[ARRAY:.*]] = cir.cast bitcast %{{.*}} : !cir.ptr<!void> -> !cir.ptr<!rec_Agg> +// CIR: %[[A:.*]] = cir.get_member %[[ARRAY]][0] {name = "o"} +// CIR: cir.call @_ZN3ObjC1Ec(%[[A]], %{{.*}}) +// CIR-NEXT: %[[TRUE:.*]] = cir.const #true +// CIR-NEXT: cir.store %[[TRUE]], %[[ACTIVE]] : !cir.bool, !cir.ptr<!cir.bool> +// CIR-NEXT: cir.cleanup.scope { +// CIR: cir.cleanup.scope { +// CIR: cir.ternary +// CIR: cir.call @_ZN3ObjC1Ec(%[[TMP]], %{{.*}}) +// CIR: %[[FALSE:.*]] = cir.const #false +// CIR-NEXT: cir.store %[[FALSE]], %[[ACTIVE]] : !cir.bool, !cir.ptr<!cir.bool> +// CIR: %[[C:.*]] = cir.const #cir.int<99> : !s8i +// CIR-NEXT: cir.call @_ZN3ObjC1Ec(%{{.*}}, %[[C]]) +// CIR: %[[NEW_ARRAY:.*]] = cir.load {{.*}} %[[RESULT]] +// CIR-NEXT: %[[G:.*]] = cir.call @_Z1gv() +// CIR-NEXT: cir.call @_Z9use_arrayP3Aggi(%[[NEW_ARRAY]], %[[G]]) +// CIR-NEXT: cir.yield +// CIR-NEXT: } cleanup all { +// CIR-NEXT: %[[IS_COND:.*]] = cir.load {{.*}} %[[COND]] : !cir.ptr<!cir.bool>, !cir.bool +// CIR-NEXT: cir.if %[[IS_COND]] { +// CIR-NEXT: cir.call @_ZN3ObjD1Ev(%[[TMP]]){{.*}} +// CIR-NEXT: } +// CIR-NEXT: cir.yield +// CIR-NEXT: } +// CIR-NEXT: cir.yield +// CIR-NEXT: } cleanup all { +// CIR-NEXT: %[[IS_ACTIVE:.*]] = cir.load {{.*}} %[[ACTIVE]] : !cir.ptr<!cir.bool>, !cir.bool +// CIR-NEXT: cir.if %[[IS_ACTIVE]] { +// CIR-NEXT: cir.call @_ZN3ObjD1Ev(%[[A]]){{.*}} + +// LLVM-LABEL: define {{.*}} void @_Z9array_newb( +// LLVM: invoke void @_ZN3ObjC1Ec(ptr {{.*}} %[[TMP:.*]], i8 {{.*}} 116) +// LLVM: invoke void @_ZN3ObjC1Ec(ptr {{.*}}, i8 {{.*}} 99) +// LLVM: invoke {{.*}} i32 @_Z1gv() +// LLVM: invoke void @_Z9use_arrayP3Aggi( +// LLVM: call void @_ZN3ObjD1Ev(ptr {{.*}} %[[TMP]]) + +// OGCG-LABEL: define {{.*}} void @_Z9array_newb( +// OGCG: invoke void @_ZN3ObjC1Ec(ptr {{.*}} %[[TMP:.*]], i8 {{.*}} 116) +// OGCG: invoke void @_ZN3ObjC1Ec(ptr {{.*}}, i8 {{.*}} 99) +// OGCG: invoke {{.*}} i32 @_Z1gv() +// OGCG: invoke void @_Z9use_arrayP3Aggi( +// OGCG: call void @_ZN3ObjD1Ev(ptr {{.*}} %[[TMP]]) + +struct Foo { + Foo(int); +}; + +void use_new(Foo *, int); + +void new_expr(bool b) { use_new(new Foo(b ? h(Obj{'x'}) : 0), g()); } + +// Without exceptions there is no operator delete cleanup to deactivate. +// CIR-LABEL: cir.func {{.*}} @_Z8new_exprb( +// CIR: %[[RESULT:.*]] = cir.alloca "__new_result" +// CIR: %[[TMP:.*]] = cir.alloca "ref.tmp0" {{.*}} : !cir.ptr<!rec_Obj> +// CIR: %[[ACTIVE:.*]] = cir.alloca "cleanup.isactive" {{.*}} : !cir.ptr<!cir.bool> +// CIR: %[[PTR:.*]] = cir.call @_Znwm( +// CIR-NEXT: cir.cleanup.scope { +// CIR-NEXT: %[[TRUE:.*]] = cir.const #true +// CIR-NEXT: cir.store %[[TRUE]], %[[ACTIVE]] : !cir.bool, !cir.ptr<!cir.bool> +// CIR: cir.cleanup.scope { +// CIR: cir.ternary +// CIR: cir.call @_ZN3FooC1Ei( +// CIR-NEXT: %[[FALSE:.*]] = cir.const #false +// CIR-NEXT: cir.store %[[FALSE]], %[[ACTIVE]] : !cir.bool, !cir.ptr<!cir.bool> +// CIR-NEXT: %[[FOO:.*]] = cir.load {{.*}} %[[RESULT]] +// CIR-NEXT: %[[G:.*]] = cir.call @_Z1gv() +// CIR-NEXT: cir.call @_Z7use_newP3Fooi(%[[FOO]], %[[G]]) +// CIR-NEXT: cir.yield +// CIR-NEXT: } cleanup all { +// CIR-NEXT: %[[IS_COND:.*]] = cir.load +// CIR-NEXT: cir.if %[[IS_COND]] { +// CIR-NEXT: cir.call @_ZN3ObjD1Ev(%[[TMP]]){{.*}} +// CIR-NEXT: } +// CIR-NEXT: cir.yield +// CIR-NEXT: } +// CIR-NEXT: cir.yield +// CIR-NEXT: } cleanup eh { +// CIR-NEXT: %[[IS_ACTIVE:.*]] = cir.load {{.*}} %[[ACTIVE]] : !cir.ptr<!cir.bool>, !cir.bool +// CIR-NEXT: cir.if %[[IS_ACTIVE]] { +// CIR-NEXT: cir.call @_ZdlPvm(%[[PTR]], %{{.*}}) + +// LLVM-LABEL: define {{.*}} void @_Z8new_exprb( +// LLVM: invoke void @_ZN3ObjC1Ec(ptr {{.*}} %[[TMP:.*]], i8 {{.*}} 120) +// LLVM: invoke void @_ZN3FooC1Ei( +// LLVM: invoke {{.*}} i32 @_Z1gv() +// LLVM: invoke void @_Z7use_newP3Fooi( +// LLVM: call void @_ZN3ObjD1Ev(ptr {{.*}} %[[TMP]]) + +// OGCG-LABEL: define {{.*}} void @_Z8new_exprb( +// OGCG: invoke void @_ZN3ObjC1Ec(ptr {{.*}} %[[TMP:.*]], i8 {{.*}} 120) +// OGCG: invoke void @_ZN3FooC1Ei( +// OGCG: invoke {{.*}} i32 @_Z1gv() +// OGCG: invoke void @_Z7use_newP3Fooi( +// OGCG: call void @_ZN3ObjD1Ev(ptr {{.*}} %[[TMP]]) + +void use_int(int, int); + +void init_capture(bool b) { + use_int([o = Obj{'a'}, i = b ? h(Obj{'t'}) : 0] { return i; }(), g()); +} + +// CIR-LABEL: cir.func {{.*}} @_Z12init_captureb( +// CIR: %[[LAMBDA:.*]] = cir.alloca "ref.tmp0" +// CIR: %[[TMP:.*]] = cir.alloca "ref.tmp1" {{.*}} : !cir.ptr<!rec_Obj> +// CIR: %[[ACTIVE:.*]] = cir.alloca "cleanup.isactive" {{.*}} : !cir.ptr<!cir.bool> +// CIR: %[[O:.*]] = cir.get_member %[[LAMBDA]][0] {name = "o"} +// CIR: cir.call @_ZN3ObjC1Ec(%[[O]], %{{.*}}) +// CIR-NEXT: %[[TRUE:.*]] = cir.const #true +// CIR-NEXT: cir.store %[[TRUE]], %[[ACTIVE]] : !cir.bool, !cir.ptr<!cir.bool> +// CIR-NEXT: cir.cleanup.scope { +// CIR: cir.cleanup.scope { +// CIR: cir.ternary +// CIR: cir.store {{.*}} : !s32i, !cir.ptr<!s32i> +// CIR-NEXT: %[[FALSE:.*]] = cir.const #false +// CIR-NEXT: cir.store %[[FALSE]], %[[ACTIVE]] : !cir.bool, !cir.ptr<!cir.bool> +// CIR-NEXT: cir.cleanup.scope { +// CIR-NEXT: %[[I:.*]] = cir.call @_ZZ12init_capturebENK3$_{{[0-9]+}}clEv(%[[LAMBDA]]) +// CIR-NEXT: %[[G:.*]] = cir.call @_Z1gv() +// CIR-NEXT: cir.call @_Z7use_intii(%[[I]], %[[G]]) +// CIR-NEXT: cir.yield +// CIR-NEXT: } cleanup all { +// CIR-NEXT: cir.call @_ZZ12init_capturebEN3$_{{[0-9]+}}D1Ev(%[[LAMBDA]]) +// CIR-NEXT: cir.yield +// CIR-NEXT: } +// CIR-NEXT: cir.yield +// CIR-NEXT: } cleanup all { +// CIR-NEXT: %[[IS_COND:.*]] = cir.load +// CIR-NEXT: cir.if %[[IS_COND]] { +// CIR-NEXT: cir.call @_ZN3ObjD1Ev(%[[TMP]]){{.*}} +// CIR-NEXT: } +// CIR-NEXT: cir.yield +// CIR-NEXT: } +// CIR-NEXT: cir.yield +// CIR-NEXT: } cleanup all { +// CIR-NEXT: %[[IS_ACTIVE:.*]] = cir.load {{.*}} %[[ACTIVE]] : !cir.ptr<!cir.bool>, !cir.bool +// CIR-NEXT: cir.if %[[IS_ACTIVE]] { +// CIR-NEXT: cir.call @_ZN3ObjD1Ev(%[[O]]) + +// LLVM-LABEL: define {{.*}} void @_Z12init_captureb( +// LLVM: invoke void @_ZN3ObjC1Ec(ptr {{.*}} %[[TMP:.*]], i8 {{.*}} 116) +// LLVM: invoke {{.*}} i32 @"_ZZ12init_capturebENK3$_{{[0-9]+}}clEv"( +// LLVM: invoke {{.*}} i32 @_Z1gv() +// LLVM: invoke void @_Z7use_intii( +// LLVM: call void @"_ZZ12init_capturebEN3$_{{[0-9]+}}D1Ev"( +// LLVM: call void @_ZN3ObjD1Ev(ptr {{.*}} %[[TMP]]) +// LLVM-LABEL: define internal {{.*}} @"_ZZ12init_capturebENK3$_{{[0-9]+}}clEv"( + +// OGCG-LABEL: define {{.*}} void @_Z12init_captureb( +// OGCG: invoke void @_ZN3ObjC1Ec(ptr {{.*}} %[[TMP:.*]], i8 {{.*}} 116) +// OGCG: invoke {{.*}} i32 @"_ZZ12init_capturebENK3$_{{[0-9]+}}clEv"( +// OGCG: invoke {{.*}} i32 @_Z1gv() +// OGCG: invoke void @_Z7use_intii( +// OGCG: call void @"_ZZ12init_capturebEN3$_{{[0-9]+}}D1Ev"( +// OGCG: call void @_ZN3ObjD1Ev(ptr {{.*}} %[[TMP]]) +// OGCG-LABEL: define internal {{.*}} @"_ZZ12init_capturebENK3$_{{[0-9]+}}clEv"( diff --git a/clang/test/CIR/CodeGen/cleanup-conditional-with-wrapper.cpp b/clang/test/CIR/CodeGen/cleanup-conditional-with-wrapper.cpp index 1d3ac68bc5a8fa..0dd8c4e517f6a6 100644 --- a/clang/test/CIR/CodeGen/cleanup-conditional-with-wrapper.cpp +++ b/clang/test/CIR/CodeGen/cleanup-conditional-with-wrapper.cpp @@ -1,9 +1,15 @@ -// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir -// RUN: FileCheck --input-file=%t.cir %s --check-prefix=CIR -// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t-cir.ll -// RUN: FileCheck --input-file=%t-cir.ll %s --check-prefix=LLVM -// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll -// RUN: FileCheck --input-file=%t.ll %s --check-prefix=OGCG +// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fclangir -fcxx-exceptions -fexceptions -emit-cir %s -o %t.eh.cir +// RUN: FileCheck --input-file=%t.eh.cir %s --check-prefix=DEACT +// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fclangir -fcxx-exceptions -fexceptions -emit-llvm %s -o %t.eh.ll +// RUN: FileCheck --input-file=%t.eh.ll %s --check-prefix=DEACT-IR +// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fcxx-exceptions -fexceptions -emit-llvm %s -o %t.ogcg.eh.ll +// RUN: FileCheck --input-file=%t.ogcg.eh.ll %s --check-prefix=DEACT-IR +// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir +// RUN: FileCheck --input-file=%t.cir %s --check-prefixes=CIR,DEACT +// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t-cir.ll +// RUN: FileCheck --input-file=%t-cir.ll %s --check-prefixes=LLVM,DEACT-IR +// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll +// RUN: FileCheck --input-file=%t.ll %s --check-prefixes=OGCG,DEACT-IR struct Base { ~Base(); }; @@ -349,3 +355,46 @@ void makeEntry() { // OGCG: [[CLEANUP_DONE]]: // OGCG: call void @_ZN4IterD1Ev(ptr {{.*}} %[[REF_TMP]]) // OGCG: ret void + +// A non-conditional array initializer leaves its temporary alive until the +// enclosing full-expression ends. + +struct Obj { + char c; + Obj(char); + ~Obj(); +}; + +extern "C" int h(const Obj &); +extern "C" int g(); + +struct Agg { + Obj o; + int i; +}; + +extern "C" void use_array(Agg *, int); + +// DEACT-LABEL: cir.func {{.*}} @array_new_temporary( +// DEACT: %[[TMP:.*]] = cir.alloca "ref.tmp0" +// DEACT: cir.call @_ZN3ObjC1Ec( +// DEACT-NEXT: %[[TRUE:.*]] = cir.const #true +// DEACT-NEXT: cir.store %[[TRUE]], %[[ACTIVE:.*]] +// DEACT: cir.call @h(%[[TMP]]) +// DEACT: %[[FALSE:.*]] = cir.const #false +// DEACT-NEXT: cir.store %[[FALSE]], %[[ACTIVE]] +// DEACT-NOT: cir.call @_ZN3ObjD1Ev(%[[TMP]]) +// DEACT: cir.call @g() +// DEACT-NEXT: cir.call @use_array( +// DEACT: cir.call @_ZN3ObjD1Ev(%[[TMP]]) + +// DEACT-IR-LABEL: define {{.*}} void @array_new_temporary( +// DEACT-IR: {{(call|invoke)}} void @_ZN3ObjC1Ec(ptr {{.*}} %[[TMP:.*]], i8 {{.*}} 116) +// DEACT-IR-NOT: call void @_ZN3ObjD1Ev(ptr {{.*}} %[[TMP]]) +// DEACT-IR: {{(call|invoke)}} i32 @g() +// DEACT-IR: {{(call|invoke)}} void @use_array( +// DEACT-IR: call void @_ZN3ObjD1Ev(ptr {{.*}} %[[TMP]]) + +extern "C" void array_new_temporary() { + use_array(new Agg[2]{{Obj{'a'}, h(Obj{'t'})}, {Obj{'c'}, 2}}, g()); +} diff --git a/clang/test/CIR/CodeGen/cleanup-conditional.cpp b/clang/test/CIR/CodeGen/cleanup-conditional.cpp index 1f86f1d1cd4831..b9df861d17e4f3 100644 --- a/clang/test/CIR/CodeGen/cleanup-conditional.cpp +++ b/clang/test/CIR/CodeGen/cleanup-conditional.cpp @@ -1356,3 +1356,284 @@ void lambda_init_capture() { // OGCG: [[DTOR]]: // OGCG-NEXT: call void @_ZN1SD1Ev(ptr {{.*}} %[[TMP]]) // OGCG: call void @"_ZZ19lambda_init_capturevENK3$_0clEv"( + +// The members that an aggregate initialization has constructed so far are +// destroyed when a later member initializer is left early, here by a return +// in a statement expression in an operand of a conditional operator. The +// member cleanups are deactivated at the end of the initialization, while the +// cleanup scope that the conditional operator opened for its operands is still +// open: the flag that guards the cleanup of the first member is cleared inside +// that scope. + +struct Obj { + char c; + Obj(char); + ~Obj(); +}; + +struct Pair { + Obj a; + Obj b; +}; + +Obj copy(const Obj &); +Obj make(bool); + +void struct_ternary(bool b, bool c) { + Pair p{Obj{'a'}, b ? ({ + if (c) + return; + Obj{'b'}; + }) + : Obj{'c'}}; +} + +// The cleanup scope of the conditional operator is empty and canonicalized +// away. +// +// CIR-LABEL: cir.func {{.*}} @_Z14struct_ternarybb( +// CIR: %[[P:.*]] = cir.alloca "p" +// CIR: %[[ACTIVE:.*]] = cir.alloca "cleanup.isactive" +// CIR: %[[A:.*]] = cir.get_member %[[P]][0] {name = "a"} +// CIR: cir.call @_ZN3ObjC1Ec(%[[A]], +// CIR-NEXT: %[[TRUE:.*]] = cir.const #true +// CIR-NEXT: cir.store %[[TRUE]], %[[ACTIVE]] +// CIR-NEXT: cir.cleanup.scope { +// CIR: cir.if +// CIR: cir.if +// CIR-NEXT: cir.return +// CIR: } else { +// CIR: cir.call @_ZN3ObjC1Ec( +// CIR-NEXT: } loc( +// CIR-NEXT: %[[FALSE:.*]] = cir.const #false +// CIR-NEXT: cir.store %[[FALSE]], %[[ACTIVE]] +// CIR-NEXT: cir.yield +// CIR-NEXT: } cleanup normal { +// CIR-NEXT: %[[IS_ACTIVE:.*]] = cir.load {{.*}} %[[ACTIVE]] +// CIR-NEXT: cir.if %[[IS_ACTIVE]] { +// CIR-NEXT: cir.call @_ZN3ObjD1Ev(%[[A]]) +// CIR: cir.call @_ZN4PairD1Ev(%[[P]]) + +// The return leaves through the cleanup, which destroys the first member. +// Completing the initialization clears the flag. +// +// LLVMCIR-LABEL: define dso_local void @_Z14struct_ternarybb( +// LLVMCIR: %[[A:.*]] = getelementptr inbounds nuw %struct.Pair, ptr %{{.*}}, i32 0, i32 0 +// LLVMCIR-NEXT: call void @_ZN3ObjC1Ec(ptr {{.*}} %[[A]], i8 noundef signext 97) +// LLVMCIR-NEXT: store i8 1, ptr %[[ACTIVE:[0-9]+]] +// LLVMCIR: store i32 1, ptr %[[SLOT:[0-9]+]] +// LLVMCIR-NEXT: br label %[[CLEANUP:[0-9]+]] +// LLVMCIR: call void @_ZN3ObjC1Ec(ptr {{.*}}, i8 noundef signext 98) +// LLVMCIR: call void @_ZN3ObjC1Ec(ptr {{.*}}, i8 noundef signext 99) +// LLVMCIR-NEXT: br label %[[END:[0-9]+]] +// LLVMCIR: [[END]]: +// LLVMCIR-NEXT: store i8 0, ptr %[[ACTIVE]] +// LLVMCIR-NEXT: store i32 0, ptr %[[SLOT]] +// LLVMCIR-NEXT: br label %[[CLEANUP]] +// LLVMCIR: [[CLEANUP]]: +// LLVMCIR-NEXT: %[[FLAG:.*]] = load i8, ptr %[[ACTIVE]] +// LLVMCIR-NEXT: %[[IS_ACTIVE:.*]] = trunc i8 %[[FLAG]] to i1 +// LLVMCIR-NEXT: br i1 %[[IS_ACTIVE]], label %[[DESTROY:[0-9]+]], label %{{.*}} +// LLVMCIR: [[DESTROY]]: +// LLVMCIR-NEXT: call void @_ZN3ObjD1Ev(ptr {{.*}} %[[A]]) +// LLVMCIR: call void @_ZN4PairD1Ev( +// LLVMCIR: ret void + +// OGCG-LABEL: define dso_local void @_Z14struct_ternarybb( +// OGCG: %[[A:.*]] = getelementptr inbounds nuw %struct.Pair, ptr %p, i32 0, i32 0 +// OGCG: if.then: +// OGCG-NEXT: call void @_ZN3ObjD1Ev(ptr {{.*}} %[[A]]) +// OGCG-NEXT: br label %return +// OGCG: cond.end: +// OGCG-NEXT: call void @_ZN4PairD1Ev( + +// A logical operator opens such a scope too. + +void logical_and(bool b, bool c) { + Pair p{Obj{'a'}, make(b && ({ + if (c) + return; + true; + }))}; +} + +// CIR-LABEL: cir.func {{.*}} @_Z11logical_andbb( +// CIR: %[[P:.*]] = cir.alloca "p" +// CIR: %[[ACTIVE:.*]] = cir.alloca "cleanup.isactive" +// CIR: %[[A:.*]] = cir.get_member %[[P]][0] {name = "a"} +// CIR: cir.store %{{.*}}, %[[ACTIVE]] +// CIR-NEXT: cir.cleanup.scope { +// CIR: cir.ternary +// CIR: cir.return +// CIR: cir.call @_Z4makeb( +// CIR-NEXT: %[[FALSE:.*]] = cir.const #false +// CIR-NEXT: cir.store %[[FALSE]], %[[ACTIVE]] +// CIR-NEXT: cir.yield +// CIR-NEXT: } cleanup normal { +// CIR-NEXT: %[[IS_ACTIVE:.*]] = cir.load {{.*}} %[[ACTIVE]] +// CIR-NEXT: cir.if %[[IS_ACTIVE]] { +// CIR-NEXT: cir.call @_ZN3ObjD1Ev(%[[A]]) + +// LLVMCIR-LABEL: define dso_local void @_Z11logical_andbb( +// LLVMCIR: call void @_Z4makeb( +// LLVMCIR-NEXT: store i8 0, ptr %[[ACTIVE:[0-9]+]] +// LLVMCIR: load i8, ptr %[[ACTIVE]] +// LLVMCIR: call void @_ZN3ObjD1Ev( + +// OGCG-LABEL: define dso_local void @_Z11logical_andbb( +// OGCG: if.then: +// OGCG-NEXT: call void @_ZN3ObjD1Ev( + +// The cleanup of an initialized member or init-capture, or the operator delete +// or partial-array cleanup of a new-expression, is deactivated once the object +// is initialized. +// If an operand of a later ?:, && or || in the same initializer created a +// temporary, the cleanup scope for that temporary is open in the body of the +// deactivated cleanup's scope. It has to stay open until the end of the +// full-expression, so the deactivated cleanup gets an active flag instead of +// ending its scope early. + +int h(const Obj &); +int g(); +void use(const Pair &, int); + +void aggregate(bool b) { + use(Pair{Obj{'a'}, b ? copy(Obj{'t'}) : Obj{'c'}}, g()); +} + +// CIR-LABEL: cir.func {{.*}} @_Z9aggregateb( +// CIR: %[[PAIR:.*]] = cir.alloca "ref.tmp0" {{.*}} : !cir.ptr<!rec_Pair> +// CIR: %[[TMP:.*]] = cir.alloca "ref.tmp1" {{.*}} : !cir.ptr<!rec_Obj> +// CIR: %[[COND:.*]] = cir.alloca "cleanup.cond" {{.*}} : !cir.ptr<!cir.bool> +// CIR: %[[ACTIVE:.*]] = cir.alloca "cleanup.isactive" {{.*}} : !cir.ptr<!cir.bool> +// CIR: %[[A:.*]] = cir.get_member %[[PAIR]][0] {name = "a"} +// CIR: cir.call @_ZN3ObjC1Ec(%[[A]], %{{.*}}) +// CIR-NEXT: %[[TRUE:.*]] = cir.const #true +// CIR-NEXT: cir.store %[[TRUE]], %[[ACTIVE]] : !cir.bool, !cir.ptr<!cir.bool> +// CIR-NEXT: cir.cleanup.scope { +// CIR: cir.cleanup.scope { +// CIR: cir.if %{{.*}} { +// CIR: cir.call @_ZN3ObjC1Ec(%[[TMP]], %{{.*}}) +// CIR: cir.call @_Z4copyRK3Obj(%{{.*}}, %[[TMP]]) +// CIR: } else { +// CIR-NEXT: %[[C:.*]] = cir.const #cir.int<99> : !s8i +// CIR-NEXT: cir.call @_ZN3ObjC1Ec(%{{.*}}, %[[C]]){{.*}} +// CIR-NEXT: } +// CIR-NEXT: %[[FALSE:.*]] = cir.const #false +// CIR-NEXT: cir.store %[[FALSE]], %[[ACTIVE]] : !cir.bool, !cir.ptr<!cir.bool> +// CIR-NEXT: cir.cleanup.scope { +// CIR-NEXT: %[[G:.*]] = cir.call @_Z1gv() +// CIR-NEXT: cir.call @_Z3useRK4Pairi(%[[PAIR]], %[[G]]) +// CIR-NEXT: cir.yield +// CIR-NEXT: } cleanup normal { +// CIR-NEXT: cir.call @_ZN4PairD1Ev(%[[PAIR]]) +// CIR-NEXT: cir.yield +// CIR-NEXT: } +// CIR-NEXT: cir.yield +// CIR-NEXT: } cleanup normal { +// CIR-NEXT: %[[IS_COND:.*]] = cir.load {{.*}} %[[COND]] : !cir.ptr<!cir.bool>, !cir.bool +// CIR-NEXT: cir.if %[[IS_COND]] { +// CIR-NEXT: cir.call @_ZN3ObjD1Ev(%[[TMP]]){{.*}} +// CIR-NEXT: } +// CIR-NEXT: cir.yield +// CIR-NEXT: } +// CIR-NEXT: cir.yield +// CIR-NEXT: } cleanup normal { +// CIR-NEXT: %[[IS_ACTIVE:.*]] = cir.load {{.*}} %[[ACTIVE]] : !cir.ptr<!cir.bool>, !cir.bool +// CIR-NEXT: cir.if %[[IS_ACTIVE]] { +// CIR-NEXT: cir.call @_ZN3ObjD1Ev(%[[A]]){{.*}} +// CIR-NEXT: } +// CIR-NEXT: cir.yield +// CIR-NEXT: } +// CIR-NEXT: cir.return + +struct Agg { + Obj o; + int i; +}; + +void use_array(Agg *, int); + +void array_new(bool b) { + use_array(new Agg[2]{{Obj{'a'}, b ? h(Obj{'t'}) : 0}, {Obj{'c'}, 2}}, g()); +} + +// The member of the first element is deactivated with a flag, which keeps it +// on the EH stack above the partial-array cleanup. Without exceptions, that +// cleanup is EH-only and therefore neither a normal nor an EH cleanup, so its +// deactivation needs no flag. +// CIR-LABEL: cir.func {{.*}} @_Z9array_newb( +// CIR: %[[TMP:.*]] = cir.alloca "ref.tmp0" {{.*}} : !cir.ptr<!rec_Obj> +// CIR: %[[COND:.*]] = cir.alloca "cleanup.cond" {{.*}} : !cir.ptr<!cir.bool> +// CIR: %[[ACTIVE:.*]] = cir.alloca "cleanup.isactive" {{.*}} : !cir.ptr<!cir.bool> +// CIR: %[[ARRAY:.*]] = cir.cast bitcast %{{.*}} : !cir.ptr<!void> -> !cir.ptr<!rec_Agg> +// CIR: %[[A:.*]] = cir.get_member %[[ARRAY]][0] {name = "o"} +// CIR: cir.call @_ZN3ObjC1Ec(%[[A]], %{{.*}}) +// CIR-NEXT: %[[TRUE:.*]] = cir.const #true +// CIR-NEXT: cir.store %[[TRUE]], %[[ACTIVE]] : !cir.bool, !cir.ptr<!cir.bool> +// CIR-NEXT: cir.cleanup.scope { +// CIR: cir.cleanup.scope { +// CIR: cir.ternary +// CIR: cir.call @_ZN3ObjC1Ec(%[[TMP]], %{{.*}}) +// CIR: %[[FALSE:.*]] = cir.const #false +// CIR-NEXT: cir.store %[[FALSE]], %[[ACTIVE]] : !cir.bool, !cir.ptr<!cir.bool> +// CIR: %[[C:.*]] = cir.const #cir.int<99> : !s8i +// CIR-NEXT: cir.call @_ZN3ObjC1Ec(%{{.*}}, %[[C]]) +// CIR: %[[G:.*]] = cir.call @_Z1gv() +// CIR-NEXT: cir.call @_Z9use_arrayP3Aggi(%[[ARRAY]], %[[G]]) +// CIR-NEXT: cir.yield +// CIR-NEXT: } cleanup normal { +// CIR-NEXT: %[[IS_COND:.*]] = cir.load {{.*}} %[[COND]] : !cir.ptr<!cir.bool>, !cir.bool +// CIR-NEXT: cir.if %[[IS_COND]] { +// CIR-NEXT: cir.call @_ZN3ObjD1Ev(%[[TMP]]){{.*}} +// CIR-NEXT: } +// CIR-NEXT: cir.yield +// CIR-NEXT: } +// CIR-NEXT: cir.yield +// CIR-NEXT: } cleanup normal { +// CIR-NEXT: %[[IS_ACTIVE:.*]] = cir.load {{.*}} %[[ACTIVE]] : !cir.ptr<!cir.bool>, !cir.bool +// CIR-NEXT: cir.if %[[IS_ACTIVE]] { +// CIR-NEXT: cir.call @_ZN3ObjD1Ev(%[[A]]){{.*}} + +void use_int(int, int); + +void init_capture(bool b) { + use_int([o = Obj{'a'}, i = b ? h(Obj{'t'}) : 0] { return i; }(), g()); +} + +// CIR-LABEL: cir.func {{.*}} @_Z12init_captureb( +// CIR: %[[LAMBDA:.*]] = cir.alloca "ref.tmp0" +// CIR: %[[TMP:.*]] = cir.alloca "ref.tmp1" {{.*}} : !cir.ptr<!rec_Obj> +// CIR: %[[ACTIVE:.*]] = cir.alloca "cleanup.isactive" {{.*}} : !cir.ptr<!cir.bool> +// CIR: %[[O:.*]] = cir.get_member %[[LAMBDA]][0] {name = "o"} +// CIR: cir.call @_ZN3ObjC1Ec(%[[O]], %{{.*}}) +// CIR-NEXT: %[[TRUE:.*]] = cir.const #true +// CIR-NEXT: cir.store %[[TRUE]], %[[ACTIVE]] : !cir.bool, !cir.ptr<!cir.bool> +// CIR-NEXT: cir.cleanup.scope { +// CIR: cir.cleanup.scope { +// CIR: cir.ternary +// CIR: cir.store {{.*}} : !s32i, !cir.ptr<!s32i> +// CIR-NEXT: %[[FALSE:.*]] = cir.const #false +// CIR-NEXT: cir.store %[[FALSE]], %[[ACTIVE]] : !cir.bool, !cir.ptr<!cir.bool> +// CIR-NEXT: cir.cleanup.scope { +// CIR-NEXT: %[[I:.*]] = cir.call @_ZZ12init_capturebENK3$_{{[0-9]+}}clEv(%[[LAMBDA]]) +// CIR-NEXT: %[[G:.*]] = cir.call @_Z1gv() +// CIR-NEXT: cir.call @_Z7use_intii(%[[I]], %[[G]]) +// CIR-NEXT: cir.yield +// CIR-NEXT: } cleanup normal { +// CIR-NEXT: cir.call @_ZZ12init_capturebEN3$_{{[0-9]+}}D1Ev(%[[LAMBDA]]) +// CIR-NEXT: cir.yield +// CIR-NEXT: } +// CIR-NEXT: cir.yield +// CIR-NEXT: } cleanup normal { +// CIR-NEXT: %[[IS_COND:.*]] = cir.load +// CIR-NEXT: cir.if %[[IS_COND]] { +// CIR-NEXT: cir.call @_ZN3ObjD1Ev(%[[TMP]]){{.*}} +// CIR-NEXT: } +// CIR-NEXT: cir.yield +// CIR-NEXT: } +// CIR-NEXT: cir.yield +// CIR-NEXT: } cleanup normal { +// CIR-NEXT: %[[IS_ACTIVE:.*]] = cir.load {{.*}} %[[ACTIVE]] : !cir.ptr<!cir.bool>, !cir.bool +// CIR-NEXT: cir.if %[[IS_ACTIVE]] { +// CIR-NEXT: cir.call @_ZN3ObjD1Ev(%[[O]]) diff --git a/clang/test/CIR/CodeGen/cleanup-scope-tmp.cpp b/clang/test/CIR/CodeGen/cleanup-scope-tmp.cpp index 5f5c3f67c136ca..e89ad0e10b41d2 100644 --- a/clang/test/CIR/CodeGen/cleanup-scope-tmp.cpp +++ b/clang/test/CIR/CodeGen/cleanup-scope-tmp.cpp @@ -1,5 +1,11 @@ // RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -Wno-unused-value -fclangir -emit-cir %s -o %t.cir -// RUN: FileCheck --input-file=%t.cir %s -check-prefix=CIR +// RUN: FileCheck --input-file=%t.cir %s --check-prefixes=CIR,ASM +// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fclangir -fcxx-exceptions -fexceptions -emit-cir %s -o %t.eh.cir +// RUN: FileCheck --input-file=%t.eh.cir %s --check-prefix=ASM +// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t-cir.ll +// RUN: FileCheck --input-file=%t-cir.ll %s --check-prefix=ASM-IR +// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll +// RUN: FileCheck --input-file=%t.ll %s --check-prefix=ASM-IR struct StructWithDestructor { ~StructWithDestructor(); @@ -29,3 +35,91 @@ void cleanup_scope_with_body_and_cleanup() { // CIR: cir.call @_ZN20StructWithDestructorD1Ev(%0) nothrow : (!cir.ptr<!rec_StructWithDestructor> {{.*}}) -> () // CIR: cir.yield // CIR: } + +// The operands of an asm statement are not wrapped in an ExprWithCleanups. +// Their temporaries are destroyed at the end of the asm statement. + +struct S { + S(int); + ~S(); +}; + +extern "C" { + +int g(const S &); +void use(int); + +void asm_plain() { + asm volatile("" : : "r"(g(S(1)))); + use(0); +} + +// ASM-LABEL: cir.func {{.*}} @asm_plain( +// ASM: %[[TMP:.*]] = cir.alloca "ref.tmp0" {{.*}} : !cir.ptr<!rec_S> +// ASM: cir.call @_ZN1SC1Ei(%[[TMP]], +// ASM-NEXT: cir.cleanup.scope { +// ASM-NEXT: %[[G:.*]] = cir.call @g(%[[TMP]]) +// ASM-NEXT: cir.asm( +// ASM: in = [%[[G]] : !s32i], +// ASM: cir.yield +// ASM-NEXT: } cleanup {{(normal|all)}} { +// ASM-NEXT: cir.call @_ZN1SD1Ev(%[[TMP]]) +// ASM-NEXT: cir.yield +// ASM-NEXT: } +// ASM-NEXT: %[[ZERO:.*]] = cir.const #cir.int<0> : !s32i +// ASM-NEXT: cir.call @use(%[[ZERO]]) + +// ASM-IR-LABEL: define {{.*}} void @asm_plain( +// ASM-IR: call void @_ZN1SC1Ei(ptr {{.*}} %[[TMP:.*]], i32 {{.*}} 1) +// ASM-IR: call void asm sideeffect +// ASM-IR: call void @_ZN1SD1Ev(ptr {{.*}} %[[TMP]]) +// ASM-IR: call void @use(i32 {{.*}} 0) + +// The cleanup scope of the enclosing full-expression is open, but the asm +// statement in the statement expression needs its own. +void asm_in_statement_expression(bool b) { + use(g(S(3)) + (b ? 1 : 0) + ({ + int y = 0; + asm volatile("" : "+r"(y) : "r"(b ? g(S(4)) : 0)); + y; + })); +} + +// ASM-LABEL: cir.func {{.*}} @asm_in_statement_expression( +// ASM: %[[TMP3:.*]] = cir.alloca "ref.tmp0" {{.*}} : !cir.ptr<!rec_S> +// ASM: %[[TMP4:.*]] = cir.alloca "ref.tmp1" {{.*}} : !cir.ptr<!rec_S> +// ASM: %[[ACTIVE:.*]] = cir.alloca "cleanup.cond" {{.*}} : !cir.ptr<!cir.bool> +// ASM: cir.scope { +// ASM: cir.cleanup.scope { +// ASM-NEXT: %[[FALSE:.*]] = cir.const #false +// ASM-NEXT: cir.store %[[FALSE]], %[[ACTIVE]] +// ASM-NEXT: %[[ARG:.*]] = cir.ternary(%{{.*}}, true { +// ASM: cir.call @_ZN1SC1Ei(%[[TMP4]], +// ASM-NEXT: %[[TRUE:.*]] = cir.const #true +// ASM-NEXT: cir.store %[[TRUE]], %[[ACTIVE]] +// ASM: cir.call @g(%[[TMP4]]) +// ASM: }) : (!cir.bool) -> !s32i +// ASM-NEXT: cir.asm( +// ASM: in = [%[[ARG]] : !s32i], +// ASM: cir.yield +// ASM-NEXT: } cleanup {{(normal|all)}} { +// ASM-NEXT: %[[IS_ACTIVE:.*]] = cir.load {{.*}} %[[ACTIVE]] +// ASM-NEXT: cir.if %[[IS_ACTIVE]] { +// ASM-NEXT: cir.call @_ZN1SD1Ev(%[[TMP4]]){{.*}} +// ASM-NEXT: } +// ASM-NEXT: cir.yield +// ASM-NEXT: } +// ASM: } +// ASM: cir.call @use( +// ASM: } cleanup {{(normal|all)}} { +// ASM-NEXT: cir.call @_ZN1SD1Ev(%[[TMP3]]) + +// ASM-IR-LABEL: define {{.*}} void @asm_in_statement_expression( +// ASM-IR: call void @_ZN1SC1Ei(ptr {{.*}} %[[TMP3:.*]], i32 {{.*}} 3) +// ASM-IR: call void @_ZN1SC1Ei(ptr {{.*}} %[[TMP4:.*]], i32 {{.*}} 4) +// ASM-IR: call i32 asm sideeffect +// ASM-IR: call void @_ZN1SD1Ev(ptr {{.*}} %[[TMP4]]) +// ASM-IR: call void @use( +// ASM-IR: call void @_ZN1SD1Ev(ptr {{.*}} %[[TMP3]]) + +} // extern "C" diff --git a/clang/test/CIR/CodeGenCoroutines/coro-agg.cpp b/clang/test/CIR/CodeGenCoroutines/coro-agg.cpp index 47791d72627716..3c09be504168f5 100644 --- a/clang/test/CIR/CodeGenCoroutines/coro-agg.cpp +++ b/clang/test/CIR/CodeGenCoroutines/coro-agg.cpp @@ -1,11 +1,11 @@ // RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fclangir -Wno-coroutine-missing-unhandled-exception -emit-cir %s -o %t.cir -// RUN: FileCheck --input-file=%t.cir %s -check-prefixes=CIR,CIR-COMMON,CIR-NOEH +// RUN: FileCheck --input-file=%t.cir %s -check-prefixes=CIR,CIR-COMMON,CIR-NOEH,RESUME,RESUME-NOEH // RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fclangir -fcxx-exceptions -fexceptions -Wno-coroutine-missing-unhandled-exception -emit-cir %s -o %t.eh.cir -// RUN: FileCheck --input-file=%t.eh.cir %s -check-prefixes=CIR-EH,CIR-COMMON +// RUN: FileCheck --input-file=%t.eh.cir %s -check-prefixes=CIR-EH,CIR-COMMON,RESUME,RESUME-EH // RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -emit-llvm -disable-llvm-passes -Wno-coroutine-missing-unhandled-exception %s -o %t.ll -// RUN: FileCheck --input-file=%t.ll %s -check-prefix=OGCG +// RUN: FileCheck --input-file=%t.ll %s -check-prefixes=OGCG,RESUME-IR // RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fcxx-exceptions -fexceptions -emit-llvm -disable-llvm-passes -Wno-coroutine-missing-unhandled-exception %s -o %t.eh.ll -// RUN: FileCheck --input-file=%t.eh.ll %s -check-prefix=OGCG-EH +// RUN: FileCheck --input-file=%t.eh.ll %s -check-prefixes=OGCG-EH,RESUME-IR #include "Inputs/coroutine.h" @@ -589,3 +589,429 @@ ThrowingDtorInitTask throwing_dtor_initial_resume() { // OGCG-EH-NEXT: to label %[[CONT:.*]] unwind label // OGCG-EH: [[CONT]]: // OGCG-EH-NEXT: store i1 false, ptr %[[RESUME_EH]], align 1 + +// Resume-expression temporaries survive through the enclosing full-expression. +// Construction guards exclude paths that destroy the coroutine while suspended; +// array elements and ready/suspend calls retain their own cleanup boundaries. + +struct S { + S(int); + ~S(); +}; + +struct Agg { + S s; + int i; +}; + +extern "C" bool resume_flag(); +extern "C" int resume_arg(const S &); +extern "C" int resume_next(); +extern "C" void resume_use(int); +extern "C" void resume_use_pair(int, int); +extern "C" void resume_use_result(NT); +extern "C" void resume_use_aggregate(const Agg &, int); +extern "C" void mark(); + +struct DA { + bool await_ready(); + void await_suspend(std::coroutine_handle<>); + int await_resume(const S & = S(1)); +}; + +struct DV { + bool await_ready(); + void await_suspend(std::coroutine_handle<>); + int await_resume(S = S(2)); +}; + +struct DCC { + bool await_ready(); + void await_suspend(std::coroutine_handle<>); + int await_resume(int = resume_flag() ? resume_arg(S(1)) : 0); +}; + +struct RA { + bool await_ready(); + void await_suspend(std::coroutine_handle<>); + NT await_resume(const S & = S(1)); +}; + +struct PL { + bool await_ready(); + void await_suspend(std::coroutine_handle<>); + int await_resume(); +}; + +struct Task { + struct promise_type { + Task get_return_object(); + std::suspend_never initial_suspend(); + std::suspend_never final_suspend() noexcept; + void return_void(); + void unhandled_exception(); + DA yield_value(int); + }; +}; + +// RESUME-LABEL: cir.func coroutine {{.*}} @defarg_condtemp( +// RESUME: %[[TMP:[^,]+]] = cir.alloca "ref.tmp{{[0-9]+}}" {{.*}} : !cir.ptr<!rec_S> +// RESUME: %[[FLAG:.*]] = cir.alloca "cleanup.cond" +// RESUME: %[[FALSE:.*]] = cir.const #false +// RESUME-NEXT: cir.store %[[FALSE]], %[[FLAG]] +// RESUME: cir.await(user, ready : { +// RESUME: }, resume : { +// RESUME: cir.ternary( +// RESUME: cir.call @_ZN1SC1Ei(%[[TMP]], +// RESUME-NEXT: %[[TRUE:.*]] = cir.const #true +// RESUME-NEXT: cir.store %[[TRUE]], %[[FLAG]] +// RESUME: cir.call @_ZN3DCC12await_resumeEi( +// RESUME: },) +// RESUME: cir.call @resume_use( +// RESUME-NOEH: } cleanup normal { +// RESUME-EH: } cleanup all { +// RESUME: %[[LIVE:.*]] = cir.load {{.*}} %[[FLAG]] +// RESUME-NEXT: cir.if %[[LIVE]] { +// RESUME-NEXT: cir.call @_ZN1SD1Ev(%[[TMP]]) + +extern "C" Task defarg_condtemp() { + resume_use(co_await DCC{}); +} + +// Cover reference and value arguments across two suspension points. Destroy +// them in reverse order at full-expression end, before the next statement. +// Construction flags exclude paths that destroy the coroutine while suspended. +// RESUME-LABEL: cir.func coroutine {{.*}} @two_awaits( +// RESUME-DAG: %[[TMP1:[^,]+]] = cir.alloca "ref.tmp{{[0-9]+}}" {{.*}} : !cir.ptr<!rec_S> +// RESUME-DAG: %[[FLAG1:.*]] = cir.alloca "cleanup.cond" +// RESUME-DAG: %[[TMP2:[^,]+]] = cir.alloca "agg.tmp{{[0-9]+}}" {{.*}} : !cir.ptr<!rec_S> +// RESUME-DAG: %[[FLAG2:.*]] = cir.alloca "cleanup.cond" +// RESUME: %[[FALSE1:.*]] = cir.const #false +// RESUME-NEXT: cir.store %[[FALSE1]], %[[FLAG1]] +// RESUME: cir.await(user, ready : { +// RESUME: }, resume : { +// RESUME: cir.call @_ZN1SC1Ei(%[[TMP1]], +// RESUME-NEXT: %[[TRUE1:.*]] = cir.const #true +// RESUME-NEXT: cir.store %[[TRUE1]], %[[FLAG1]] +// RESUME: cir.call @_ZN2DA12await_resumeERK1S( +// RESUME: },) +// RESUME: %[[FALSE2:.*]] = cir.const #false +// RESUME-NEXT: cir.store %[[FALSE2]], %[[FLAG2]] +// RESUME: cir.await(user, ready : { +// RESUME: }, resume : { +// RESUME: cir.call @_ZN1SC1Ei(%[[TMP2]], +// RESUME-NEXT: %[[TRUE2:.*]] = cir.const #true +// RESUME-NEXT: cir.store %[[TRUE2]], %[[FLAG2]] +// RESUME: cir.call @_ZN2DV12await_resumeE1S( +// RESUME: },) +// RESUME: cir.call @resume_use_pair( +// RESUME-NOEH: } cleanup normal { +// RESUME-EH: } cleanup all { +// RESUME: %[[LIVE2:.*]] = cir.load {{.*}} %[[FLAG2]] +// RESUME-NEXT: cir.if %[[LIVE2]] { +// RESUME-NEXT: cir.call @_ZN1SD1Ev(%[[TMP2]]) +// RESUME-NOEH: } cleanup normal { +// RESUME-EH: } cleanup all { +// RESUME: %[[LIVE1:.*]] = cir.load {{.*}} %[[FLAG1]] +// RESUME-NEXT: cir.if %[[LIVE1]] { +// RESUME-NEXT: cir.call @_ZN1SD1Ev(%[[TMP1]]) +// RESUME: cir.call @mark() + +// RESUME-IR-LABEL: define {{.*}} void @two_awaits( +// RESUME-IR: await.ready: +// RESUME-IR: {{(call|invoke)}} void @_ZN1SC1Ei(ptr {{.*}} %[[TMP1:[^,]+]], i32 {{.*}} 1) +// RESUME-IR: {{(call|invoke)}} {{.*}} i32 @_ZN2DA12await_resumeERK1S( +// RESUME-IR: {{(call|invoke)}} void @_ZN1SC1Ei(ptr {{.*}} %[[TMP2:[^,]+]], i32 {{.*}} 2) +// RESUME-IR: {{(call|invoke)}} void @resume_use_pair( +// RESUME-IR: call void @_ZN1SD1Ev(ptr {{.*}} %[[TMP2]]) +// RESUME-IR: call void @_ZN1SD1Ev(ptr {{.*}} %[[TMP1]]) +// RESUME-IR: {{(call|invoke)}} void @mark() + +extern "C" Task two_awaits() { + resume_use_pair(co_await DA{}, co_await DV{}); + mark(); +} + +// RESUME-LABEL: cir.func coroutine {{.*}} @class_result( +// RESUME: cir.await(user, ready : { +// RESUME: cir.call @_ZN2RA12await_resumeERK1S( +// RESUME: },) +// RESUME: cir.call @resume_use_result( +// RESUME: cir.call @_ZN2NTD1Ev( +// RESUME: cir.call @_ZN1SD1Ev( + +// RESUME-IR-LABEL: define {{.*}} void @class_result( +// RESUME-IR: {{(call|invoke)}} void @_ZN2RA12await_resumeERK1S( +// RESUME-IR: {{(call|invoke)}} void @resume_use_result( +// RESUME-IR: call void @_ZN2NTD1Ev( +// RESUME-IR: call void @_ZN1SD1Ev( + +extern "C" Task class_result() { + resume_use_result(co_await RA{}); +} + +// RESUME-LABEL: cir.func coroutine {{.*}} @in_conditional( +// RESUME: %[[FLAG:.*]] = cir.alloca "cleanup.cond" +// RESUME: %[[FALSE:.*]] = cir.const #false +// RESUME-NEXT: cir.store %[[FALSE]], %[[FLAG]] +// RESUME: cir.ternary( +// RESUME: cir.await(user, ready : { +// RESUME: cir.call @_ZN1SC1Ei(%[[TMP:[^,]+]], +// RESUME: },) +// RESUME: cir.call @resume_use( +// RESUME: %[[LIVE:.*]] = cir.load {{.*}} %[[FLAG]] +// RESUME-NEXT: cir.if %[[LIVE]] { +// RESUME-NEXT: cir.call @_ZN1SD1Ev(%[[TMP]]) + +extern "C" Task in_conditional() { + resume_use(resume_flag() ? co_await DA{} : 0); +} + +// RESUME-LABEL: cir.func coroutine {{.*}} @agg_init( +// RESUME: cir.call @_ZN1SC1Ei( +// RESUME-NEXT: %[[TRUE:.*]] = cir.const #true +// RESUME-NEXT: cir.store %[[TRUE]], %[[ACTIVE:.*]] +// RESUME: cir.await(user, ready : { +// RESUME: cir.call @_ZN1SC1Ei(%[[TMP:[^,]+]], +// RESUME: cir.call @_ZN2DA12await_resumeERK1S( +// RESUME: },) +// RESUME: %[[FALSE:.*]] = cir.const #false +// RESUME-NEXT: cir.store %[[FALSE]], %[[ACTIVE]] +// RESUME-NOT: cir.call @_ZN1SD1Ev(%[[TMP]]) +// RESUME: cir.call @resume_next() +// RESUME: cir.call @resume_use_aggregate( +// RESUME: cir.call @_ZN3AggD1Ev( +// RESUME: cir.call @_ZN1SD1Ev(%[[TMP]]) + +// RESUME-IR-LABEL: define {{.*}} void @agg_init( +// RESUME-IR: {{(call|invoke)}} {{.*}} i32 @_ZN2DA12await_resumeERK1S( +// RESUME-IR: {{(call|invoke)}} i32 @resume_next() +// RESUME-IR: {{(call|invoke)}} void @resume_use_aggregate( +// RESUME-IR: call void @_ZN3AggD1Ev( +// RESUME-IR: call void @_ZN1SD1Ev( + +extern "C" Task agg_init() { + resume_use_aggregate(Agg{S(0), co_await DA{}}, resume_next()); +} + +// An unrelated conditional must not create a resume cleanup guard. +// RESUME-LABEL: cir.func coroutine {{.*}} @agg_plain_resume( +// RESUME-NOT: cir.alloca "cleanup.cond" +// RESUME: cir.await(user, ready : { +// RESUME: cir.call @_ZN2PL12await_resumeEv( +// RESUME: },) +// RESUME: cir.call @resume_use_aggregate( +// RESUME: cir.call @_ZN3AggD1Ev( +// RESUME: cir.call @mark() + +extern "C" Task agg_plain_resume() { + resume_use_aggregate(Agg{S(0), co_await PL{}}, resume_flag() ? 1 : 2); + mark(); +} + +struct E { + E(const S & = S(3)); + ~E(); +}; + +struct Holder { + E e[2]; + int k; +}; + +extern "C" int take(E *); +extern "C" int take2(Holder *); + +struct AN { + bool await_ready(); + void await_suspend(std::coroutine_handle<>); + int await_resume(int = take(new E[2]) + take2(new Holder{})); +}; + +// Array-element boundaries still apply inside a deferred resume expression. +// RESUME-LABEL: cir.func coroutine {{.*}} @array_elements( +// RESUME: cir.await(user, ready : { +// RESUME: }, resume : { +// RESUME: cir.do { +// RESUME: cir.call @_ZN1SC1Ei(%[[TMP1:[^,]+]], +// RESUME: cir.call @_ZN1EC1ERK1S( +// RESUME-NOEH: } cleanup normal { +// RESUME-EH: } cleanup all { +// RESUME-NEXT: cir.call @_ZN1SD1Ev(%[[TMP1]]) +// RESUME: } while { +// RESUME: cir.call @take( +// RESUME: cir.do { +// RESUME: cir.call @_ZN1SC1Ei(%[[TMP2:[^,]+]], +// RESUME: cir.call @_ZN1EC1ERK1S( +// RESUME-NOEH: } cleanup normal { +// RESUME-EH: } cleanup all { +// RESUME-NEXT: cir.call @_ZN1SD1Ev(%[[TMP2]]) +// RESUME: } while { +// RESUME: cir.call @take2( +// RESUME: cir.call @_ZN2AN12await_resumeEi( +// RESUME: },) +// RESUME: cir.call @resume_use( + +extern "C" Task array_elements() { + resume_use(co_await AN{}); + mark(); +} + +// RESUME-LABEL: cir.func coroutine {{.*}} @asm_operand( +// RESUME: cir.await(user, ready : { +// RESUME: cir.call @_ZN1SC1Ei(%[[TMP:[^,]+]], +// RESUME: },) +// RESUME: cir.asm( +// RESUME: cir.call @_ZN1SD1Ev(%[[TMP]]) +// RESUME: cir.call @mark() + +extern "C" Task asm_operand() { + asm volatile("" : : "r"(co_await DA{})); + mark(); +} + +// RESUME-LABEL: cir.func coroutine {{.*}} @coyield_arg( +// RESUME: cir.await(yield, ready : { +// RESUME: cir.call @_ZN1SC1Ei(%[[TMP:[^,]+]], +// RESUME: },) +// RESUME: cir.call @resume_use( +// RESUME: cir.call @_ZN1SD1Ev(%[[TMP]]) + +extern "C" Task coyield_arg() { + resume_use(co_yield 1); +} + +struct TransformTask { + struct promise_type { + TransformTask get_return_object(); + std::suspend_never initial_suspend(); + std::suspend_never final_suspend() noexcept; + void return_void(); + void unhandled_exception(); + PL await_transform(int, int = resume_flag() ? resume_arg(S(5)) : 0); + }; +}; + +// RESUME-LABEL: cir.func coroutine {{.*}} @transform_condtemp( +// RESUME: %[[FLAG:.*]] = cir.alloca "cleanup.cond" +// RESUME: cir.ternary( +// RESUME: cir.call @_ZN1SC1Ei(%[[TMP:[^,]+]], +// RESUME: cir.call @_ZN13TransformTask12promise_type15await_transformEii( +// RESUME: cir.await(user, ready : { +// RESUME: },) +// RESUME: cir.call @resume_use( +// RESUME: %[[LIVE:.*]] = cir.load {{.*}} %[[FLAG]] +// RESUME-NEXT: cir.if %[[LIVE]] { +// RESUME-NEXT: cir.call @_ZN1SD1Ev(%[[TMP]]) + +extern "C" TransformTask transform_condtemp() { + resume_use(co_await 1); +} + +struct NoexceptInitAwaiter { + bool await_ready(); + void await_suspend(std::coroutine_handle<>); + void await_resume(const S & = S(1)) noexcept; +}; + +struct NoexceptFinalAwaiter { + bool await_ready() noexcept; + void await_suspend(std::coroutine_handle<>) noexcept; + void await_resume(const S & = S(2)) noexcept; +}; + +struct DefaultInitTask { + struct promise_type { + DefaultInitTask get_return_object(); + NoexceptInitAwaiter initial_suspend(); + NoexceptFinalAwaiter final_suspend() noexcept; + void return_void(); + void unhandled_exception(); + }; +}; + +// RESUME-LABEL: cir.func coroutine {{.*}} @init_final_noexcept( +// RESUME-NOT: resume.eh +// RESUME: cir.await(init, ready : { +// RESUME: cir.call @_ZN1SC1Ei(%[[INIT_TMP:[^,]+]], +// RESUME: cir.call @_ZN19NoexceptInitAwaiter12await_resumeERK1S( +// RESUME: },) +// RESUME: cir.call @_ZN1SD1Ev(%[[INIT_TMP]]) +// RESUME: }, body : { +// RESUME: cir.call @mark() +// RESUME: }, finalSuspend : { +// RESUME: cir.call @_ZN1SC1Ei(%[[FINAL_TMP:[^,]+]], +// RESUME: cir.call @_ZN20NoexceptFinalAwaiter12await_resumeERK1S( +// RESUME: },) +// RESUME: cir.call @_ZN1SD1Ev(%[[FINAL_TMP]]) + +extern "C" DefaultInitTask init_final_noexcept() { + mark(); + co_return; +} + +struct InitAwaiter { + bool await_ready(); + void await_suspend(std::coroutine_handle<>); + void await_resume(const S & = S(1), int = resume_flag() ? resume_arg(S(2)) : 0); +}; + +struct InitTask { + struct promise_type { + InitTask get_return_object(); + InitAwaiter initial_suspend(); + std::suspend_never final_suspend() noexcept; + void return_void(); + void unhandled_exception(); + }; +}; + +// RESUME-LABEL: cir.func coroutine {{.*}} @init_try( +// RESUME-EH: %[[RESUMED:.*]] = cir.alloca "resume.eh" +// RESUME: cir.await(init, ready : { +// RESUME-EH: cir.try { +// RESUME: cir.call @_ZN1SC1Ei(%[[TMP1:[^,]+]], +// RESUME: cir.ternary( +// RESUME: cir.call @_ZN1SC1Ei(%[[TMP2:[^,]+]], +// RESUME: cir.call @_ZN11InitAwaiter12await_resumeERK1Si( +// RESUME: cir.call @_ZN1SD1Ev(%[[TMP2]]) +// RESUME: cir.call @_ZN1SD1Ev(%[[TMP1]]) +// RESUME-EH-NEXT: cir.yield +// RESUME-EH-NEXT: } +// RESUME-EH-NEXT: %[[TRUE:.*]] = cir.const #true +// RESUME-EH-NEXT: cir.store %[[TRUE]], %[[RESUMED]] +// RESUME-EH: } catch all +// RESUME: }, body : { +// RESUME: cir.call @mark() + +extern "C" InitTask init_try() { + mark(); + co_return; +} + +struct ReadySuspendArgs { + bool await_ready(const S & = S(8)); + void await_suspend(std::coroutine_handle<>, const S & = S(9)); + int await_resume(); +}; + +// Ready and suspend arguments end before the next coroutine phase. +// RESUME-LABEL: cir.func coroutine {{.*}} @ready_suspend_in_conditional( +// RESUME: cir.ternary( +// RESUME: cir.await(user, ready : { +// RESUME: cir.call @_ZN1SC1Ei(%[[READY:[^,]+]], +// RESUME: cir.call @_ZN16ReadySuspendArgs11await_readyERK1S( +// RESUME-NOEH: } cleanup normal { +// RESUME-EH: } cleanup all { +// RESUME-NEXT: cir.call @_ZN1SD1Ev(%[[READY]]) +// RESUME: }, suspend : { +// RESUME: cir.call @_ZN1SC1Ei(%[[SUSPEND:[^,]+]], +// RESUME: cir.call @_ZN16ReadySuspendArgs13await_suspendESt16coroutine_handleIvERK1S( +// RESUME-NOEH: } cleanup normal { +// RESUME-EH: } cleanup all { +// RESUME-NEXT: cir.call @_ZN1SD1Ev(%[[SUSPEND]]) +// RESUME: cir.coro.suspend_point +// RESUME: }, resume : { +// RESUME: cir.call @_ZN16ReadySuspendArgs12await_resumeEv( + +extern "C" Task ready_suspend_in_conditional(bool b) { resume_use(b ? co_await ReadySuspendArgs{} : 0); } _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
