[llvm] [FunctionAttrs] Handle nofreeobj (PR #218364)

Nikita Popov via llvm-commits llvm-commits at lists.llvm.org
Mon Aug 24 02:51:45 PDT 2026


https://github.com/nikic created https://github.com/llvm/llvm-project/pull/218364

This adds handling for nofreeobj (introduced in https://github.com/llvm/llvm-project/pull/206445) in two places:

 * Don't infer `nofree` if we already have `nofreeobj`. `nofreeobj` is a stronger property, it's pointless to have both.
 * Use `nofreeobj` on call arguments when inferring `nofree`.

>From ae3f76b3db6c6c7bda996f50616e0c6b11013811 Mon Sep 17 00:00:00 2001
From: Nikita Popov <npopov at redhat.com>
Date: Mon, 24 Aug 2026 11:45:21 +0200
Subject: [PATCH 1/2] Add test

---
 llvm/test/Transforms/FunctionAttrs/nofree.ll | 25 +++++++++++++++++---
 1 file changed, 22 insertions(+), 3 deletions(-)

diff --git a/llvm/test/Transforms/FunctionAttrs/nofree.ll b/llvm/test/Transforms/FunctionAttrs/nofree.ll
index c477bf8e00aeb..4cfff49d49f23 100644
--- a/llvm/test/Transforms/FunctionAttrs/nofree.ll
+++ b/llvm/test/Transforms/FunctionAttrs/nofree.ll
@@ -103,7 +103,7 @@ define void @_Z4foo7Pi(ptr %a) local_unnamed_addr #1 {
 ; CHECK-NEXT:    [[ISNULL:%.*]] = icmp eq ptr [[A]], null
 ; CHECK-NEXT:    br i1 [[ISNULL]], label %[[DELETE_END:.*]], label %[[DELETE_NOTNULL:.*]]
 ; CHECK:       [[DELETE_NOTNULL]]:
-; CHECK-NEXT:    tail call void @_ZdlPv(ptr [[A]]) #[[ATTR8:[0-9]+]]
+; CHECK-NEXT:    tail call void @_ZdlPv(ptr [[A]]) #[[ATTR9:[0-9]+]]
 ; CHECK-NEXT:    br label %[[DELETE_END]]
 ; CHECK:       [[DELETE_END]]:
 ; CHECK-NEXT:    ret void
@@ -130,7 +130,7 @@ define void @_Z4foo8Pi(ptr %a) local_unnamed_addr #1 {
 ; CHECK-NEXT:    [[ISNULL:%.*]] = icmp eq ptr [[A]], null
 ; CHECK-NEXT:    br i1 [[ISNULL]], label %[[DELETE_END:.*]], label %[[DELETE_NOTNULL:.*]]
 ; CHECK:       [[DELETE_NOTNULL]]:
-; CHECK-NEXT:    tail call void @_ZdaPv(ptr [[A]]) #[[ATTR8]]
+; CHECK-NEXT:    tail call void @_ZdaPv(ptr [[A]]) #[[ATTR9]]
 ; CHECK-NEXT:    br label %[[DELETE_END]]
 ; CHECK:       [[DELETE_END]]:
 ; CHECK-NEXT:    ret void
@@ -252,7 +252,7 @@ define void @passed_to_nofree_fn_maybe_capture_arg(ptr %p) {
 define void @passed_to_readonly_fn_nocapture_arg(ptr %p) {
 ; CHECK-LABEL: define void @passed_to_readonly_fn_nocapture_arg(
 ; CHECK-SAME: ptr nofree readonly captures(address) [[P:%.*]]) {
-; CHECK-NEXT:    call void @takes_ptr(ptr captures(address, read_provenance) [[P]]) #[[ATTR9:[0-9]+]]
+; CHECK-NEXT:    call void @takes_ptr(ptr captures(address, read_provenance) [[P]]) #[[ATTR10:[0-9]+]]
 ; CHECK-NEXT:    call void @may_free()
 ; CHECK-NEXT:    ret void
 ;
@@ -298,6 +298,25 @@ define void @passed_to_unknown_bundle(ptr %p) {
   ret void
 }
 
+define void @already_nofreeobj(ptr nofreeobj %p) {
+; CHECK: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(none)
+; CHECK-LABEL: define void @already_nofreeobj(
+; CHECK-SAME: ptr nofree nofreeobj readnone captures(none) [[P:%.*]]) #[[ATTR7:[0-9]+]] {
+; CHECK-NEXT:    ret void
+;
+  ret void
+}
+
+define void @call_with_nofreeobj(ptr %p) {
+; CHECK-LABEL: define void @call_with_nofreeobj(
+; CHECK-SAME: ptr captures(none) [[P:%.*]]) {
+; CHECK-NEXT:    call void @takes_ptr(ptr nofreeobj captures(none) [[P]])
+; CHECK-NEXT:    ret void
+;
+  call void @takes_ptr(ptr nofreeobj captures(none) %p)
+  ret void
+}
+
 declare void @takes_ptr(ptr)
 
 declare void @_ZdaPv(ptr) local_unnamed_addr #4

>From 4be06e4afaa38e34eb4e0404484280426a6e1cbf Mon Sep 17 00:00:00 2001
From: Nikita Popov <npopov at redhat.com>
Date: Mon, 24 Aug 2026 11:49:04 +0200
Subject: [PATCH 2/2] Handle nofreeobj

---
 llvm/lib/Transforms/IPO/FunctionAttrs.cpp    | 6 ++++--
 llvm/test/Transforms/FunctionAttrs/nofree.ll | 4 ++--
 2 files changed, 6 insertions(+), 4 deletions(-)

diff --git a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp
index d98d3a1c46309..d2f89150ee663 100644
--- a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp
+++ b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp
@@ -952,7 +952,8 @@ determinePointerAccessAttrs(Argument *A,
       if (isModSet(ArgMR) && !CB->onlyReadsMemory(UseIndex)) {
         Props.IsWrite = true;
         if (CB->isArgOperand(U) && !CB->hasFnAttr(Attribute::NoFree) &&
-            !CB->paramHasAttr(UseIndex, Attribute::NoFree))
+            !CB->paramHasAttr(UseIndex, Attribute::NoFree) &&
+            !CB->paramHasAttr(UseIndex, Attribute::NoFreeObj))
           Props.IsFree = true;
       }
     } else {
@@ -1075,7 +1076,8 @@ static bool addAccessAttrs(Argument *A, ArgAccessProperties Props) {
   assert(A && "Argument must not be null.");
 
   bool Changed = false;
-  if (!Props.IsFree && !A->hasAttribute(Attribute::NoFree)) {
+  if (!Props.IsFree && !A->hasAttribute(Attribute::NoFree) &&
+      !A->hasAttribute(Attribute::NoFreeObj)) {
     ++NumNoFreeArg;
     A->addAttr(Attribute::NoFree);
     Changed = true;
diff --git a/llvm/test/Transforms/FunctionAttrs/nofree.ll b/llvm/test/Transforms/FunctionAttrs/nofree.ll
index 4cfff49d49f23..e342ed8f4c198 100644
--- a/llvm/test/Transforms/FunctionAttrs/nofree.ll
+++ b/llvm/test/Transforms/FunctionAttrs/nofree.ll
@@ -301,7 +301,7 @@ define void @passed_to_unknown_bundle(ptr %p) {
 define void @already_nofreeobj(ptr nofreeobj %p) {
 ; CHECK: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(none)
 ; CHECK-LABEL: define void @already_nofreeobj(
-; CHECK-SAME: ptr nofree nofreeobj readnone captures(none) [[P:%.*]]) #[[ATTR7:[0-9]+]] {
+; CHECK-SAME: ptr nofreeobj readnone captures(none) [[P:%.*]]) #[[ATTR7:[0-9]+]] {
 ; CHECK-NEXT:    ret void
 ;
   ret void
@@ -309,7 +309,7 @@ define void @already_nofreeobj(ptr nofreeobj %p) {
 
 define void @call_with_nofreeobj(ptr %p) {
 ; CHECK-LABEL: define void @call_with_nofreeobj(
-; CHECK-SAME: ptr captures(none) [[P:%.*]]) {
+; CHECK-SAME: ptr nofree captures(none) [[P:%.*]]) {
 ; CHECK-NEXT:    call void @takes_ptr(ptr nofreeobj captures(none) [[P]])
 ; CHECK-NEXT:    ret void
 ;



More information about the llvm-commits mailing list