https://github.com/E00N777 updated https://github.com/llvm/llvm-project/pull/206695
>From 3889e950b6e9e61716f6d888ebe79836779fc67e Mon Sep 17 00:00:00 2001 From: E00N777 <[email protected]> Date: Tue, 30 Jun 2026 17:40:25 +0800 Subject: [PATCH 1/5] [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 02475b70c5dcd..20369d1331eb9 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..15aec51f724be 100644 --- a/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp @@ -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 && @@ -450,8 +450,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 e17550a8c1668..25a67d3bd027a 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); @@ -1063,6 +1097,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 { @@ -1319,6 +1362,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, @@ -1375,3 +1422,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 66e7b6d5061df..c729dceb8cddf 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp @@ -28,11 +28,31 @@ 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), curFPFeatures(cgm.getLangOpts()) { 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 d318338187f12..d5b5aa0aa9305 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.h +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h @@ -723,6 +723,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 {}; @@ -795,6 +798,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) { @@ -1632,6 +1636,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); @@ -2796,6 +2803,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 c04cbcefe935258a89508cf332c9d21104571dc0 Mon Sep 17 00:00:00 2001 From: E00N777 <[email protected]> Date: Thu, 2 Jul 2026 18:32:46 +0800 Subject: [PATCH 2/5] [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 15aec51f724be..51d41cbd4fcd7 100644 --- a/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp @@ -450,6 +450,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 25a67d3bd027a..d7e4e0cb03da3 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; } @@ -1097,15 +1091,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 { @@ -1362,10 +1347,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, @@ -1424,16 +1405,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 c729dceb8cddf..d31f639cde908 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp @@ -46,13 +46,27 @@ 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), curFPFeatures(cgm.getLangOpts()) { ehStack.setCGF(this); - shouldEmitLifetimeOp = shouldEmitLifetimeMarkers(cgm.getCodeGenOpts(), - getContext().getLangOpts()); + shouldEmitLifetimeMarkers = CIRGen::shouldEmitLifetimeMarkers( + cgm.getCodeGenOpts(), getContext().getLangOpts()); } CIRGenFunction::~CIRGenFunction() {} @@ -765,6 +779,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 d5b5aa0aa9305..cc26f7decfbd0 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.h +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h @@ -724,7 +724,7 @@ class CIRGenFunction : public CIRGenTypeCache { bool emittedAsOffload = false; /// True if lifetime op should be used. - bool useLifetimeOp = false; + bool useLifetimeMarkers = false; mlir::Value nrvoFlag{}; @@ -798,7 +798,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) { @@ -2803,10 +2802,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 = &⌖ + 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 7080a1b13258348107bd6bc5d923a8ce81d2e191 Mon Sep 17 00:00:00 2001 From: E00N777 <[email protected]> Date: Wed, 8 Jul 2026 15:22:05 +0800 Subject: [PATCH 3/5] [CIR] Add lifetime marker tests for if-body/EH paths and suppress markers for loop condition variables --- clang/lib/CIR/CodeGen/CIRGenDecl.cpp | 23 ++--- clang/lib/CIR/CodeGen/CIRGenFunction.h | 4 + clang/lib/CIR/CodeGen/CIRGenStmt.cpp | 26 +++--- clang/test/CIR/CodeGen/lifetime-marker.cpp | 99 ++++++++++++++++++++++ 4 files changed, 129 insertions(+), 23 deletions(-) diff --git a/clang/lib/CIR/CodeGen/CIRGenDecl.cpp b/clang/lib/CIR/CodeGen/CIRGenDecl.cpp index d7e4e0cb03da3..f00bfc672a866 100644 --- a/clang/lib/CIR/CodeGen/CIRGenDecl.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenDecl.cpp @@ -147,7 +147,7 @@ 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 (shouldEmitLifetimeMarkersForAutoVar() && haveInsertPoint()) { emission.useLifetimeMarkers = emitLifetimeStartOp( loc, address.getUnderlyingAllocaOp().getResult()); } @@ -390,10 +390,10 @@ 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. + // initializer is emitted with capturing disabled so that any cleanups it + // introduces get their normal cir.cleanup.scope handling. The variable's + // lifetime-end and destructor cleanups are captured for the loop's + // per-iteration cleanup region. assert(d.hasLocalStorage() && "loop condition variable is not local"); // Mirror the diagnostic emitted by emitVarDecl on the automatic-storage path. @@ -404,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. @@ -416,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()); @@ -436,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); diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h index cc26f7decfbd0..f54a4bb09959c 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.h +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h @@ -2807,6 +2807,10 @@ class CIRGenFunction : public CIRGenTypeCache { /// 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 ceda5811cd065..97537f899b9bc 100644 --- a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp @@ -1002,15 +1002,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()); + bool needsCondCleanup = condVar && + (condVar->needsDestruction(getContext()) != + QualType::DK_none || + shouldEmitLifetimeMarkersForAutoVar()); DeferredLoopConditionCleanup loopCondScope(*this, needsCondCleanup); auto condBuilder = [&](mlir::OpBuilder &b, mlir::Location loc) { @@ -1135,14 +1135,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()); + bool needsCondCleanup = 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 index 0e2642e2223f7..9fea56c5a307f 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,98 @@ 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]]) + +// 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 >From 1475eb24bc53974bffc41d0ae60ecd74e6f1ba94 Mon Sep 17 00:00:00 2001 From: E00N777 <[email protected]> Date: Fri, 7 Aug 2026 21:37:55 +0800 Subject: [PATCH 4/5] [CIR] Add lifetime marker tests and support loop condition variables --- clang/lib/CIR/CodeGen/CIRGenCleanup.cpp | 12 ++- clang/lib/CIR/CodeGen/CIRGenDecl.cpp | 10 +- clang/lib/CIR/CodeGen/CIRGenFunction.h | 8 +- clang/lib/CIR/CodeGen/CIRGenStmt.cpp | 16 +-- clang/test/CIR/CodeGen/lifetime-marker.cpp | 119 ++++++++++++++++++--- 5 files changed, 128 insertions(+), 37 deletions(-) diff --git a/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp b/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp index 51d41cbd4fcd7..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; @@ -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/CIRGenDecl.cpp b/clang/lib/CIR/CodeGen/CIRGenDecl.cpp index f00bfc672a866..a7c909ea408ef 100644 --- a/clang/lib/CIR/CodeGen/CIRGenDecl.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenDecl.cpp @@ -389,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 is emitted with capturing disabled so that any cleanups it - // introduces get their normal cir.cleanup.scope handling. The variable's - // lifetime-end and destructor cleanups are 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. diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h index f54a4bb09959c..125a8a58f7e18 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.h +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h @@ -1353,8 +1353,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; @@ -1374,8 +1374,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) diff --git a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp index 97537f899b9bc..61f51619efbff 100644 --- a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp @@ -1007,10 +1007,10 @@ mlir::LogicalResult CIRGenFunction::emitForStmt(const ForStmt &s) { // per-iteration cleanup region. This scope is constructed after the // 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 || - shouldEmitLifetimeMarkersForAutoVar()); + bool needsCondCleanup = + condVar && + (condVar->needsDestruction(getContext()) != QualType::DK_none || + shouldEmitLifetimeMarkersForAutoVar()); DeferredLoopConditionCleanup loopCondScope(*this, needsCondCleanup); auto condBuilder = [&](mlir::OpBuilder &b, mlir::Location loc) { @@ -1139,10 +1139,10 @@ mlir::LogicalResult CIRGenFunction::emitWhileStmt(const WhileStmt &s) { // 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 || - shouldEmitLifetimeMarkersForAutoVar()); + bool needsCondCleanup = + 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 index 9fea56c5a307f..f8e23c4854305 100644 --- a/clang/test/CIR/CodeGen/lifetime-marker.cpp +++ b/clang/test/CIR/CodeGen/lifetime-marker.cpp @@ -175,11 +175,8 @@ void eh_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. +// (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() { @@ -187,25 +184,117 @@ void while_condvar() { use(c); } -// CIR-LABEL: cir.func{{.*}}while_condvar -// CIR-NOT: cir.lifetime +// 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{{.*}}while_condvar -// LLVM-NOT: call void @llvm.lifetime +// LLVM-LABEL: define{{.*}} void @_Z13while_condvarv +// LLVM: call void @llvm.lifetime.start.p0(ptr %[[C:.*]]) +// LLVM: call void @llvm.lifetime.end.p0(ptr %[[C]]) -// O0-LABEL: cir.func{{.*}}while_condvar +// O0-LABEL: cir.func{{.*}} @_Z13while_condvarv // O0-NOT: cir.lifetime -// CIR-EH-LABEL: cir.func{{.*}}while_condvar -// CIR-EH-NOT: cir.lifetime +// 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{{.*}}for_condvar -// CIR-NOT: cir.lifetime +// 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]]) + +// O0-LABEL: cir.func{{.*}} @_Z11for_condvarv +// O0-NOT: cir.lifetime + +// 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(); +}; -// O0-LABEL: cir.func{{.*}}for_condvar +LoopCond make_loop_cond(); + +// A non-trivial condition variable runs its destructor before lifetime.end in +// the loop cleanup region. +void while_record_condvar() { + while (LoopCond c = make_loop_cond()) {} +} + +// 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]]) + +// O0-LABEL: cir.func{{.*}} @_Z20while_record_condvarv // O0-NOT: cir.lifetime + +// 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]]) >From 2998a7580b7d9195a2edcbe3765f9e16760e9054 Mon Sep 17 00:00:00 2001 From: E00N777 <[email protected]> Date: Tue, 11 Aug 2026 21:11:20 +0800 Subject: [PATCH 5/5] [CIR] Strengthen lifetime marker test checks --- clang/test/CIR/CodeGen/lifetime-marker.cpp | 51 +++++++--------------- 1 file changed, 16 insertions(+), 35 deletions(-) diff --git a/clang/test/CIR/CodeGen/lifetime-marker.cpp b/clang/test/CIR/CodeGen/lifetime-marker.cpp index f8e23c4854305..ce661aafe4498 100644 --- a/clang/test/CIR/CodeGen/lifetime-marker.cpp +++ b/clang/test/CIR/CodeGen/lifetime-marker.cpp @@ -3,7 +3,7 @@ // 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 +// 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 @@ -22,6 +22,7 @@ void f() { // 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: } @@ -29,13 +30,9 @@ void f() { // 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]]) -// 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(); }; @@ -59,9 +56,6 @@ void g() { // LLVM: call void @_ZN1SD1Ev(ptr {{.*}} %[[S]]) // LLVM: call void @llvm.lifetime.end.p0(ptr %[[S]]) -// 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 @@ -83,9 +77,6 @@ void bypass_switch(int n) { // 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); @@ -97,9 +88,6 @@ void bypass_label(int n) { // 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); @@ -112,9 +100,6 @@ void bypass_indirect_goto() { // CIR-LABEL: cir.func{{.*}}bypass_indirect_goto // CIR-NOT: cir.lifetime -// 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. @@ -123,24 +108,31 @@ void if_body(int n) { int x; use(x); } + use(n); } // CIR-LABEL: cir.func{{.*}} @_Z7if_bodyi -// CIR: cir.if +// 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: } +// 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]]) - -// O0-LABEL: cir.func{{.*}} @_Z7if_bodyi -// O0-NOT: cir.lifetime +// 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 @@ -196,9 +188,6 @@ void while_condvar() { // LLVM: call void @llvm.lifetime.start.p0(ptr %[[C:.*]]) // LLVM: call void @llvm.lifetime.end.p0(ptr %[[C]]) -// O0-LABEL: cir.func{{.*}} @_Z13while_condvarv -// O0-NOT: cir.lifetime - // CIR-EH-LABEL: cir.func{{.*}} @_Z13while_condvarv // CIR-EH: %[[C:.*]] = cir.alloca "c" {{.*}} : !cir.ptr<!s32i> // CIR-EH: cir.while { @@ -232,9 +221,6 @@ void for_condvar() { // LLVM: call void @llvm.lifetime.start.p0(ptr %[[C:.*]]) // LLVM: call void @llvm.lifetime.end.p0(ptr %[[C]]) -// O0-LABEL: cir.func{{.*}} @_Z11for_condvarv -// O0-NOT: cir.lifetime - // CIR-EH-LABEL: cir.func{{.*}} @_Z11for_condvarv // CIR-EH: %[[C:.*]] = cir.alloca "c" {{.*}} : !cir.ptr<!s32i> // CIR-EH: cir.for : cond { @@ -256,12 +242,10 @@ struct LoopCond { ~LoopCond(); }; -LoopCond make_loop_cond(); - // A non-trivial condition variable runs its destructor before lifetime.end in // the loop cleanup region. void while_record_condvar() { - while (LoopCond c = make_loop_cond()) {} + while (LoopCond c{}) {} } // CIR-LABEL: cir.func{{.*}} @_Z20while_record_condvarv @@ -278,9 +262,6 @@ void while_record_condvar() { // LLVM: call void @_ZN8LoopCondD1Ev(ptr {{.*}} %[[C]]) // LLVM: call void @llvm.lifetime.end.p0(ptr %[[C]]) -// O0-LABEL: cir.func{{.*}} @_Z20while_record_condvarv -// O0-NOT: cir.lifetime - // CIR-EH-LABEL: cir.func{{.*}} @_Z20while_record_condvarv // CIR-EH: %[[C:.*]] = cir.alloca "c" {{.*}} : !cir.ptr<!rec_LoopCond> // CIR-EH: cir.while { _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
