llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-clangir Author: Andy Kaylor (andykaylor) <details> <summary>Changes</summary> This change adds the dead_on_return attribute for the `this` argument to destructors unless `-fno-lifetime-dse` was used. Assisted-by: Cursor / claude-opus-5.5 --- Full diff: https://github.com/llvm/llvm-project/pull/235125.diff 5 Files Affected: - (modified) clang/lib/CIR/CodeGen/CIRGenCall.cpp (+21-8) - (modified) clang/lib/CIR/CodeGen/CIRGenModule.h (+1-1) - (modified) clang/test/CIR/CodeGen/call-conv-lowering-x86_64-non-byval.cpp (+5-10) - (added) clang/test/CIR/CodeGen/destructor-dead-on-return.cpp (+121) - (modified) clang/test/CIR/CodeGen/paren-init-list.cpp (+1-1) ``````````diff diff --git a/clang/lib/CIR/CodeGen/CIRGenCall.cpp b/clang/lib/CIR/CodeGen/CIRGenCall.cpp index 16087a04086ac..c832c7e94cb1d 100644 --- a/clang/lib/CIR/CodeGen/CIRGenCall.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenCall.cpp @@ -574,8 +574,8 @@ void CIRGenModule::constructAttributeList( // TODO(cir): Add loader-replaceable attribute here. constructFunctionReturnAttributes(info, targetDecl, isThunk, retAttrs); - constructFunctionArgumentAttributes(info, targetDecl, isThunk, attrOnCallSite, - argAttrs); + constructFunctionArgumentAttributes(info, calleeInfo.getCalleeDecl(), isThunk, + attrOnCallSite, argAttrs); } bool CIRGenModule::hasStrictReturn(QualType retTy, const Decl *targetDecl) { @@ -722,9 +722,10 @@ void CIRGenModule::constructFunctionReturnAttributes( } void CIRGenModule::constructFunctionArgumentAttributes( - const CIRGenFunctionInfo &info, const Decl *targetDecl, bool isThunk, + const CIRGenFunctionInfo &info, GlobalDecl calleeDecl, bool isThunk, bool attrOnCallSite, llvm::MutableArrayRef<mlir::NamedAttrList> argAttrs) { assert(!cir::MissingFeatures::abiArgInfo()); + const Decl *targetDecl = calleeDecl.getDecl(); // TODO(cir): classic codegen does a lot of work here based on the ABIArgInfo // to set things based on calling convention. @@ -756,11 +757,23 @@ void CIRGenModule::constructFunctionArgumentAttributes( builder.getI64IntegerAttr( getNaturalPointeeTypeAlignment(thisPtrTy).getQuantity())); - // TODO(cir): the classic codegen has a recently-added bunch of logic for - // 'dead_on_return' as an attribute. This both doesn't exist in the LLVM - // dialect, and is 'too new' at the time of writing this to be considered - // stable enough here. For now, we'll leave this as a TODO so that when - // we come back, it is hopefully a more stabilized implementation. + // With strict lifetimes, the object's data is dead when a destructor + // returns, except for a deleting destructor, whose operator delete may + // still use the storage, and except where a virtual base outlives the + // destructor of the object holding it. An object with no data gets no + // attribute. + const auto *dd = dyn_cast_if_present<CXXDestructorDecl>(targetDecl); + if (dd && codeGenOpts.StrictLifetimes && + calleeDecl.getDtorType() != CXXDtorType::Dtor_VectorDeleting && + calleeDecl.getDtorType() != CXXDtorType::Dtor_Deleting) { + const CXXRecordDecl *classDecl = dd->getParent(); + uint64_t dataSize = astContext.getASTRecordLayout(classDecl) + .getDataSize() + .getQuantity(); + if (classDecl->getNumVBases() == 0 && dataSize != 0) + argAttrs[0].set(mlir::LLVM::LLVMDialect::getDeadOnReturnAttrName(), + builder.getI64IntegerAttr(dataSize)); + } } } diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.h b/clang/lib/CIR/CodeGen/CIRGenModule.h index 074ca5818ae2e..ed193267b7ee0 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.h +++ b/clang/lib/CIR/CodeGen/CIRGenModule.h @@ -174,7 +174,7 @@ class CIRGenModule : public CIRGenTypeCache { mlir::NamedAttrList &retAttrs); /// A helper for constructAttributeList that handles argument attributes. void constructFunctionArgumentAttributes( - const CIRGenFunctionInfo &info, const clang::Decl *targetDecl, + const CIRGenFunctionInfo &info, clang::GlobalDecl calleeDecl, bool isThunk, bool attrOnCallSite, llvm::MutableArrayRef<mlir::NamedAttrList> argAttrs); /// A helper function for constructAttributeList that determines whether a diff --git a/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-non-byval.cpp b/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-non-byval.cpp index 9e9ddfc405734..8acdb9833aa5e 100644 --- a/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-non-byval.cpp +++ b/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-non-byval.cpp @@ -43,10 +43,8 @@ void callNonByval() { // LLVM-LABEL: define dso_local void @_Z12callNonByvalv() // LLVM: call void @llvm.memcpy.p0.p0.i64(ptr align 4 %[[TMP:[^,]+]], ptr align 4 %[[T:[^,]+]], i64 4, i1 false) // LLVM: call void @_Z12takeNonByval8WithDtor(ptr nofreeobj noundef align 4 dereferenceable(4) %[[TMP]]) -// LLVM-CIR: call void @_ZN8WithDtorD1Ev(ptr noundef nonnull align 4 dereferenceable(4) %[[TMP]]) -// LLVM-CIR: call void @_ZN8WithDtorD1Ev(ptr noundef nonnull align 4 dereferenceable(4) %[[T]]) -// OGCG: call void @_ZN8WithDtorD1Ev(ptr noundef nonnull align 4 dead_on_return(4) dereferenceable(4) %[[TMP]]) -// OGCG: call void @_ZN8WithDtorD1Ev(ptr noundef nonnull align 4 dead_on_return(4) dereferenceable(4) %[[T]]) +// LLVM: call void @_ZN8WithDtorD1Ev(ptr noundef nonnull align 4 dead_on_return(4) dereferenceable(4) %[[TMP]]) +// LLVM: call void @_ZN8WithDtorD1Ev(ptr noundef nonnull align 4 dead_on_return(4) dereferenceable(4) %[[T]]) // Each non-byval argument forwards its own temporary. void callTwoNonByval() { @@ -66,10 +64,8 @@ void callTwoNonByval() { // LLVM: call void @llvm.memcpy.p0.p0.i64(ptr align 4 %[[TMP_A:[^,]+]], ptr align 4 %{{[^,]+}}, i64 4, i1 false) // LLVM: call void @llvm.memcpy.p0.p0.i64(ptr align 4 %[[TMP_B:[^,]+]], ptr align 4 %{{[^,]+}}, i64 4, i1 false) // LLVM: call void @_Z15takeTwoNonByval8WithDtorS_(ptr nofreeobj noundef align 4 dereferenceable(4) %[[TMP_A]], ptr nofreeobj noundef align 4 dereferenceable(4) %[[TMP_B]]) -// LLVM-CIR: call void @_ZN8WithDtorD1Ev(ptr noundef nonnull align 4 dereferenceable(4) %[[TMP_B]]) -// LLVM-CIR: call void @_ZN8WithDtorD1Ev(ptr noundef nonnull align 4 dereferenceable(4) %[[TMP_A]]) -// OGCG: call void @_ZN8WithDtorD1Ev(ptr noundef nonnull align 4 dead_on_return(4) dereferenceable(4) %[[TMP_B]]) -// OGCG: call void @_ZN8WithDtorD1Ev(ptr noundef nonnull align 4 dead_on_return(4) dereferenceable(4) %[[TMP_A]]) +// LLVM: call void @_ZN8WithDtorD1Ev(ptr noundef nonnull align 4 dead_on_return(4) dereferenceable(4) %[[TMP_B]]) +// LLVM: call void @_ZN8WithDtorD1Ev(ptr noundef nonnull align 4 dead_on_return(4) dereferenceable(4) %[[TMP_A]]) // A non-trivial copy constructor also classifies non-byval: the constructor // call populates the forwarded temporary directly, with no load in between. @@ -151,8 +147,7 @@ void callInheritedCtor(WithDtor t) { Derived d(t); } // LLVM: %[[INHTMP:.+]] = alloca %struct.WithDtor, align 4 // LLVM: call void @llvm.memcpy.p0.p0.i64(ptr align 4 %[[INHTMP]], ptr align 4 %[[INHARG]], i64 4, i1 false) // LLVM: call void @_ZN7DerivedCI14BaseE8WithDtor(ptr noundef nonnull align 1 dereferenceable(1) %{{.+}}, ptr nofreeobj noundef align 4 dereferenceable(4) %[[INHTMP]]) -// LLVM-CIR: call void @_ZN8WithDtorD1Ev(ptr noundef nonnull align 4 dereferenceable(4) %[[INHTMP]]) -// OGCG: call void @_ZN8WithDtorD1Ev(ptr noundef nonnull align 4 dead_on_return(4) dereferenceable(4) %[[INHTMP]]) +// LLVM: call void @_ZN8WithDtorD1Ev(ptr noundef nonnull align 4 dead_on_return(4) dereferenceable(4) %[[INHTMP]]) // Both inheriting constructor variants hand their own parameter on unchanged. // CIR-LABEL: cir.func {{.*}}@_ZN7DerivedCI14BaseE8WithDtor diff --git a/clang/test/CIR/CodeGen/destructor-dead-on-return.cpp b/clang/test/CIR/CodeGen/destructor-dead-on-return.cpp new file mode 100644 index 0000000000000..35bd11e20f870 --- /dev/null +++ b/clang/test/CIR/CodeGen/destructor-dead-on-return.cpp @@ -0,0 +1,121 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir +// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t-cir.ll +// RUN: FileCheck --check-prefix=LLVM --input-file=%t-cir.ll %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll +// RUN: FileCheck --check-prefix=LLVM --input-file=%t.ll %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -fno-lifetime-dse -emit-cir %s -o %t-nodse.cir +// RUN: FileCheck --check-prefix=NODSE --input-file=%t-nodse.cir %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -fno-lifetime-dse -emit-llvm %s -o %t-nodse-cir.ll +// RUN: FileCheck --check-prefix=NODSE --input-file=%t-nodse-cir.ll %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fno-lifetime-dse -emit-llvm %s -o %t-nodse.ll +// RUN: FileCheck --check-prefix=NODSE --input-file=%t-nodse.ll %s + +// A destructor's `this` is dead_on_return for the bytes of the object's data, +// unless lifetime DSE is disabled. + +// NODSE-NOT: dead_on_return + +void opaque(); +void opaque(char *); + +class Foo { +public: + ~Foo(); + +private: + char fooVar[4]; +}; + +Foo::~Foo() { opaque(fooVar); } + +// CIR: cir.func {{.*}} @_ZN3FooD2Ev(%arg0: !cir.ptr<!rec_Foo> {llvm.align = 1 : i64, llvm.dead_on_return = 4 : i64, llvm.dereferenceable = 4 : i64, llvm.nonnull, llvm.noundef} +// CIR: cir.func {{.*}} @_ZN3FooD1Ev(%arg0: !cir.ptr<!rec_Foo> {llvm.align = 1 : i64, llvm.dead_on_return = 4 : i64, llvm.dereferenceable = 4 : i64, llvm.nonnull, llvm.noundef} +// CIR: cir.call @_ZN3FooD2Ev(%{{.*}}) nothrow : (!cir.ptr<!rec_Foo> {llvm.align = 1 : i64, llvm.dead_on_return = 4 : i64, llvm.dereferenceable = 4 : i64, llvm.nonnull, llvm.noundef}) -> () + +// LLVM: define dso_local void @_ZN3FooD2Ev(ptr noundef nonnull align 1 dead_on_return(4) dereferenceable(4) %{{.*}}) +// LLVM: define dso_local void @_ZN3FooD1Ev(ptr noundef nonnull align 1 dead_on_return(4) dereferenceable(4) %{{.*}}) +// LLVM: call void @_ZN3FooD2Ev(ptr noundef nonnull align 1 dead_on_return(4) dereferenceable(4) %{{.*}}) + +// The data of a derived class includes its bases. +class BarInheritsFoo : public Foo { +public: + ~BarInheritsFoo(); + +private: + char barVar[12]; +}; + +BarInheritsFoo::~BarInheritsFoo() { opaque(barVar); } + +// CIR: cir.func {{.*}} @_ZN14BarInheritsFooD2Ev(%arg0: !cir.ptr<!rec_BarInheritsFoo> {llvm.align = 1 : i64, llvm.dead_on_return = 16 : i64, llvm.dereferenceable = 16 : i64, llvm.nonnull, llvm.noundef} +// CIR: cir.call @_ZN3FooD2Ev(%{{.*}}) nothrow : (!cir.ptr<!rec_Foo> {llvm.align = 1 : i64, llvm.dead_on_return = 4 : i64, llvm.dereferenceable = 4 : i64, llvm.nonnull, llvm.noundef}) -> () + +// LLVM: define dso_local void @_ZN14BarInheritsFooD2Ev(ptr noundef nonnull align 1 dead_on_return(16) dereferenceable(16) %{{.*}}) +// LLVM: call void @_ZN3FooD2Ev(ptr noundef nonnull align 1 dead_on_return(4) dereferenceable(4) %{{.*}}) + +// A virtual base outlives the destructor of the object holding it. +class BarVirtualInheritsFoo : public virtual Foo { +public: + ~BarVirtualInheritsFoo(); + +private: + char barVar[16]; +}; + +BarVirtualInheritsFoo::~BarVirtualInheritsFoo() { opaque(barVar); } + +// CIR: cir.func {{.*}} @_ZN21BarVirtualInheritsFooD2Ev(%arg0: !cir.ptr<!rec_BarVirtualInheritsFoo> {llvm.align = 8 : i64, llvm.dereferenceable = 24 : i64, llvm.nonnull, llvm.noundef}{{.*}}, %arg1: !cir.ptr<!cir.ptr<!void>> {llvm.noundef} +// CIR: cir.func {{.*}} @_ZN21BarVirtualInheritsFooD1Ev(%arg0: !cir.ptr<!rec_BarVirtualInheritsFoo> {llvm.align = 8 : i64, llvm.dereferenceable = 24 : i64, llvm.nonnull, llvm.noundef} + +// LLVM: define dso_local void @_ZN21BarVirtualInheritsFooD2Ev(ptr noundef nonnull align 8 dereferenceable(24) %{{.*}}, ptr noundef %{{.*}}) +// LLVM: define dso_local void @_ZN21BarVirtualInheritsFooD1Ev(ptr noundef nonnull align 8 dereferenceable(24) %{{.*}}) + +// An object with no data gets no attribute. +class Empty { +public: + ~Empty(); +}; + +Empty::~Empty() { opaque(); } + +// CIR: cir.func {{.*}} @_ZN5EmptyD2Ev(%arg0: !cir.ptr<!rec_Empty> {llvm.align = 1 : i64, llvm.dereferenceable = 1 : i64, llvm.nonnull, llvm.noundef} + +// LLVM: define dso_local void @_ZN5EmptyD2Ev(ptr noundef nonnull align 1 dereferenceable(1) %{{.*}}) + +// The deleting destructor's operator delete may still use the storage. +class Poly { +public: + virtual ~Poly(); + +private: + char polyVar[4]; +}; + +Poly::~Poly() { opaque(polyVar); } + +// CIR: cir.func {{.*}} @_ZN4PolyD2Ev(%arg0: !cir.ptr<!rec_Poly> {llvm.align = 8 : i64, llvm.dead_on_return = 12 : i64, llvm.dereferenceable = 12 : i64, llvm.nonnull, llvm.noundef} +// CIR: cir.func {{.*}} @_ZN4PolyD0Ev(%arg0: !cir.ptr<!rec_Poly> {llvm.align = 8 : i64, llvm.dereferenceable = 12 : i64, llvm.nonnull, llvm.noundef} +// CIR: cir.call @_ZN4PolyD1Ev(%{{.*}}) nothrow : (!cir.ptr<!rec_Poly> {llvm.align = 8 : i64, llvm.dead_on_return = 12 : i64, llvm.dereferenceable = 12 : i64, llvm.nonnull, llvm.noundef}) -> () + +// LLVM: define dso_local void @_ZN4PolyD2Ev(ptr noundef nonnull align 8 dead_on_return(12) dereferenceable(12) %{{.*}}) +// LLVM: define dso_local void @_ZN4PolyD0Ev(ptr noundef nonnull align 8 dereferenceable(12) %{{.*}}) +// LLVM: call void @_ZN4PolyD1Ev(ptr noundef nonnull align 8 dead_on_return(12) dereferenceable(12) %{{.*}}) + +void callDtor() { + Foo f; +} + +// CIR: cir.func {{.*}} @_Z8callDtorv() +// CIR: cir.call @_ZN3FooD1Ev(%{{.*}}) nothrow : (!cir.ptr<!rec_Foo> {llvm.align = 1 : i64, llvm.dead_on_return = 4 : i64, llvm.dereferenceable = 4 : i64, llvm.nonnull, llvm.noundef}) -> () + +// LLVM: define dso_local void @_Z8callDtorv() +// LLVM: call void @_ZN3FooD1Ev(ptr noundef nonnull align 1 dead_on_return(4) dereferenceable(4) %{{.*}}) + +void deletePoly(Poly *p) { delete p; } + +// CIR: cir.func {{.*}} @_Z10deletePolyP4Poly +// CIR: cir.call %{{.*}}(%{{.*}}) nothrow : (!cir.ptr<!cir.func<(!cir.ptr<!rec_Poly>)>>, !cir.ptr<!rec_Poly> {llvm.align = 8 : i64, llvm.dereferenceable = 12 : i64, llvm.nonnull, llvm.noundef}) -> () + +// LLVM: define dso_local void @_Z10deletePolyP4Poly +// LLVM: call void %{{.*}}(ptr noundef nonnull align 8 dereferenceable(12) %{{.*}}) diff --git a/clang/test/CIR/CodeGen/paren-init-list.cpp b/clang/test/CIR/CodeGen/paren-init-list.cpp index 48ca454cf1f91..6689d80e6a144 100644 --- a/clang/test/CIR/CodeGen/paren-init-list.cpp +++ b/clang/test/CIR/CodeGen/paren-init-list.cpp @@ -51,7 +51,7 @@ void test_init_list_with_dtor() { // CIR: cir.cleanup.scope { // CIR: cir.yield // CIR: } cleanup normal { -// CIR: cir.call @_ZN5OuterD1Ev(%[[O]]) nothrow : (!cir.ptr<!rec_Outer> {llvm.align = 4 : i64, llvm.dereferenceable = 8 : i64, llvm.nonnull, llvm.noundef}) -> () +// CIR: cir.call @_ZN5OuterD1Ev(%[[O]]) nothrow : (!cir.ptr<!rec_Outer> {llvm.align = 4 : i64, llvm.dead_on_return = 8 : i64, llvm.dereferenceable = 8 : i64, llvm.nonnull, llvm.noundef}) -> () // CIR: cir.yield // CIR: } // CIR: cir.return `````````` </details> https://github.com/llvm/llvm-project/pull/235125 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
