https://github.com/E00N777 updated 
https://github.com/llvm/llvm-project/pull/206695

>From bcd87ec04965353797c79ef88a4f3b5e373e74b3 Mon Sep 17 00:00:00 2001
From: E00N777 <[email protected]>
Date: Tue, 30 Jun 2026 17:40:25 +0800
Subject: [PATCH 1/4] [CIR] Emit lifetime markers for automatic variables

---
 clang/include/clang/CIR/MissingFeatures.h  |  1 +
 clang/lib/CIR/CodeGen/CIRGenCleanup.cpp    |  5 +-
 clang/lib/CIR/CodeGen/CIRGenCleanup.h      |  1 +
 clang/lib/CIR/CodeGen/CIRGenDecl.cpp       | 62 ++++++++++++++++++++++
 clang/lib/CIR/CodeGen/CIRGenFunction.cpp   | 20 +++++++
 clang/lib/CIR/CodeGen/CIRGenFunction.h     | 12 +++++
 clang/test/CIR/CodeGen/lifetime-marker.cpp | 54 +++++++++++++++++++
 7 files changed, 152 insertions(+), 3 deletions(-)
 create mode 100644 clang/test/CIR/CodeGen/lifetime-marker.cpp

diff --git a/clang/include/clang/CIR/MissingFeatures.h 
b/clang/include/clang/CIR/MissingFeatures.h
index 9a1546fe14e65..c1df47a5de637 100644
--- a/clang/include/clang/CIR/MissingFeatures.h
+++ b/clang/include/clang/CIR/MissingFeatures.h
@@ -227,6 +227,7 @@ struct MissingFeatures {
   static bool emitCondLikelihoodViaExpectIntrinsic() { return false; }
   static bool emitConstrainedFPCall() { return false; }
   static bool emitLifetimeMarkers() { return false; }
+  static bool lifetimeMarkersBypass() { return false; }
   static bool emitLValueAlignmentAssumption() { return false; }
   static bool emitNullCheckForDeleteCalls() { return false; }
   static bool emitNullabilityCheck() { return false; }
diff --git a/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp 
b/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp
index c1d2def6909d7..1bf4317347817 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp
@@ -392,7 +392,7 @@ void *EHScopeStack::pushCleanup(CleanupKind kind, size_t 
size) {
     innermostEHScope = stable_begin();
 
   if (isLifetimeMarker)
-    cgf->cgm.errorNYI("push lifetime marker cleanup");
+    scope->setLifetimeMarker();
 
   // With Windows -EHa, Invoke llvm.seh.scope.begin() for EHCleanup
   if (cgf->getLangOpts().EHAsynch && isEHCleanup && !isLifetimeMarker &&
@@ -436,8 +436,7 @@ bool EHScopeStack::requiresCatchOrCleanup() const {
   for (stable_iterator si = getInnermostEHScope(); si != stable_end();) {
     if (auto *cleanup = dyn_cast<EHCleanupScope>(&*find(si))) {
       if (cleanup->isLifetimeMarker()) {
-        // Skip lifetime markers and continue from the enclosing EH scope
-        assert(!cir::MissingFeatures::emitLifetimeMarkers());
+        si = cleanup->getEnclosingEHScope();
         continue;
       }
     }
diff --git a/clang/lib/CIR/CodeGen/CIRGenCleanup.h 
b/clang/lib/CIR/CodeGen/CIRGenCleanup.h
index bae04a2452006..46f1382bced7d 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCleanup.h
+++ b/clang/lib/CIR/CodeGen/CIRGenCleanup.h
@@ -157,6 +157,7 @@ class alignas(EHScopeStack::ScopeStackAlignment) 
EHCleanupScope
   void setActive(bool isActive) { cleanupBits.isActive = isActive; }
 
   bool isLifetimeMarker() const { return cleanupBits.isLifetimeMarker; }
+  void setLifetimeMarker() { cleanupBits.isLifetimeMarker = true; }
 
   bool hasActiveFlag() const { return activeFlag.isValid(); }
   Address getActiveFlag() const { return activeFlag; }
diff --git a/clang/lib/CIR/CodeGen/CIRGenDecl.cpp 
b/clang/lib/CIR/CodeGen/CIRGenDecl.cpp
index ad572f23b3667..34ae84252c21c 100644
--- a/clang/lib/CIR/CodeGen/CIRGenDecl.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenDecl.cpp
@@ -10,9 +10,11 @@
 //
 
//===----------------------------------------------------------------------===//
 
+#include "Address.h"
 #include "CIRGenCleanup.h"
 #include "CIRGenConstantEmitter.h"
 #include "CIRGenFunction.h"
+#include "EHScopeStack.h"
 #include "mlir/IR/Location.h"
 #include "clang/AST/Attr.h"
 #include "clang/AST/Attrs.inc"
@@ -28,6 +30,20 @@
 using namespace clang;
 using namespace clang::CIRGen;
 
+/// Does the statement tree rooted at \p s contain a label, switch, or indirect
+/// goto that could bypass a local's initialization? A coarse stand-in for
+/// classic CodeGen's per-decl bypass analysis (PR28267).
+static bool functionMightHaveBypass(const Stmt *s) {
+  if (!s)
+    return false;
+  if (isa<LabelStmt, SwitchStmt, IndirectGotoStmt>(s))
+    return true;
+  for (const Stmt *child : s->children())
+    if (functionMightHaveBypass(child))
+      return true;
+  return false;
+}
+
 CIRGenFunction::AutoVarEmission
 CIRGenFunction::emitAutoVarAlloca(const VarDecl &d,
                                   mlir::OpBuilder::InsertPoint ip) {
@@ -129,6 +145,21 @@ CIRGenFunction::emitAutoVarAlloca(const VarDecl &d,
                                  /*arraySize=*/nullptr, /*alloca=*/nullptr, 
ip);
       declare(address.getPointer(), &d, ty, getLoc(d.getSourceRange()),
               alignment);
+      // A goto/switch that bypasses the init splits the lifetime across IR
+      // regions and miscompiles under stack coloring (PR28267). Lacking
+      // classic's per-decl bypass analysis, drop markers for the whole
+      // function if any such statement is present.
+      assert(!cir::MissingFeatures::lifetimeMarkersBypass());
+      if (shouldEmitLifetimeOp && haveInsertPoint()) {
+        if (!fnHasBypassStmt.has_value())
+          fnHasBypassStmt = functionMightHaveBypass(
+              curFuncDecl ? curFuncDecl->getBody() : nullptr);
+        // Peel address-space casts to the alloca so the op verifier sees a
+        // value produced by cir.alloca.
+        if (!*fnHasBypassStmt)
+          emission.useLifetimeOp = emitLifetimeStartOp(
+              loc, address.getUnderlyingAllocaOp().getResult());
+      }
     }
   } else {
     // Non-constant size type
@@ -165,6 +196,9 @@ CIRGenFunction::emitAutoVarAlloca(const VarDecl &d,
     assert(!cir::MissingFeatures::generateDebugInfo());
   }
 
+  if (emission.useLifetimeOp)
+    pushLifetimeEnd(address);
+
   emission.addr = address;
   setAddrOfLocalVar(&d, address);
 
@@ -1005,6 +1039,15 @@ struct CallStackRestore final : EHScopeStack::Cleanup {
   }
 };
 
+struct CallLifetimeEnd final : EHScopeStack::Cleanup {
+  Address addr;
+  CallLifetimeEnd(Address addr) : addr(addr) {}
+  void emit(CIRGenFunction &cgf, Flags flags) override {
+    mlir::Value allocaPtr = addr.getUnderlyingAllocaOp().getResult();
+    cgf.emitLifetimeEndOp(allocaPtr.getLoc(), allocaPtr);
+  }
+};
+
 /// A cleanup which performs a partial array destroy where the end pointer is
 /// irregularly determined and must be loaded from a local.
 struct IrregularPartialArrayDestroy final : EHScopeStack::Cleanup {
@@ -1261,6 +1304,10 @@ void CIRGenFunction::pushStackRestore(CleanupKind kind, 
Address spMem) {
   ehStack.pushCleanup<CallStackRestore>(kind, spMem);
 }
 
+void CIRGenFunction::pushLifetimeEnd(Address addr) {
+  ehStack.pushCleanup<CallLifetimeEnd>(NormalEHLifetimeMarker, addr);
+}
+
 /// Enter a destroy cleanup for the given local variable.
 void CIRGenFunction::emitAutoVarTypeCleanup(
     const CIRGenFunction::AutoVarEmission &emission,
@@ -1317,3 +1364,18 @@ void CIRGenFunction::maybeEmitDeferredVarDeclInit(const 
VarDecl *vd) {
         emitVarDecl(*hd);
   }
 }
+
+bool CIRGenFunction::emitLifetimeStartOp(mlir::Location loc, mlir::Value addr) 
{
+  if (!shouldEmitLifetimeOp)
+    return false;
+
+  cir::LifetimeStartOp::create(builder, loc, addr);
+  return true;
+}
+
+void CIRGenFunction::emitLifetimeEndOp(mlir::Location loc, mlir::Value addr) {
+  if (!shouldEmitLifetimeOp)
+    return;
+
+  cir::LifetimeEndOp::create(builder, loc, addr);
+}
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp 
b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
index 6606cf74c7dea..be1f01c606a02 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
@@ -28,10 +28,30 @@
 
 namespace clang::CIRGen {
 
+/// shouldEmitLifetimeMarkers - Decide whether we need emit the life-time
+/// markers. Mirror of CodeGenFunction::shouldEmitLifetimeMarkers.
+static bool shouldEmitLifetimeMarkers(const CodeGenOptions &cgOpts,
+                                      const LangOptions &langOpts) {
+
+  if (cgOpts.DisableLifetimeMarkers)
+    return false;
+
+  // Sanitizers may use markers.
+  if (cgOpts.SanitizeAddressUseAfterScope ||
+      langOpts.Sanitize.has(SanitizerKind::HWAddress) ||
+      langOpts.Sanitize.has(SanitizerKind::Memory) ||
+      langOpts.Sanitize.has(SanitizerKind::MemtagStack))
+    return true;
+
+  return cgOpts.OptimizationLevel != 0;
+}
+
 CIRGenFunction::CIRGenFunction(CIRGenModule &cgm, CIRGenBuilderTy &builder,
                                bool suppressNewContext)
     : CIRGenTypeCache(cgm), cgm{cgm}, builder(builder) {
   ehStack.setCGF(this);
+  shouldEmitLifetimeOp = shouldEmitLifetimeMarkers(cgm.getCodeGenOpts(),
+                                                   getContext().getLangOpts());
 }
 
 CIRGenFunction::~CIRGenFunction() {}
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h 
b/clang/lib/CIR/CodeGen/CIRGenFunction.h
index b6a4a277fab92..5e3768a332042 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.h
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h
@@ -688,6 +688,9 @@ class CIRGenFunction : public CIRGenTypeCache {
     /// have the same sort of alloca initialization.
     bool emittedAsOffload = false;
 
+    /// True if lifetime op should be used.
+    bool useLifetimeOp = false;
+
     mlir::Value nrvoFlag{};
 
     struct Invalid {};
@@ -769,6 +772,7 @@ class CIRGenFunction : public CIRGenTypeCache {
   }
 
   void pushStackRestore(CleanupKind kind, Address spMem);
+  void pushLifetimeEnd(Address addr);
 
   /// Set the address of a local variable.
   void setAddrOfLocalVar(const clang::VarDecl *vd, Address addr) {
@@ -1553,6 +1557,9 @@ class CIRGenFunction : public CIRGenTypeCache {
                                       int64_t alignment,
                                       mlir::Value offsetValue = nullptr);
 
+  bool emitLifetimeStartOp(mlir::Location loc, mlir::Value addr);
+  void emitLifetimeEndOp(mlir::Location loc, mlir::Value addr);
+
 private:
   void emitAndUpdateRetAlloca(clang::QualType type, mlir::Location loc,
                               clang::CharUnits alignment);
@@ -2699,6 +2706,11 @@ class CIRGenFunction : public CIRGenTypeCache {
 private:
   QualType getVarArgType(const Expr *arg);
 
+  bool shouldEmitLifetimeOp = false;
+  /// Set when the current function has a goto/switch that may bypass a local's
+  /// init; lifetime markers are then suppressed. See functionMightHaveBypass.
+  std::optional<bool> fnHasBypassStmt;
+
   class InlinedInheritingConstructorScope {
   public:
     InlinedInheritingConstructorScope(CIRGenFunction &cgf, GlobalDecl gd)
diff --git a/clang/test/CIR/CodeGen/lifetime-marker.cpp 
b/clang/test/CIR/CodeGen/lifetime-marker.cpp
new file mode 100644
index 0000000000000..3e8e54dbce096
--- /dev/null
+++ b/clang/test/CIR/CodeGen/lifetime-marker.cpp
@@ -0,0 +1,54 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O2 -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 -O2 -fclangir -emit-llvm 
-disable-llvm-passes %s -o %t.ll
+// RUN: FileCheck --input-file=%t.ll %s --check-prefix=LLVM
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o 
%t-o0.cir
+// RUN: FileCheck --input-file=%t-o0.cir %s --check-prefix=O0
+
+void use(int);
+
+// A scalar automatic variable gets a lifetime.start at its declaration and a
+// matching lifetime.end when its scope is left.
+void f() {
+  int x;
+  use(x);
+}
+
+// CIR-LABEL: cir.func{{.*}} @_Z1fv()
+// CIR:         %[[X:.*]] = cir.alloca "x" {{.*}} : !cir.ptr<!s32i>
+// CIR:         cir.lifetime.start %[[X]] : !cir.ptr<!s32i>
+// CIR:         cir.cleanup.scope {
+// CIR:         } cleanup normal {
+// CIR:           cir.lifetime.end %[[X]] : !cir.ptr<!s32i>
+// CIR:         }
+
+// LLVM-LABEL: define{{.*}} void @_Z1fv()
+// LLVM:         %[[X:.*]] = alloca i32
+// LLVM:         call void @llvm.lifetime.start.p0(ptr %[[X]])
+// LLVM:         call void @llvm.lifetime.end.p0(ptr %[[X]])
+
+struct S {
+  ~S();
+};
+
+// The destructor runs before lifetime.end: the end marker is the outermost
+// cleanup, so it is emitted after the destructor call. FileCheck matches in
+// order, which pins the relative ordering.
+void g() {
+  S s;
+}
+
+// CIR-LABEL: cir.func{{.*}} @_Z1gv()
+// CIR:         %[[S:.*]] = cir.alloca "s" {{.*}} : !cir.ptr<!rec_S>
+// CIR:         cir.lifetime.start %[[S]] : !cir.ptr<!rec_S>
+// CIR:         cir.call @_ZN1SD1Ev(%[[S]])
+// CIR:         cir.lifetime.end %[[S]] : !cir.ptr<!rec_S>
+
+// LLVM-LABEL: define{{.*}} void @_Z1gv()
+// LLVM:         %[[S:.*]] = alloca %struct.S
+// LLVM:         call void @llvm.lifetime.start.p0(ptr %[[S]])
+// LLVM:         call void @_ZN1SD1Ev(ptr {{.*}} %[[S]])
+// LLVM:         call void @llvm.lifetime.end.p0(ptr %[[S]])
+
+// Without optimization no lifetime markers are emitted at all.
+// O0-NOT: cir.lifetime

>From 827a2e7f41d4c3fdbe8cf4444aa3d99bd019da42 Mon Sep 17 00:00:00 2001
From: E00N777 <[email protected]>
Date: Thu, 2 Jul 2026 18:32:46 +0800
Subject: [PATCH 2/4] [CIR] Address review feedback for lifetime markers

---
 clang/lib/CIR/CodeGen/CIRGenCleanup.cpp    |  1 +
 clang/lib/CIR/CodeGen/CIRGenDecl.cpp       | 69 +++++++++-------------
 clang/lib/CIR/CodeGen/CIRGenFunction.cpp   | 21 ++++++-
 clang/lib/CIR/CodeGen/CIRGenFunction.h     |  7 +--
 clang/test/CIR/CodeGen/lifetime-marker.cpp | 62 ++++++++++++++++++-
 5 files changed, 112 insertions(+), 48 deletions(-)

diff --git a/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp 
b/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp
index 1bf4317347817..d13cf33884162 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp
@@ -436,6 +436,7 @@ bool EHScopeStack::requiresCatchOrCleanup() const {
   for (stable_iterator si = getInnermostEHScope(); si != stable_end();) {
     if (auto *cleanup = dyn_cast<EHCleanupScope>(&*find(si))) {
       if (cleanup->isLifetimeMarker()) {
+        // Skip lifetime markers and continue from the enclosing EH scope
         si = cleanup->getEnclosingEHScope();
         continue;
       }
diff --git a/clang/lib/CIR/CodeGen/CIRGenDecl.cpp 
b/clang/lib/CIR/CodeGen/CIRGenDecl.cpp
index 34ae84252c21c..d6524bd05089e 100644
--- a/clang/lib/CIR/CodeGen/CIRGenDecl.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenDecl.cpp
@@ -30,19 +30,16 @@
 using namespace clang;
 using namespace clang::CIRGen;
 
-/// Does the statement tree rooted at \p s contain a label, switch, or indirect
-/// goto that could bypass a local's initialization? A coarse stand-in for
-/// classic CodeGen's per-decl bypass analysis (PR28267).
-static bool functionMightHaveBypass(const Stmt *s) {
-  if (!s)
-    return false;
-  if (isa<LabelStmt, SwitchStmt, IndirectGotoStmt>(s))
-    return true;
-  for (const Stmt *child : s->children())
-    if (functionMightHaveBypass(child))
-      return true;
-  return false;
-}
+struct CallLifetimeEnd final : EHScopeStack::Cleanup {
+  // The raw alloca pointer (in the alloca address space). Mirrors classic
+  // CodeGen's CallLifetimeEnd, which stores the llvm::Value pointer rather
+  // than an Address.
+  mlir::Value addr;
+  CallLifetimeEnd(mlir::Value addr) : addr(addr) {}
+  void emit(CIRGenFunction &cgf, Flags flags) override {
+    cgf.emitLifetimeEndOp(addr.getLoc(), addr);
+  }
+};
 
 CIRGenFunction::AutoVarEmission
 CIRGenFunction::emitAutoVarAlloca(const VarDecl &d,
@@ -150,15 +147,9 @@ CIRGenFunction::emitAutoVarAlloca(const VarDecl &d,
       // classic's per-decl bypass analysis, drop markers for the whole
       // function if any such statement is present.
       assert(!cir::MissingFeatures::lifetimeMarkersBypass());
-      if (shouldEmitLifetimeOp && haveInsertPoint()) {
-        if (!fnHasBypassStmt.has_value())
-          fnHasBypassStmt = functionMightHaveBypass(
-              curFuncDecl ? curFuncDecl->getBody() : nullptr);
-        // Peel address-space casts to the alloca so the op verifier sees a
-        // value produced by cir.alloca.
-        if (!*fnHasBypassStmt)
-          emission.useLifetimeOp = emitLifetimeStartOp(
-              loc, address.getUnderlyingAllocaOp().getResult());
+      if (shouldEmitLifetimeMarkers && haveInsertPoint() && !fnHasBypassStmt) {
+        emission.useLifetimeMarkers = emitLifetimeStartOp(
+            loc, address.getUnderlyingAllocaOp().getResult());
       }
     }
   } else {
@@ -196,12 +187,15 @@ CIRGenFunction::emitAutoVarAlloca(const VarDecl &d,
     assert(!cir::MissingFeatures::generateDebugInfo());
   }
 
-  if (emission.useLifetimeOp)
-    pushLifetimeEnd(address);
-
   emission.addr = address;
   setAddrOfLocalVar(&d, address);
 
+  // The lifetime marker must reference the original alloca, so peel any
+  // address-space cast back to it.
+  if (emission.useLifetimeMarkers)
+    ehStack.pushCleanup<CallLifetimeEnd>(
+        NormalEHLifetimeMarker, address.getUnderlyingAllocaOp().getResult());
+
   return emission;
 }
 
@@ -1039,15 +1033,6 @@ struct CallStackRestore final : EHScopeStack::Cleanup {
   }
 };
 
-struct CallLifetimeEnd final : EHScopeStack::Cleanup {
-  Address addr;
-  CallLifetimeEnd(Address addr) : addr(addr) {}
-  void emit(CIRGenFunction &cgf, Flags flags) override {
-    mlir::Value allocaPtr = addr.getUnderlyingAllocaOp().getResult();
-    cgf.emitLifetimeEndOp(allocaPtr.getLoc(), allocaPtr);
-  }
-};
-
 /// A cleanup which performs a partial array destroy where the end pointer is
 /// irregularly determined and must be loaded from a local.
 struct IrregularPartialArrayDestroy final : EHScopeStack::Cleanup {
@@ -1304,10 +1289,6 @@ void CIRGenFunction::pushStackRestore(CleanupKind kind, 
Address spMem) {
   ehStack.pushCleanup<CallStackRestore>(kind, spMem);
 }
 
-void CIRGenFunction::pushLifetimeEnd(Address addr) {
-  ehStack.pushCleanup<CallLifetimeEnd>(NormalEHLifetimeMarker, addr);
-}
-
 /// Enter a destroy cleanup for the given local variable.
 void CIRGenFunction::emitAutoVarTypeCleanup(
     const CIRGenFunction::AutoVarEmission &emission,
@@ -1366,16 +1347,24 @@ void CIRGenFunction::maybeEmitDeferredVarDeclInit(const 
VarDecl *vd) {
 }
 
 bool CIRGenFunction::emitLifetimeStartOp(mlir::Location loc, mlir::Value addr) 
{
-  if (!shouldEmitLifetimeOp)
+  if (!shouldEmitLifetimeMarkers)
     return false;
 
+  assert(mlir::cast<cir::PointerType>(addr.getType()).getAddrSpace() ==
+             cir::normalizeDefaultAddressSpace(getCIRAllocaAddressSpace()) &&
+         "Pointer should be in alloca address space");
+
   cir::LifetimeStartOp::create(builder, loc, addr);
   return true;
 }
 
 void CIRGenFunction::emitLifetimeEndOp(mlir::Location loc, mlir::Value addr) {
-  if (!shouldEmitLifetimeOp)
+  if (!shouldEmitLifetimeMarkers)
     return;
 
+  assert(mlir::cast<cir::PointerType>(addr.getType()).getAddrSpace() ==
+             cir::normalizeDefaultAddressSpace(getCIRAllocaAddressSpace()) &&
+         "Pointer should be in alloca address space");
+
   cir::LifetimeEndOp::create(builder, loc, addr);
 }
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp 
b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
index be1f01c606a02..ea904b197d962 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
@@ -46,12 +46,26 @@ static bool shouldEmitLifetimeMarkers(const CodeGenOptions 
&cgOpts,
   return cgOpts.OptimizationLevel != 0;
 }
 
+/// Does the statement tree rooted at \p s contain a label, switch, or indirect
+/// goto that could bypass a local's initialization? A coarse stand-in for
+/// classic CodeGen's per-decl bypass analysis (PR28267).
+static bool functionMightHaveBypass(const Stmt *s) {
+  if (!s)
+    return false;
+  if (isa<LabelStmt, SwitchStmt, IndirectGotoStmt>(s))
+    return true;
+  for (const Stmt *child : s->children())
+    if (functionMightHaveBypass(child))
+      return true;
+  return false;
+}
+
 CIRGenFunction::CIRGenFunction(CIRGenModule &cgm, CIRGenBuilderTy &builder,
                                bool suppressNewContext)
     : CIRGenTypeCache(cgm), cgm{cgm}, builder(builder) {
   ehStack.setCGF(this);
-  shouldEmitLifetimeOp = shouldEmitLifetimeMarkers(cgm.getCodeGenOpts(),
-                                                   getContext().getLangOpts());
+  shouldEmitLifetimeMarkers = CIRGen::shouldEmitLifetimeMarkers(
+      cgm.getCodeGenOpts(), getContext().getLangOpts());
 }
 
 CIRGenFunction::~CIRGenFunction() {}
@@ -791,6 +805,9 @@ cir::FuncOp CIRGenFunction::generateCode(clang::GlobalDecl 
gd, cir::FuncOp fn,
     if (body && isa_and_nonnull<CoroutineBodyStmt>(body))
       llvm::append_range(fnArgs, funcDecl->parameters());
 
+    if (shouldEmitLifetimeMarkers)
+      fnHasBypassStmt = functionMightHaveBypass(body);
+
     if (isa<CXXDestructorDecl>(funcDecl)) {
       emitDestructorBody(args);
     } else if (isa<CXXConstructorDecl>(funcDecl)) {
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h 
b/clang/lib/CIR/CodeGen/CIRGenFunction.h
index 5e3768a332042..10adfa8e38b65 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.h
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h
@@ -689,7 +689,7 @@ class CIRGenFunction : public CIRGenTypeCache {
     bool emittedAsOffload = false;
 
     /// True if lifetime op should be used.
-    bool useLifetimeOp = false;
+    bool useLifetimeMarkers = false;
 
     mlir::Value nrvoFlag{};
 
@@ -772,7 +772,6 @@ class CIRGenFunction : public CIRGenTypeCache {
   }
 
   void pushStackRestore(CleanupKind kind, Address spMem);
-  void pushLifetimeEnd(Address addr);
 
   /// Set the address of a local variable.
   void setAddrOfLocalVar(const clang::VarDecl *vd, Address addr) {
@@ -2706,10 +2705,10 @@ class CIRGenFunction : public CIRGenTypeCache {
 private:
   QualType getVarArgType(const Expr *arg);
 
-  bool shouldEmitLifetimeOp = false;
+  bool shouldEmitLifetimeMarkers = false;
   /// Set when the current function has a goto/switch that may bypass a local's
   /// init; lifetime markers are then suppressed. See functionMightHaveBypass.
-  std::optional<bool> fnHasBypassStmt;
+  bool fnHasBypassStmt = false;
 
   class InlinedInheritingConstructorScope {
   public:
diff --git a/clang/test/CIR/CodeGen/lifetime-marker.cpp 
b/clang/test/CIR/CodeGen/lifetime-marker.cpp
index 3e8e54dbce096..0e2642e2223f7 100644
--- a/clang/test/CIR/CodeGen/lifetime-marker.cpp
+++ b/clang/test/CIR/CodeGen/lifetime-marker.cpp
@@ -27,6 +27,11 @@ void f() {
 // LLVM:         call void @llvm.lifetime.start.p0(ptr %[[X]])
 // LLVM:         call void @llvm.lifetime.end.p0(ptr %[[X]])
 
+// Without optimization no lifetime markers are emitted. Checked per function 
so
+// a regression in a single function can't hide behind a passing global check.
+// O0-LABEL: cir.func{{.*}} @_Z1fv()
+// O0-NOT:     cir.lifetime
+
 struct S {
   ~S();
 };
@@ -50,5 +55,58 @@ void g() {
 // LLVM:         call void @_ZN1SD1Ev(ptr {{.*}} %[[S]])
 // LLVM:         call void @llvm.lifetime.end.p0(ptr %[[S]])
 
-// Without optimization no lifetime markers are emitted at all.
-// O0-NOT: cir.lifetime
+// O0-LABEL: cir.func{{.*}} @_Z1gv()
+// O0-NOT:     cir.lifetime
+
+// A statement that can bypass a local's initialization -- switch, label, or
+// indirect goto -- miscompiles under stack coloring (PR28267). Lacking classic
+// CodeGen's per-decl bypass analysis, we conservatively drop lifetime markers
+// for the *whole* function whenever any such statement is present, even at -O2
+// and even for locals (like `x` below) that are not themselves bypassed.
+
+void bypass_switch(int n) {
+  int x;
+  use(x);
+  switch (n) {
+  case 0:
+    return;
+  }
+}
+
+// CIR-LABEL: cir.func{{.*}}bypass_switch
+// CIR-NOT:     cir.lifetime
+
+// LLVM-LABEL: define{{.*}}bypass_switch
+// LLVM-NOT:    call void @llvm.lifetime
+
+// O0-LABEL: cir.func{{.*}}bypass_switch
+// O0-NOT:     cir.lifetime
+
+void bypass_label(int n) {
+  int x;
+  use(x);
+target:
+  if (n)
+    goto target;
+}
+
+// CIR-LABEL: cir.func{{.*}}bypass_label
+// CIR-NOT:     cir.lifetime
+
+// O0-LABEL: cir.func{{.*}}bypass_label
+// O0-NOT:     cir.lifetime
+
+void bypass_indirect_goto() {
+  int x;
+  use(x);
+  void *p = &&target;
+  goto *p;
+target:
+  return;
+}
+
+// CIR-LABEL: cir.func{{.*}}bypass_indirect_goto
+// CIR-NOT:     cir.lifetime
+
+// O0-LABEL: cir.func{{.*}}bypass_indirect_goto
+// O0-NOT:     cir.lifetime

>From 8a5fe3c014d7e486472ffc9d62764ff7f7547e83 Mon Sep 17 00:00:00 2001
From: E00N777 <[email protected]>
Date: Fri, 3 Jul 2026 17:28:54 +0800
Subject: [PATCH 3/4] temp

---
 clang/test/CIR/CodeGen/lifetime-marker.cpp | 63 ++++++++++++++++++++++
 1 file changed, 63 insertions(+)

diff --git a/clang/test/CIR/CodeGen/lifetime-marker.cpp 
b/clang/test/CIR/CodeGen/lifetime-marker.cpp
index 0e2642e2223f7..2ea6fc207ec3f 100644
--- a/clang/test/CIR/CodeGen/lifetime-marker.cpp
+++ b/clang/test/CIR/CodeGen/lifetime-marker.cpp
@@ -4,6 +4,10 @@
 // RUN: FileCheck --input-file=%t.ll %s --check-prefix=LLVM
 // RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o 
%t-o0.cir
 // RUN: FileCheck --input-file=%t-o0.cir %s --check-prefix=O0
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O2 -fcxx-exceptions 
-fexceptions -fclangir -emit-cir %s -o %t-eh.cir
+// RUN: FileCheck --input-file=%t-eh.cir %s --check-prefix=CIR-EH
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O2 -fcxx-exceptions 
-fexceptions -fclangir -emit-llvm -disable-llvm-passes %s -o %t-eh.ll
+// RUN: FileCheck --input-file=%t-eh.ll %s --check-prefix=LLVM-EH
 
 void use(int);
 
@@ -110,3 +114,62 @@ void bypass_indirect_goto() {
 
 // O0-LABEL: cir.func{{.*}}bypass_indirect_goto
 // O0-NOT:     cir.lifetime
+
+// A local declared inside the body region of an if statement is scoped to that
+// region: its lifetime.start/end are nested in the region and the end marker
+// is the region's cleanup, not the function's.
+void if_body(int n) {
+  if (n) {
+    int x;
+    use(x);
+  }
+}
+
+// CIR-LABEL: cir.func{{.*}} @_Z7if_bodyi
+// CIR:         cir.if
+// CIR:           %[[X:.*]] = cir.alloca "x" {{.*}} : !cir.ptr<!s32i>
+// CIR:           cir.lifetime.start %[[X]] : !cir.ptr<!s32i>
+// CIR:           cir.cleanup.scope {
+// CIR:           } cleanup normal {
+// CIR:             cir.lifetime.end %[[X]] : !cir.ptr<!s32i>
+// CIR:           }
+
+// LLVM-LABEL: define{{.*}} void @_Z7if_bodyi
+// LLVM:         %[[X:.*]] = alloca i32
+// LLVM:         call void @llvm.lifetime.start.p0(ptr %[[X]])
+// LLVM:         call void @llvm.lifetime.end.p0(ptr %[[X]])
+
+// O0-LABEL: cir.func{{.*}} @_Z7if_bodyi
+// O0-NOT:     cir.lifetime
+
+// With exceptions enabled the scope cleanup runs on both the normal and the
+// exceptional edge, so the cleanup kind is "all" and lifetime.end is emitted 
in
+// the EH cleanup handler (the landing pad) as well as on the normal path. The
+// may_throw() call is what forces an unwind edge.
+void may_throw();
+
+void eh_cleanup() {
+  int x;
+  may_throw();
+  use(x);
+}
+
+// CIR-EH-LABEL: cir.func{{.*}} @_Z10eh_cleanupv
+// CIR-EH:         %[[X:.*]] = cir.alloca "x" {{.*}} : !cir.ptr<!s32i>
+// CIR-EH:         cir.lifetime.start %[[X]] : !cir.ptr<!s32i>
+// CIR-EH:         cir.cleanup.scope {
+// CIR-EH:           cir.call @_Z9may_throwv()
+// CIR-EH:         } cleanup all {
+// CIR-EH:           cir.lifetime.end %[[X]] : !cir.ptr<!s32i>
+// CIR-EH:         }
+
+// LLVM-EH-LABEL: define{{.*}} void @_Z10eh_cleanupv()
+// LLVM-EH:         %[[X:.*]] = alloca i32
+// LLVM-EH:         call void @llvm.lifetime.start.p0(ptr %[[X]])
+// LLVM-EH:         invoke void @_Z9may_throwv()
+// The normal-path end marker.
+// LLVM-EH:         call void @llvm.lifetime.end.p0(ptr %[[X]])
+// The EH cleanup handler runs the same end marker on the unwind path.
+// LLVM-EH:         landingpad { ptr, i32 }
+// LLVM-EH-NEXT:      cleanup
+// LLVM-EH:         call void @llvm.lifetime.end.p0(ptr %[[X]])

>From 4a21a1db0bb5be28baddb0884401ab002af34aa8 Mon Sep 17 00:00:00 2001
From: E00N777 <[email protected]>
Date: Wed, 8 Jul 2026 14:33:22 +0800
Subject: [PATCH 4/4] [CIR]  Suppress lifetime markers for loop condition
 variables and add tests

---
 clang/include/clang/CIR/MissingFeatures.h  |  1 +
 clang/lib/CIR/CodeGen/CIRGenDecl.cpp       |  3 +-
 clang/lib/CIR/CodeGen/CIRGenFunction.h     |  6 ++++
 clang/lib/CIR/CodeGen/CIRGenStmt.cpp       | 17 ++++++++--
 clang/test/CIR/CodeGen/lifetime-marker.cpp | 36 ++++++++++++++++++++++
 5 files changed, 60 insertions(+), 3 deletions(-)

diff --git a/clang/include/clang/CIR/MissingFeatures.h 
b/clang/include/clang/CIR/MissingFeatures.h
index c1df47a5de637..f97a7e37a6880 100644
--- a/clang/include/clang/CIR/MissingFeatures.h
+++ b/clang/include/clang/CIR/MissingFeatures.h
@@ -228,6 +228,7 @@ struct MissingFeatures {
   static bool emitConstrainedFPCall() { return false; }
   static bool emitLifetimeMarkers() { return false; }
   static bool lifetimeMarkersBypass() { return false; }
+  static bool lifetimeMarkersLoopCondVar() { return false; }
   static bool emitLValueAlignmentAssumption() { return false; }
   static bool emitNullCheckForDeleteCalls() { return false; }
   static bool emitNullabilityCheck() { return false; }
diff --git a/clang/lib/CIR/CodeGen/CIRGenDecl.cpp 
b/clang/lib/CIR/CodeGen/CIRGenDecl.cpp
index d6524bd05089e..7ffdd50af733b 100644
--- a/clang/lib/CIR/CodeGen/CIRGenDecl.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenDecl.cpp
@@ -147,7 +147,8 @@ CIRGenFunction::emitAutoVarAlloca(const VarDecl &d,
       // classic's per-decl bypass analysis, drop markers for the whole
       // function if any such statement is present.
       assert(!cir::MissingFeatures::lifetimeMarkersBypass());
-      if (shouldEmitLifetimeMarkers && haveInsertPoint() && !fnHasBypassStmt) {
+      if (shouldEmitLifetimeMarkers && haveInsertPoint() && !fnHasBypassStmt &&
+          !suppressLoopCondVarLifetime) {
         emission.useLifetimeMarkers = emitLifetimeStartOp(
             loc, address.getUnderlyingAllocaOp().getResult());
       }
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h 
b/clang/lib/CIR/CodeGen/CIRGenFunction.h
index 10adfa8e38b65..e0853b4d50000 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.h
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h
@@ -2709,6 +2709,12 @@ class CIRGenFunction : public CIRGenTypeCache {
   /// Set when the current function has a goto/switch that may bypass a local's
   /// init; lifetime markers are then suppressed. See functionMightHaveBypass.
   bool fnHasBypassStmt = false;
+  /// Set while emitting a loop condition variable's declaration. Such a
+  /// variable is destroyed and re-created on every iteration, so its
+  /// lifetime.end would have to run on both the loop's back edge and its exit
+  /// edge; the structured cond region cannot express a cleanup on both edges
+  /// yet, so lifetime markers are suppressed for these variables.
+  bool suppressLoopCondVarLifetime = false;
 
   class InlinedInheritingConstructorScope {
   public:
diff --git a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp 
b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
index 47c94cb4ec535..34f43b7a20aad 100644
--- a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
@@ -21,6 +21,7 @@
 #include "clang/AST/StmtOpenACC.h"
 #include "clang/AST/StmtOpenMP.h"
 #include "clang/CIR/MissingFeatures.h"
+#include "llvm/Support/SaveAndRestore.h"
 
 using namespace clang;
 using namespace clang::CIRGen;
@@ -994,8 +995,14 @@ mlir::LogicalResult CIRGenFunction::emitForStmt(const 
ForStmt &s) {
           if (s.getCond()) {
             // If the for statement has a condition scope,
             // emit the local variable declaration.
-            if (s.getConditionVariable())
+            if (s.getConditionVariable()) {
+              // A lifetime.end for the condition variable would have to run
+              // on both the back edge and the exit edge, which the cond
+              // region cannot express; suppress its lifetime markers.
+              assert(!cir::MissingFeatures::lifetimeMarkersLoopCondVar());
+              llvm::SaveAndRestore suppress(suppressLoopCondVarLifetime, true);
               emitDecl(*s.getConditionVariable());
+            }
             // C99 6.8.5p2/p4: The first substatement is executed if the
             // expression compares unequal to 0. The condition must be a
             // scalar type.
@@ -1104,8 +1111,14 @@ mlir::LogicalResult CIRGenFunction::emitWhileStmt(const 
WhileStmt &s) {
           mlir::Value condVal;
           // If the for statement has a condition scope,
           // emit the local variable declaration.
-          if (s.getConditionVariable())
+          if (s.getConditionVariable()) {
+            // A lifetime.end for the condition variable would have to run
+            // on both the back edge and the exit edge, which the cond
+            // region cannot express; suppress its lifetime markers.
+            assert(!cir::MissingFeatures::lifetimeMarkersLoopCondVar());
+            llvm::SaveAndRestore suppress(suppressLoopCondVarLifetime, true);
             emitDecl(*s.getConditionVariable());
+          }
           // C99 6.8.5p2/p4: The first substatement is executed if the
           // expression compares unequal to 0. The condition must be a
           // scalar type.
diff --git a/clang/test/CIR/CodeGen/lifetime-marker.cpp 
b/clang/test/CIR/CodeGen/lifetime-marker.cpp
index 2ea6fc207ec3f..9fea56c5a307f 100644
--- a/clang/test/CIR/CodeGen/lifetime-marker.cpp
+++ b/clang/test/CIR/CodeGen/lifetime-marker.cpp
@@ -173,3 +173,39 @@ void eh_cleanup() {
 // LLVM-EH:         landingpad { ptr, i32 }
 // LLVM-EH-NEXT:      cleanup
 // LLVM-EH:         call void @llvm.lifetime.end.p0(ptr %[[X]])
+
+// A loop condition variable is destroyed and re-created on every iteration
+// (C++ [stmt.while]p2), so its lifetime.end would have to run on both the
+// loop's back edge and its exit edge. The structured cond region cannot
+// express a cleanup on both edges (a cleanup scope would trap the loop's
+// condition terminator inside it), so lifetime markers are suppressed for
+// variables declared in a while or for condition.
+int source();
+
+void while_condvar() {
+  while (int c = source())
+    use(c);
+}
+
+// CIR-LABEL: cir.func{{.*}}while_condvar
+// CIR-NOT:     cir.lifetime
+
+// LLVM-LABEL: define{{.*}}while_condvar
+// LLVM-NOT:    call void @llvm.lifetime
+
+// O0-LABEL: cir.func{{.*}}while_condvar
+// O0-NOT:     cir.lifetime
+
+// CIR-EH-LABEL: cir.func{{.*}}while_condvar
+// CIR-EH-NOT:     cir.lifetime
+
+void for_condvar() {
+  for (; int c = source();)
+    use(c);
+}
+
+// CIR-LABEL: cir.func{{.*}}for_condvar
+// CIR-NOT:     cir.lifetime
+
+// O0-LABEL: cir.func{{.*}}for_condvar
+// O0-NOT:     cir.lifetime

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to