[clang] b3438c9 - [CIR] Add cir.lifetime.start and cir.lifetime.end Op (#199599)
via cfe-commits
cfe-commits at lists.llvm.org
Mon Jun 1 23:58:22 PDT 2026
Author: Jiahao Guo
Date: 2026-06-02T14:58:17+08:00
New Revision: b3438c9af04b2ccf13b0adb6faf5e1fa0e3fb585
URL: https://github.com/llvm/llvm-project/commit/b3438c9af04b2ccf13b0adb6faf5e1fa0e3fb585
DIFF: https://github.com/llvm/llvm-project/commit/b3438c9af04b2ccf13b0adb6faf5e1fa0e3fb585.diff
LOG: [CIR] Add cir.lifetime.start and cir.lifetime.end Op (#199599)
### summary
Adds the `cir.lifetime.start` and `cir.lifetime.end` ops, which mark the
beginning and end of an alloca's live range. They mirror the LLVM
intrinsics `llvm.lifetime.start` / `llvm.lifetime.end`
Added:
clang/test/CIR/IR/invalid-lifetime.cir
clang/test/CIR/IR/lifetime.cir
clang/test/CIR/Lowering/lifetime.cir
Modified:
clang/include/clang/CIR/Dialect/IR/CIROps.td
clang/lib/CIR/Dialect/IR/CIRDialect.cpp
clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
Removed:
################################################################################
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index 3d3395281f167..0353da7715191 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -4670,6 +4670,84 @@ def CIR_StackRestoreOp : CIR_Op<"stackrestore"> {
let assemblyFormat = "$ptr attr-dict `:` qualified(type($ptr))";
}
+//===----------------------------------------------------------------------===//
+// LifetimeStartOp & LifetimeEndOp
+//===----------------------------------------------------------------------===//
+
+def CIR_LifetimeStartOp : CIR_Op<"lifetime.start"> {
+ let summary = "Marks the start of the lifetime of a variable produced by a cir.alloca operation";
+ let description = [{
+ The `cir.lifetime.start` operation marks the beginning of the lifetime
+ of the storage pointed to by `$ptr`. Between this operation and a
+ matching `cir.lifetime.end` on the same pointer, the underlying storage
+ is considered live; outside that range it is considered dead, and the
+ optimizer is free to reuse the storage for other purposes.
+
+ The `cir.scope` is the operation that models the block scope of the C/C++
+ source. Once ClangIR is no longer structured, `cir.scope` can no longer express
+ the lifetime of a local variable. This happens, for example, after `FlattenCFG`,
+ where `cir.scope` regions are dissolved into plain basic blocks, or after
+ `HoistAllocas`, where an alloca is moved to the function entry so that its
+ position no longer reflects the scope it was declared in. In that form, these
+ lifetime markers are what delimit the beginning and end of a variable's
+ lifetime.
+
+ The verifier requires `$ptr` to be produced by a `cir.alloca`. For the
+ lifetime to be meaningful, a matching `cir.lifetime.end` on the same
+ pointer should follow on every control-flow path. This is
diff erent from
+ LLVM, where an `llvm.lifetime.start` may appear without a matching
+ `llvm.lifetime.end` -- there the storage is also implicitly marked dead
+ when the function returns (see the
+ [LLVM LangRef](https://llvm.org/docs/LangRef.html#int-lifestart)).
+
+ This operation corresponds to the LLVM intrinsic `llvm.lifetime.start`.
+
+ Example:
+ ```
+ cir.lifetime.start %ptr : !cir.ptr<!s32i>
+ ```
+ }];
+
+ let arguments = (ins CIR_PointerType:$ptr);
+ let assemblyFormat = "$ptr attr-dict `:` qualified(type($ptr))";
+ let hasVerifier = 1;
+}
+
+def CIR_LifetimeEndOp : CIR_Op<"lifetime.end"> {
+ let summary = "Marks the end of the lifetime of a variable produced by a cir.alloca operation";
+ let description = [{
+ The `cir.lifetime.end` operation marks the end of the lifetime of the
+ storage pointed to by `$ptr`. After this operation the underlying storage
+ is considered dead, and the optimizer is free to reuse the storage for
+ other purposes, until a subsequent `cir.lifetime.start` on the same
+ pointer revives it.
+
+ The `cir.scope` is the operation that models the block scope of the C/C++
+ source. Once ClangIR is no longer structured, `cir.scope` can no longer express
+ the lifetime of a local variable. This happens, for example, after `FlattenCFG`,
+ where `cir.scope` regions are dissolved into plain basic blocks, or after
+ `HoistAllocas`, where an alloca is moved to the function entry so that its
+ position no longer reflects the scope it was declared in. In that form, these
+ lifetime markers are what delimit the beginning and end of a variable's
+ lifetime.
+
+ The verifier requires `$ptr` to be produced by a `cir.alloca`.
+ `cir.lifetime.end` should be preceded by a matching `cir.lifetime.start` on
+ the same pointer on every control-flow path that reaches it.
+
+ This operation corresponds to the LLVM intrinsic `llvm.lifetime.end`.
+
+ Example:
+ ```
+ cir.lifetime.end %ptr : !cir.ptr<!s32i>
+ ```
+ }];
+
+ let arguments = (ins CIR_PointerType:$ptr);
+ let assemblyFormat = "$ptr attr-dict `:` qualified(type($ptr))";
+ let hasVerifier = 1;
+}
+
//===----------------------------------------------------------------------===//
// InlineAsmOp
//===----------------------------------------------------------------------===//
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index 6e1e3915f8edf..02360fb08cc79 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -19,6 +19,7 @@
#include "mlir/IR/Attributes.h"
#include "mlir/IR/DialectImplementation.h"
#include "mlir/IR/PatternMatch.h"
+#include "mlir/IR/Value.h"
#include "mlir/Interfaces/ControlFlowInterfaces.h"
#include "mlir/Interfaces/FunctionImplementation.h"
#include "mlir/Support/LLVM.h"
@@ -208,6 +209,19 @@ static bool omitRegionTerm(mlir::Region &r) {
return singleNonEmptyBlock && yieldsNothing();
}
+// Verifies that the given operand is produced by an operation of type
+// ExpectedProducerOp.
+template <typename ExpectedProducerOp>
+static LogicalResult verifyProducedBy(Operation *op, Value operand,
+ StringRef operandName) {
+ Operation *producer = operand.getDefiningOp();
+ if (!producer || !isa<ExpectedProducerOp>(producer))
+ return op->emitOpError()
+ << "operand '" << operandName << "' must be produced by '"
+ << ExpectedProducerOp::getOperationName() << "'";
+ return success();
+}
+
//===----------------------------------------------------------------------===//
// InlineKindAttr (FIXME: remove once FuncOp uses assembly format)
//===----------------------------------------------------------------------===//
@@ -4382,6 +4396,18 @@ cir::EhTypeIdOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
return success();
}
+//===----------------------------------------------------------------------===//
+// LifetimeStartOp & LifetimeEndOp
+//===----------------------------------------------------------------------===//
+
+LogicalResult cir::LifetimeStartOp::verify() {
+ return verifyProducedBy<cir::AllocaOp>(*this, getPtr(), "ptr");
+}
+
+LogicalResult cir::LifetimeEndOp::verify() {
+ return verifyProducedBy<cir::AllocaOp>(*this, getPtr(), "ptr");
+}
+
//===----------------------------------------------------------------------===//
// ConstructCatchParamOp
//===----------------------------------------------------------------------===//
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
index cc016c87411cd..8c7e1406d6567 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
@@ -4189,6 +4189,21 @@ mlir::LogicalResult CIRToLLVMStackRestoreOpLowering::matchAndRewrite(
return mlir::success();
}
+mlir::LogicalResult CIRToLLVMLifetimeStartOpLowering::matchAndRewrite(
+ cir::LifetimeStartOp op, OpAdaptor adaptor,
+ mlir::ConversionPatternRewriter &rewriter) const {
+ rewriter.replaceOpWithNewOp<mlir::LLVM::LifetimeStartOp>(op,
+ adaptor.getPtr());
+ return mlir::success();
+}
+
+mlir::LogicalResult CIRToLLVMLifetimeEndOpLowering::matchAndRewrite(
+ cir::LifetimeEndOp op, OpAdaptor adaptor,
+ mlir::ConversionPatternRewriter &rewriter) const {
+ rewriter.replaceOpWithNewOp<mlir::LLVM::LifetimeEndOp>(op, adaptor.getPtr());
+ return mlir::success();
+}
+
mlir::LogicalResult CIRToLLVMVecCreateOpLowering::matchAndRewrite(
cir::VecCreateOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
diff --git a/clang/test/CIR/IR/invalid-lifetime.cir b/clang/test/CIR/IR/invalid-lifetime.cir
new file mode 100644
index 0000000000000..6462a20ca06d3
--- /dev/null
+++ b/clang/test/CIR/IR/invalid-lifetime.cir
@@ -0,0 +1,36 @@
+// RUN: cir-opt %s -verify-diagnostics -split-input-file
+
+!s32i = !cir.int<s, 32>
+
+// `cir.lifetime.start` requires its pointer to be produced by a `cir.alloca`.
+cir.func @start_not_alloca(%arg0: !cir.ptr<!s32i>) {
+// expected-error @below {{operand 'ptr' must be produced by 'cir.alloca'}}
+cir.lifetime.start %arg0 : !cir.ptr<!s32i>
+cir.return
+}
+
+// -----
+
+!s32i = !cir.int<s, 32>
+
+// `cir.lifetime.end` requires its pointer to be produced by a `cir.alloca`.
+cir.func @end_not_alloca(%arg0: !cir.ptr<!s32i>) {
+// expected-error @below {{operand 'ptr' must be produced by 'cir.alloca'}}
+cir.lifetime.end %arg0 : !cir.ptr<!s32i>
+cir.return
+}
+
+// -----
+
+!s32i = !cir.int<s, 32>
+
+cir.global external @g = #cir.int<0> : !s32i
+
+// The pointer has a defining op, but it is a `cir.get_global`, not a
+// `cir.alloca`, so the verifier still rejects it.
+cir.func @start_from_global() {
+%0 = cir.get_global @g : !cir.ptr<!s32i>
+// expected-error @below {{operand 'ptr' must be produced by 'cir.alloca'}}
+cir.lifetime.start %0 : !cir.ptr<!s32i>
+cir.return
+}
diff --git a/clang/test/CIR/IR/lifetime.cir b/clang/test/CIR/IR/lifetime.cir
new file mode 100644
index 0000000000000..5b310d93679d1
--- /dev/null
+++ b/clang/test/CIR/IR/lifetime.cir
@@ -0,0 +1,14 @@
+// Test the CIR operations can parse and print correctly (roundtrip)
+
+// RUN: cir-opt %s --verify-roundtrip | FileCheck %s
+
+!s32i = !cir.int<s, 32>
+
+cir.func @lifetime_start_end() {
+ %0 = cir.alloca !s32i, !cir.ptr<!s32i>, ["x"] {alignment = 4 : i64}
+ // CHECK: cir.lifetime.start %0 : !cir.ptr<!s32i>
+ cir.lifetime.start %0 : !cir.ptr<!s32i>
+ // CHECK: cir.lifetime.end %0 : !cir.ptr<!s32i>
+ cir.lifetime.end %0 : !cir.ptr<!s32i>
+ cir.return
+}
diff --git a/clang/test/CIR/Lowering/lifetime.cir b/clang/test/CIR/Lowering/lifetime.cir
new file mode 100644
index 0000000000000..70db17eae76bf
--- /dev/null
+++ b/clang/test/CIR/Lowering/lifetime.cir
@@ -0,0 +1,12 @@
+// RUN: cir-opt %s -cir-to-llvm -o - | mlir-translate -mlir-to-llvmir | FileCheck %s
+
+!s32i = !cir.int<s, 32>
+
+cir.func @lifetime_markers() {
+ %0 = cir.alloca !s32i, !cir.ptr<!s32i>, ["x"] {alignment = 4 : i64}
+ // CHECK: call void @llvm.lifetime.start.p0(ptr %[[X:.*]])
+ cir.lifetime.start %0 : !cir.ptr<!s32i>
+ // CHECK: call void @llvm.lifetime.end.p0(ptr %[[X]])
+ cir.lifetime.end %0 : !cir.ptr<!s32i>
+ cir.return
+}
More information about the cfe-commits
mailing list