Author: Jiahao Guo
Date: 2026-08-23T17:16:50+08:00
New Revision: 32e47a2c6c7d874b2469eef519879073de50c11c

URL: 
https://github.com/llvm/llvm-project/commit/32e47a2c6c7d874b2469eef519879073de50c11c
DIFF: 
https://github.com/llvm/llvm-project/commit/32e47a2c6c7d874b2469eef519879073de50c11c.diff

LOG: [CIR] Emit lifetime markers for automatic variables (#206695)

### summary 

This is a follow up of : #199599

Per the consensus from the PR #199599, this implements lifetime-marker
emission in ClangIR that closely follows classic CodeGen.

Emit lifetime markers (cir.lifetime.start/end) for automatic variables
in ClangIR.

- Gated by shouldEmitLifetimeMarkers (optimized builds or
ASan-use-after-scope/
  HWASan/MSan/MemtagStack), like classic CodeGen.
- start at the decl; end as a NormalEHLifetimeMarker cleanup (after the
destructor);
both target the underlying alloca. Wired through the EH stack so they
don't force
  a landing pad.
- Bypass analysis (PR28267) not ported; conservatively suppress markers
for functions
  with a label/switch/indirect goto.
- Test: scalar, destructor ordering, no markers at -O0.

Assisted by Claude Opus4.8

Added: 
    clang/test/CIR/CodeGen/lifetime-marker.cpp

Modified: 
    clang/include/clang/CIR/MissingFeatures.h
    clang/lib/CIR/CodeGen/CIRGenCleanup.cpp
    clang/lib/CIR/CodeGen/CIRGenCleanup.h
    clang/lib/CIR/CodeGen/CIRGenDecl.cpp
    clang/lib/CIR/CodeGen/CIRGenFunction.cpp
    clang/lib/CIR/CodeGen/CIRGenFunction.h
    clang/lib/CIR/CodeGen/CIRGenStmt.cpp

Removed: 
    


################################################################################
diff  --git a/clang/include/clang/CIR/MissingFeatures.h 
b/clang/include/clang/CIR/MissingFeatures.h
index ea14bf20713b2..184cb833b3ffb 100644
--- a/clang/include/clang/CIR/MissingFeatures.h
+++ b/clang/include/clang/CIR/MissingFeatures.h
@@ -228,6 +228,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 07cbe34409ea4..8103ef37f9225 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp
@@ -364,7 +364,7 @@ void *EHScopeStack::pushCleanup(CleanupKind kind, size_t 
size) {
   }
 
   // While emitting a loop's condition variable, suppress cir.cleanup.scope
-  // creation. The variable's destructor is captured on the EH stack and later
+  // creation. The variable's cleanups are captured on the EH stack and later
   // emitted into the loop op's per-iteration cleanup region.
   if (capturingLoopConditionCleanups)
     skipCleanupScope = true;
@@ -406,7 +406,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 &&
@@ -451,7 +451,7 @@ bool EHScopeStack::requiresCatchOrCleanup() const {
     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;
       }
     }
