https://github.com/E00N777 updated https://github.com/llvm/llvm-project/pull/199599
>From f3ac5a1c40e82ba626c6825efccdca3710c50112 Mon Sep 17 00:00:00 2001 From: E0N777 <[email protected]> Date: Tue, 26 May 2026 12:08:52 +0800 Subject: [PATCH] [CIR] Add cir.lifetime.start and cir.lifetime.end Op --- clang/include/clang/CIR/Dialect/IR/CIROps.td | 76 +++++++++++++++++++ clang/lib/CIR/Dialect/IR/CIRDialect.cpp | 26 +++++++ .../CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp | 15 ++++ clang/test/CIR/IR/invalid-lifetime.cir | 36 +++++++++ clang/test/CIR/IR/lifetime.cir | 14 ++++ clang/test/CIR/Lowering/lifetime.cir | 12 +++ 6 files changed, 179 insertions(+) create mode 100644 clang/test/CIR/IR/invalid-lifetime.cir create mode 100644 clang/test/CIR/IR/lifetime.cir create mode 100644 clang/test/CIR/Lowering/lifetime.cir diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td index 67ddaa73d9184..bf2d3af87c509 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIROps.td +++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td @@ -4670,6 +4670,82 @@ 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. Note that, unlike LLVM + where a `llvm.lifetime.start` may appear without a matching `llvm.lifetime.end` + (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 c23a02d6f49fb..92cedea28cfca 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) //===----------------------------------------------------------------------===// @@ -4377,6 +4391,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 c4e98e299dfc1..7fdf6ce101303 100644 --- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp +++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp @@ -4193,6 +4193,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..6970eb8bd2ecb --- /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 +} \ No newline at end of file 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 +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
