[clang] 5b56352 - [CIR] Implement cleanups for temporaries with automatic duration (#189754)
via cfe-commits
cfe-commits at lists.llvm.org
Fri Apr 3 08:38:11 PDT 2026
Author: Andy Kaylor
Date: 2026-04-03T08:38:06-07:00
New Revision: 5b56352757434eb6756cf0211c32832a2ced4ea3
URL: https://github.com/llvm/llvm-project/commit/5b56352757434eb6756cf0211c32832a2ced4ea3
DIFF: https://github.com/llvm/llvm-project/commit/5b56352757434eb6756cf0211c32832a2ced4ea3.diff
LOG: [CIR] Implement cleanups for temporaries with automatic duration (#189754)
This implements handling for cleanup of temporary variables with
automatic storage duration. This is a simplified implementation that
doesn't yet handle the possibility of exceptions being thrown within
this cleanup scope or the cleanup scope being inside a conditional
operation. Support for those cases will be added later.
Added:
Modified:
clang/lib/CIR/CodeGen/CIRGenCleanup.cpp
clang/lib/CIR/CodeGen/CIRGenDecl.cpp
clang/lib/CIR/CodeGen/CIRGenExpr.cpp
clang/lib/CIR/CodeGen/CIRGenFunction.h
clang/lib/CIR/CodeGen/EHScopeStack.h
clang/test/CIR/CodeGen/cleanup.cpp
Removed:
################################################################################
diff --git a/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp b/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp
index f83f99e3ebb63..1bc3df92cc64b 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp
@@ -542,3 +542,18 @@ void CIRGenFunction::popCleanupBlocks(
}
}
}
+
+/// Pops cleanup blocks until the given savepoint is reached, then add the
+/// cleanups from the given savepoint in the lifetime-extended cleanups stack.
+void CIRGenFunction::popCleanupBlocks(
+ EHScopeStack::stable_iterator oldCleanupStackDepth,
+ size_t oldLifetimeExtendedSize, ArrayRef<mlir::Value *> valuesToReload) {
+ popCleanupBlocks(oldCleanupStackDepth, valuesToReload);
+
+ // Promote deferred lifetime-extended cleanups onto the EH scope stack.
+ for (const LifetimeExtendedCleanupEntry &cleanup : llvm::make_range(
+ lifetimeExtendedCleanupStack.begin() + oldLifetimeExtendedSize,
+ lifetimeExtendedCleanupStack.end()))
+ pushLifetimeExtendedCleanupToEHStack(cleanup);
+ lifetimeExtendedCleanupStack.truncate(oldLifetimeExtendedSize);
+}
diff --git a/clang/lib/CIR/CodeGen/CIRGenDecl.cpp b/clang/lib/CIR/CodeGen/CIRGenDecl.cpp
index 94d8632a48437..26a1a17243130 100644
--- a/clang/lib/CIR/CodeGen/CIRGenDecl.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenDecl.cpp
@@ -1007,6 +1007,37 @@ void CIRGenFunction::pushDestroy(CleanupKind cleanupKind, Address addr,
pushFullExprCleanup<DestroyObject>(cleanupKind, addr, type, destroyer);
}
+void CIRGenFunction::pushLifetimeExtendedDestroy(CleanupKind cleanupKind,
+ Address addr, QualType type,
+ Destroyer *destroyer,
+ bool useEHCleanupForArray) {
+ if (isInConditionalBranch()) {
+ cgm.errorNYI("conditional lifetime-extended destroy");
+ return;
+ }
+
+ // Classic codegen also uses pushDestroyAndDeferDeactivation here to push an
+ // EH cleanup that protects the temporary during the rest of the full
+ // expression, then deactivates it when the full expression ends. We don't
+ // have deferred deactivation yet, so we only queue the lifetime-extended
+ // cleanup below. When deferred deactivation is implemented, add the
+ // pushDestroyAndDeferDeactivation call here.
+ if (getLangOpts().Exceptions) {
+ cgm.errorNYI("lifetime-extended cleanup with exceptions enabled");
+ return;
+ }
+
+ assert(!cir::MissingFeatures::useEHCleanupForArray());
+
+ pushCleanupAfterFullExpr(cleanupKind, addr, type, destroyer);
+}
+
+void CIRGenFunction::pushLifetimeExtendedCleanupToEHStack(
+ const LifetimeExtendedCleanupEntry &entry) {
+ ehStack.pushCleanup<DestroyObject>(entry.kind, entry.addr, entry.type,
+ entry.destroyer);
+}
+
/// Destroys all the elements of the given array, beginning from last to first.
/// The array cannot be zero-length.
///
diff --git a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
index 65644bc1a3fd9..060a90ca5fb8c 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
@@ -1719,8 +1719,9 @@ static void pushTemporaryCleanup(CIRGenFunction &cgf,
break;
case SD_Automatic:
- cgf.cgm.errorNYI(e->getSourceRange(),
- "pushTemporaryCleanup: automatic storage duration");
+ cgf.pushLifetimeExtendedDestroy(
+ NormalAndEHCleanup, referenceTemporary, e->getType(),
+ CIRGenFunction::destroyCXXObject, cgf.getLangOpts().Exceptions);
break;
case SD_Dynamic:
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h
index 0bd440a61db20..7914a03a7f1a8 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.h
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h
@@ -92,6 +92,25 @@ class CIRGenFunction : public CIRGenTypeCache {
/// Tracks function scope overall cleanup handling.
EHScopeStack ehStack;
+ typedef void Destroyer(CIRGenFunction &cgf, Address addr, QualType ty);
+
+ /// An entry in the lifetime-extended cleanup stack. Each entry represents a
+ /// cleanup that was deferred past a full-expression boundary (e.g.,
+ /// destroying a temporary bound to a local reference). When the enclosing
+ /// scope exits, these entries are promoted to the EH scope stack.
+ ///
+ /// Currently only DestroyObject cleanups are lifetime-extended. When other
+ /// cleanup types are needed (e.g., CallLifetimeEnd), this struct can be
+ /// extended with a std::variant of cleanup data types.
+ struct LifetimeExtendedCleanupEntry {
+ CleanupKind kind;
+ Address addr;
+ QualType type;
+ Destroyer *destroyer;
+ };
+
+ llvm::SmallVector<LifetimeExtendedCleanupEntry> lifetimeExtendedCleanupStack;
+
GlobalDecl curSEHParent;
/// A mapping from NRVO variables to the flags used to indicate
@@ -970,6 +989,12 @@ class CIRGenFunction : public CIRGenTypeCache {
/// that have been added.
void popCleanupBlocks(EHScopeStack::stable_iterator oldCleanupStackDepth,
ArrayRef<mlir::Value *> valuesToReload = {});
+
+ /// Pops cleanup blocks until the given savepoint is reached, then adds the
+ /// cleanups from the given savepoint in the lifetime-extended cleanups stack.
+ void popCleanupBlocks(EHScopeStack::stable_iterator oldCleanupStackDepth,
+ size_t oldLifetimeExtendedSize,
+ ArrayRef<mlir::Value *> valuesToReload = {});
void popCleanupBlock();
void terminateStructuredRegionBody(mlir::Region &r, mlir::Location loc);
@@ -997,10 +1022,19 @@ class CIRGenFunction : public CIRGenTypeCache {
cgm.errorNYI("pushFullExprCleanup in conditional branch");
}
+ /// Queue a cleanup to be pushed after finishing the current full-expression.
+ /// When the enclosing RunCleanupsScope exits, popCleanupBlocks promotes these
+ /// entries onto the EH scope stack for the enclosing scope.
+ void pushCleanupAfterFullExpr(CleanupKind kind, Address addr, QualType type,
+ Destroyer *destroyer) {
+ lifetimeExtendedCleanupStack.push_back({kind, addr, type, destroyer});
+ }
+
/// Enters a new scope for capturing cleanups, all of which
/// will be executed once the scope is exited.
class RunCleanupsScope {
EHScopeStack::stable_iterator cleanupStackDepth, oldCleanupStackDepth;
+ size_t lifetimeExtendedCleanupStackSize;
protected:
bool performCleanup;
@@ -1018,6 +1052,8 @@ class CIRGenFunction : public CIRGenTypeCache {
explicit RunCleanupsScope(CIRGenFunction &cgf)
: performCleanup(true), cgf(cgf) {
cleanupStackDepth = cgf.ehStack.stable_begin();
+ lifetimeExtendedCleanupStackSize =
+ cgf.lifetimeExtendedCleanupStack.size();
oldDidCallStackSave = cgf.didCallStackSave;
cgf.didCallStackSave = false;
oldCleanupStackDepth = cgf.currentCleanupStackDepth;
@@ -1035,7 +1071,8 @@ class CIRGenFunction : public CIRGenTypeCache {
void forceCleanup(ArrayRef<mlir::Value *> valuesToReload = {}) {
assert(performCleanup && "Already forced cleanup");
cgf.didCallStackSave = oldDidCallStackSave;
- cgf.popCleanupBlocks(cleanupStackDepth, valuesToReload);
+ cgf.popCleanupBlocks(cleanupStackDepth, lifetimeExtendedCleanupStackSize,
+ valuesToReload);
performCleanup = false;
cgf.currentCleanupStackDepth = oldCleanupStackDepth;
}
@@ -1247,8 +1284,6 @@ class CIRGenFunction : public CIRGenTypeCache {
LexicalScope *curLexScope = nullptr;
- typedef void Destroyer(CIRGenFunction &cgf, Address addr, QualType ty);
-
static Destroyer destroyCXXObject;
void pushDestroy(QualType::DestructionKind dtorKind, Address addr,
@@ -1257,6 +1292,15 @@ class CIRGenFunction : public CIRGenTypeCache {
void pushDestroy(CleanupKind kind, Address addr, QualType type,
Destroyer *destroyer);
+ void pushLifetimeExtendedDestroy(CleanupKind kind, Address addr,
+ QualType type, Destroyer *destroyer,
+ bool useEHCleanupForArray);
+
+ /// Promote a single lifetime-extended cleanup entry onto the EH scope stack.
+ /// Defined in CIRGenDecl.cpp where the concrete cleanup types are visible.
+ void pushLifetimeExtendedCleanupToEHStack(
+ const LifetimeExtendedCleanupEntry &entry);
+
Destroyer *getDestroyer(clang::QualType::DestructionKind kind);
/// Start generating a thunk function.
diff --git a/clang/lib/CIR/CodeGen/EHScopeStack.h b/clang/lib/CIR/CodeGen/EHScopeStack.h
index 09b78820a2587..308d98f108101 100644
--- a/clang/lib/CIR/CodeGen/EHScopeStack.h
+++ b/clang/lib/CIR/CodeGen/EHScopeStack.h
@@ -206,6 +206,21 @@ class EHScopeStack {
return new (buffer) T(n, a...);
}
+ /// Push a cleanup by copying a serialized cleanup object from the
+ /// LifetimeExtendedCleanupStack onto the EH scope stack. This is used when
+ /// a full-expression's RunCleanupsScope exits: cleanups that were deferred
+ /// for lifetime extension (e.g. destroying a temporary bound to a local
+ /// reference) are promoted from the byte buffer to the enclosing scope's
+ /// EH stack so they run when that scope ends.
+ ///
+ /// The memcpy is safe because Cleanup subclasses are required to be POD-like
+ /// (see the Cleanup class comment), and the vtable pointer is part of the
+ /// copied bytes, so the clone dispatches to the correct emit() override.
+ void pushCopyOfCleanup(CleanupKind kind, const void *cleanup, size_t size) {
+ void *buffer = pushCleanup(kind, size);
+ std::memcpy(buffer, cleanup, size);
+ }
+
void setCGF(CIRGenFunction *inCGF) { cgf = inCGF; }
/// Pops a cleanup scope off the stack. This is private to CIRGenCleanup.cpp.
diff --git a/clang/test/CIR/CodeGen/cleanup.cpp b/clang/test/CIR/CodeGen/cleanup.cpp
index 542ca4b6c9499..4097522525fc7 100644
--- a/clang/test/CIR/CodeGen/cleanup.cpp
+++ b/clang/test/CIR/CodeGen/cleanup.cpp
@@ -149,3 +149,18 @@ void complex_expr_with_cleanup_inside_cleanupscope() {
// CHECK: }
// CHECK: %[[RELOAD:.*]] = cir.load {{.*}} %[[TEMP_ADDR]] : !cir.ptr<!cir.complex<!s32i>>, !cir.complex<!s32i>
// CHECK: cir.store {{.*}} %[[RELOAD]], %[[RESULT]] : !cir.complex<!s32i>, !cir.ptr<!cir.complex<!s32i>>
+
+void test_cleanup_with_automatic_storage_duration() {
+ const Struk &ref = Struk{};
+}
+
+// CHECK: cir.func{{.*}} @_Z44test_cleanup_with_automatic_storage_durationv()
+// CHECK: %[[REF_TMP:.*]] = cir.alloca !rec_Struk, !cir.ptr<!rec_Struk>, ["ref.tmp0"]
+// CHECK: %[[REF:.*]] = cir.alloca !cir.ptr<!rec_Struk>, !cir.ptr<!cir.ptr<!rec_Struk>>, ["ref", init, const]
+// CHECK: cir.cleanup.scope {
+// CHECK: cir.store{{.*}} %[[REF_TMP]], %[[REF]]
+// CHECK: cir.yield
+// CHECK: } cleanup normal {
+// CHECK: cir.call @_ZN5StrukD1Ev(%[[REF_TMP]]) nothrow
+// CHECK: cir.yield
+// CHECK: }
More information about the cfe-commits
mailing list