@@ -743,10 +743,12 @@ void CIRGenFunction::emitLoopConditionCleanups(
     if (scope.isEHCleanup())
       cleanupFlags.setIsEHCleanupKind();
 
-    // The condition variable's cleanup is guarded by an active flag that is
-    // false while its initializer runs, so a throwing initializer does not
-    // destroy the not-yet-constructed variable. The single guarded emission
-    // serves both the normal per-iteration exit and the EH unwind path.
+    // A condition variable's destructor cleanup is guarded by an active flag
+    // that is false while its initializer runs, so a throwing initializer does
+    // not destroy the not-yet-constructed variable. The lifetime-end cleanup
+    // has no flag because its lifetime starts before initialization. Each
+    // emission serves both the normal per-iteration exit and the EH unwind
+    // path.
     Address activeFlag = scope.getActiveFlag();
 
     // Copy the cleanup emission data out before popping, since popCleanup

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 bd7a249f1f67e..c46f40d0c0f2c 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,17 @@
 using namespace clang;
 using namespace clang::CIRGen;
 
+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,
                                   mlir::OpBuilder::InsertPoint ip) {
@@ -129,6 +142,15 @@ 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 (shouldEmitLifetimeMarkersForAutoVar() && haveInsertPoint()) {
+        emission.useLifetimeMarkers = emitLifetimeStartOp(
+            loc, address.getUnderlyingAllocaOp().getResult());
+      }
     }
   } else {
     // Non-constant size type
@@ -168,6 +190,12 @@ CIRGenFunction::emitAutoVarAlloca(const VarDecl &d,
   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;
 }
 
@@ -361,11 +389,11 @@ void CIRGenFunction::emitAutoVarDecl(const VarDecl &d) {
 void CIRGenFunction::emitLoopConditionVariable(
     const VarDecl &d, DeferredLoopConditionCleanup &condCleanup) {
   // A condition variable always has automatic storage duration, so this
-  // mirrors the auto-var path of emitVarDecl/emitAutoVarDecl. The alloca and
-  // initializer are emitted with capturing disabled so that any cleanups they
-  // introduce get their normal cir.cleanup.scope handling; only the variable's
-  // own destructor cleanup is captured for the loop's per-iteration cleanup
-  // region.
+  // mirrors the auto-var path of emitVarDecl/emitAutoVarDecl. Capture the
+  // lifetime-end cleanup pushed while emitting the alloca, but emit the
+  // initializer with capturing disabled so its own cleanups get their normal
+  // cir.cleanup.scope handling. The variable's destructor cleanup is captured
+  // separately after initialization.
   assert(d.hasLocalStorage() && "loop condition variable is not local");
 
   // Mirror the diagnostic emitted by emitVarDecl on the automatic-storage 
path.
@@ -376,7 +404,10 @@ void CIRGenFunction::emitLoopConditionVariable(
                  "emitLoopConditionVariable: OpenCL local address space");
 
   CIRGenFunction::VarDeclContext varDeclCtx{*this, &d};
-  CIRGenFunction::AutoVarEmission emission = emitAutoVarAlloca(d);
+  CIRGenFunction::AutoVarEmission emission = [&] {
+    DeferredLoopConditionCleanup::CaptureScope capture(condCleanup);
+    return emitAutoVarAlloca(d);
+  }();
 
   // The condition variable's destructor is captured into the loop op's
   // per-iteration cleanup region, which structurally spans the initializer.
@@ -388,8 +419,6 @@ void CIRGenFunction::emitLoopConditionVariable(
   // completes. The flag is stored to on every iteration, so it also resets
   // correctly across iterations.
   bool needsCleanup = d.needsDestruction(getContext()) != QualType::DK_none;
-  // We will also need cleanup if lifetime markers are enabled.
-  assert(!cir::MissingFeatures::emitLifetimeMarkers());
   Address activeFlag = Address::invalid();
   if (needsCleanup) {
     mlir::Location loc = getLoc(d.getSourceRange());
@@ -408,8 +437,10 @@ void CIRGenFunction::emitLoopConditionVariable(
     builder.createFlagStore(loc, true, activeFlag.getPointer());
   }
 
-  DeferredLoopConditionCleanup::CaptureScope capture(condCleanup);
-  emitAutoVarCleanups(emission);
+  {
+    DeferredLoopConditionCleanup::CaptureScope capture(condCleanup);
+    emitAutoVarCleanups(emission);
+  }
 
   if (needsCleanup)
     initFullExprCleanupWithFlag(activeFlag);
@@ -1372,3 +1403,26 @@ void CIRGenFunction::maybeEmitDeferredVarDeclInit(const 
VarDecl *vd) {
         emitVarDecl(*hd);
   }
 }
+
+bool CIRGenFunction::emitLifetimeStartOp(mlir::Location loc, mlir::Value addr) 
{
+  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 (!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 b099a4a2e72bf..8301627ad8123 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
@@ -28,11 +28,45 @@
 
 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;
+}
+
+/// 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),
       curFPFeatures(cgm.getLangOpts()) {
   ehStack.setCGF(this);
+  shouldEmitLifetimeMarkers = CIRGen::shouldEmitLifetimeMarkers(
+      cgm.getCodeGenOpts(), getContext().getLangOpts());
 }
 
 CIRGenFunction::~CIRGenFunction() {}
@@ -748,6 +782,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 8da7ea2c963c3..e738c3b1fb72d 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.h
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h
@@ -739,6 +739,9 @@ class CIRGenFunction : public CIRGenTypeCache {
     /// have the same sort of alloca initialization.
     bool emittedAsOffload = false;
 
+    /// True if lifetime op should be used.
+    bool useLifetimeMarkers = false;
+
     mlir::Value nrvoFlag{};
 
     struct Invalid {};
@@ -1366,8 +1369,8 @@ class CIRGenFunction : public CIRGenTypeCache {
     void operator=(const FullExprCleanupScope &) = delete;
   };
 
-  /// Captures the destructor cleanup for a loop's condition variable so that 
it
-  /// can be emitted into the loop op's per-iteration cleanup region.
+  /// Captures cleanups for a loop's condition variable so that they can be
+  /// emitted into the loop op's per-iteration cleanup region.
   class DeferredLoopConditionCleanup {
     CIRGenFunction &cgf;
     EHScopeStack::stable_iterator depth;
@@ -1387,8 +1390,8 @@ class CIRGenFunction : public CIRGenTypeCache {
     public:
       explicit CaptureScope(DeferredLoopConditionCleanup &scope)
           : ehStack(scope.cgf.ehStack) {
-        // Capturing wraps only the condition variable's own destructor push,
-        // which emits no nested code, so it can never already be active.
+        // Capture scopes deliberately wrap individual cleanup-producing
+        // operations, so they must never nest.
         assert(!ehStack.isCapturingLoopConditionCleanups() &&
                "loop condition cleanup capturing should not nest");
         if (scope.active)
@@ -1648,6 +1651,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);
@@ -2821,6 +2827,15 @@ class CIRGenFunction : public CIRGenTypeCache {
 private:
   QualType getVarArgType(const Expr *arg);
 
+  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.
+  bool fnHasBypassStmt = false;
+
+  bool shouldEmitLifetimeMarkersForAutoVar() const {
+    return shouldEmitLifetimeMarkers && !fnHasBypassStmt;
+  }
+
   class InlinedInheritingConstructorScope {
   public:
     InlinedInheritingConstructorScope(CIRGenFunction &cgf, GlobalDecl gd)

diff  --git a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp 
b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
index 28c82794614d5..e9f5e466c63d2 100644
--- a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
@@ -1021,15 +1021,15 @@ mlir::LogicalResult CIRGenFunction::emitForStmt(const 
ForStmt &s) {
         return mlir::failure();
     assert(!cir::MissingFeatures::loopInfoStack());
 
-    // If the condition variable has a non-trivial destructor, its lifetime is
-    // a single iteration, so capture its cleanup and emit it into the loop's
+    // A condition variable's lifetime is a single iteration, so capture its
+    // destructor and lifetime-end cleanups and emit them into the loop's
     // per-iteration cleanup region. This scope is constructed after the
-    // init-statement so its cleanups are not captured.
+    // init-statement so the init-statement's cleanups are not captured.
     const VarDecl *condVar = s.getConditionVariable();
     bool needsCondCleanup =
-        condVar && condVar->needsDestruction(getContext()) != 
QualType::DK_none;
-    // We will also need cleanup if lifetime markers are enabled.
-    assert(!cir::MissingFeatures::emitLifetimeMarkers());
+        condVar &&
+        (condVar->needsDestruction(getContext()) != QualType::DK_none ||
+         shouldEmitLifetimeMarkersForAutoVar());
     DeferredLoopConditionCleanup loopCondScope(*this, needsCondCleanup);
 
     auto condBuilder = [&](mlir::OpBuilder &b, mlir::Location loc) {
@@ -1154,14 +1154,14 @@ mlir::LogicalResult CIRGenFunction::emitWhileStmt(const 
WhileStmt &s) {
     mlir::LogicalResult loopRes = mlir::success();
     assert(!cir::MissingFeatures::loopInfoStack());
 
-    // If the condition variable has a non-trivial destructor, its lifetime is
-    // a single iteration, so capture its cleanup and emit it into the loop's
+    // A condition variable's lifetime is a single iteration, so capture its
+    // destructor and lifetime-end cleanups and emit them into the loop's
     // per-iteration cleanup region.
     const VarDecl *condVar = s.getConditionVariable();
     bool needsCondCleanup =
-        condVar && condVar->needsDestruction(getContext()) != 
QualType::DK_none;
-    // We will also need cleanup if lifetime markers are enabled.
-    assert(!cir::MissingFeatures::emitLifetimeMarkers());
+        condVar &&
+        (condVar->needsDestruction(getContext()) != QualType::DK_none ||
+         shouldEmitLifetimeMarkersForAutoVar());
     DeferredLoopConditionCleanup loopCondScope(*this, needsCondCleanup);
 
     auto condBuilder = [&](mlir::OpBuilder &b, mlir::Location loc) {

diff  --git a/clang/test/CIR/CodeGen/lifetime-marker.cpp 
b/clang/test/CIR/CodeGen/lifetime-marker.cpp
new file mode 100644
index 0000000000000..ce661aafe4498
--- /dev/null
+++ b/clang/test/CIR/CodeGen/lifetime-marker.cpp
@@ -0,0 +1,281 @@
+// 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 --implicit-check-not "cir.lifetime"
+// 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);
+
+// 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:           cir.call @_Z3usei
+// 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 @_Z3usei
+// 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]])
+
+// 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
+
+void bypass_label(int n) {
+  int x;
+  use(x);
+target:
+  if (n)
+    goto target;
+}
+
+// CIR-LABEL: cir.func{{.*}}bypass_label
+// CIR-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
+
+// 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);
+  }
+  use(n);
+}
+
+// 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:             cir.call @_Z3usei
+// CIR:           } cleanup normal {
+// CIR:             cir.lifetime.end %[[X]] : !cir.ptr<!s32i>
+// CIR-NEXT:        cir.yield
+// CIR-NEXT:      }
+// CIR-NEXT:    }
+// CIR:         cir.call @_Z3usei
+
+// LLVM-LABEL: define{{.*}} void @_Z7if_bodyi
+// LLVM:         %[[X:.*]] = alloca i32
+// LLVM:         br i1 %{{.*}}, label %[[IF_BODY:[0-9]+]], label 
%[[IF_END:[0-9]+]]
+// LLVM:       [[IF_BODY]]:
+// LLVM:         call void @llvm.lifetime.start.p0(ptr %[[X]])
+// LLVM:         call void @_Z3usei
+// LLVM:         call void @llvm.lifetime.end.p0(ptr %[[X]])
+// LLVM:       [[IF_END]]:
+// LLVM:         call void @_Z3usei
+
+// 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]])
+
+// A loop condition variable is destroyed and re-created on every iteration
+// (C++ [stmt.while]p2). Its lifetime starts in the condition region and ends 
in
+// the loop cleanup region, which runs on both the back edge and the exit edge.
+int source();
+
+void while_condvar() {
+  while (int c = source())
+    use(c);
+}
+
+// CIR-LABEL: cir.func{{.*}} @_Z13while_condvarv
+// CIR:           %[[C:.*]] = cir.alloca "c" {{.*}} : !cir.ptr<!s32i>
+// CIR:           cir.while {
+// CIR:             cir.lifetime.start %[[C]] : !cir.ptr<!s32i>
+// CIR:           } do {
+// CIR:           } cleanup normal {
+// CIR:             cir.lifetime.end %[[C]] : !cir.ptr<!s32i>
+
+// LLVM-LABEL: define{{.*}} void @_Z13while_condvarv
+// LLVM:         call void @llvm.lifetime.start.p0(ptr %[[C:.*]])
+// LLVM:         call void @llvm.lifetime.end.p0(ptr %[[C]])
+
+// CIR-EH-LABEL: cir.func{{.*}} @_Z13while_condvarv
+// CIR-EH:         %[[C:.*]] = cir.alloca "c" {{.*}} : !cir.ptr<!s32i>
+// CIR-EH:         cir.while {
+// CIR-EH:           cir.lifetime.start %[[C]] : !cir.ptr<!s32i>
+// CIR-EH:         } do {
+// CIR-EH:         } cleanup all {
+// CIR-EH:           cir.lifetime.end %[[C]] : !cir.ptr<!s32i>
+
+// LLVM-EH-LABEL: define{{.*}} void @_Z13while_condvarv
+// LLVM-EH:         call void @llvm.lifetime.start.p0(ptr %[[C:.*]])
+// LLVM-EH:         call void @llvm.lifetime.end.p0(ptr %[[C]])
+// LLVM-EH:         landingpad { ptr, i32 }
+// LLVM-EH-NEXT:      cleanup
+// LLVM-EH:         call void @llvm.lifetime.end.p0(ptr %[[C]])
+
+void for_condvar() {
+  for (; int c = source();)
+    use(c);
+}
+
+// CIR-LABEL: cir.func{{.*}} @_Z11for_condvarv
+// CIR:           %[[C:.*]] = cir.alloca "c" {{.*}} : !cir.ptr<!s32i>
+// CIR:           cir.for : cond {
+// CIR:             cir.lifetime.start %[[C]] : !cir.ptr<!s32i>
+// CIR:           } body {
+// CIR:           } step {
+// CIR:           } cleanup normal {
+// CIR:             cir.lifetime.end %[[C]] : !cir.ptr<!s32i>
+
+// LLVM-LABEL: define{{.*}} void @_Z11for_condvarv
+// LLVM:         call void @llvm.lifetime.start.p0(ptr %[[C:.*]])
+// LLVM:         call void @llvm.lifetime.end.p0(ptr %[[C]])
+
+// CIR-EH-LABEL: cir.func{{.*}} @_Z11for_condvarv
+// CIR-EH:         %[[C:.*]] = cir.alloca "c" {{.*}} : !cir.ptr<!s32i>
+// CIR-EH:         cir.for : cond {
+// CIR-EH:           cir.lifetime.start %[[C]] : !cir.ptr<!s32i>
+// CIR-EH:         } body {
+// CIR-EH:         } step {
+// CIR-EH:         } cleanup all {
+// CIR-EH:           cir.lifetime.end %[[C]] : !cir.ptr<!s32i>
+
+// LLVM-EH-LABEL: define{{.*}} void @_Z11for_condvarv
+// LLVM-EH:         call void @llvm.lifetime.start.p0(ptr %[[C:.*]])
+// LLVM-EH:         call void @llvm.lifetime.end.p0(ptr %[[C]])
+// LLVM-EH:         landingpad { ptr, i32 }
+// LLVM-EH-NEXT:      cleanup
+// LLVM-EH:         call void @llvm.lifetime.end.p0(ptr %[[C]])
+
+struct LoopCond {
+  operator bool() const;
+  ~LoopCond();
+};
+
+// A non-trivial condition variable runs its destructor before lifetime.end in
+// the loop cleanup region.
+void while_record_condvar() {
+  while (LoopCond c{}) {}
+}
+
+// CIR-LABEL: cir.func{{.*}} @_Z20while_record_condvarv
+// CIR:           %[[C:.*]] = cir.alloca "c" {{.*}} : !cir.ptr<!rec_LoopCond>
+// CIR:           cir.while {
+// CIR:             cir.lifetime.start %[[C]] : !cir.ptr<!rec_LoopCond>
+// CIR:           } do {
+// CIR:           } cleanup normal {
+// CIR:             cir.call @_ZN8LoopCondD1Ev(%[[C]])
+// CIR:             cir.lifetime.end %[[C]] : !cir.ptr<!rec_LoopCond>
+
+// LLVM-LABEL: define{{.*}} void @_Z20while_record_condvarv
+// LLVM:         call void @llvm.lifetime.start.p0(ptr %[[C:.*]])
+// LLVM:         call void @_ZN8LoopCondD1Ev(ptr {{.*}} %[[C]])
+// LLVM:         call void @llvm.lifetime.end.p0(ptr %[[C]])
+
+// CIR-EH-LABEL: cir.func{{.*}} @_Z20while_record_condvarv
+// CIR-EH:         %[[C:.*]] = cir.alloca "c" {{.*}} : !cir.ptr<!rec_LoopCond>
+// CIR-EH:         cir.while {
+// CIR-EH:           cir.lifetime.start %[[C]] : !cir.ptr<!rec_LoopCond>
+// CIR-EH:         } do {
+// CIR-EH:         } cleanup all {
+// CIR-EH:           cir.call @_ZN8LoopCondD1Ev(%[[C]])
+// CIR-EH:           cir.lifetime.end %[[C]] : !cir.ptr<!rec_LoopCond>
+
+// LLVM-EH-LABEL: define{{.*}} void @_Z20while_record_condvarv
+// LLVM-EH:         call void @llvm.lifetime.start.p0(ptr %[[C:.*]])
+// LLVM-EH:         call void @_ZN8LoopCondD1Ev(ptr {{.*}} %[[C]])
+// LLVM-EH:         call void @llvm.lifetime.end.p0(ptr %[[C]])
+// LLVM-EH:         landingpad { ptr, i32 }
+// LLVM-EH-NEXT:      cleanup
+// LLVM-EH:         call void @_ZN8LoopCondD1Ev(ptr {{.*}} %[[C]])
+// LLVM-EH:         call void @llvm.lifetime.end.p0(ptr %[[C]])


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

Reply via email to