[llvm] [IR] Add `dead_on_return` attribute (PR #143271)

Antonio Frighetto via llvm-commits llvm-commits at lists.llvm.org
Tue Jun 10 09:58:05 PDT 2025


https://github.com/antoniofrighetto updated https://github.com/llvm/llvm-project/pull/143271

>From bd4cc703f1a68766aa1d73642dce0a8c1f55e015 Mon Sep 17 00:00:00 2001
From: Antonio Frighetto <me at antoniofrighetto.com>
Date: Sat, 7 Jun 2025 15:29:18 +0200
Subject: [PATCH 1/4] [IR] Add `dead_on_return` attribute
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Introduce `dead_on_return` attribute, which is meant to be taken
advantage by the frontend, and states that the memory pointed to
by the argument is dead upon function return. As with `byval`,
it is supposed to be used for passing aggregates by value. The
difference lies in the ABI: `byval` implies that the pointer is
explicitly passed as argument to the callee (during codegen the
copy is emitted as per byval contract), whereas a `dead_on_return`
-marked argument may imply that the copy already exists at the IR
level, is located at a specific stack offset within the caller,
and this memory will not be read further by the caller upon callee
return – or otherwise poison, if read before being written.
---
 llvm/docs/LangRef.rst                           | 17 +++++++++++++++++
 llvm/include/llvm/Bitcode/LLVMBitCodes.h        |  1 +
 llvm/include/llvm/IR/Argument.h                 |  9 ++++++---
 llvm/include/llvm/IR/Attributes.td              |  3 +++
 llvm/lib/Bitcode/Reader/BitcodeReader.cpp       |  2 ++
 llvm/lib/Bitcode/Writer/BitcodeWriter.cpp       |  2 ++
 llvm/lib/IR/Attributes.cpp                      |  3 ++-
 llvm/lib/IR/Function.cpp                        |  6 ++++++
 .../Transforms/Scalar/DeadStoreElimination.cpp  |  6 +++---
 llvm/lib/Transforms/Utils/CodeExtractor.cpp     |  1 +
 llvm/test/Bitcode/attributes.ll                 |  5 +++++
 .../Transforms/DeadStoreElimination/simple.ll   |  8 ++++++++
 llvm/test/Verifier/dead-on-return.ll            |  7 +++++++
 13 files changed, 63 insertions(+), 7 deletions(-)
 create mode 100644 llvm/test/Verifier/dead-on-return.ll

diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst
index 0958f6a4b729b..2be0e83506803 100644
--- a/llvm/docs/LangRef.rst
+++ b/llvm/docs/LangRef.rst
@@ -1741,6 +1741,23 @@ Currently, only the following parameter attributes are defined:
 
     This attribute cannot be applied to return values.
 
+``dead_on_return``
+    This attribute indicates that the memory pointed to by the argument is dead
+    upon normal function return, meaning that the caller will not depend on its
+    contents. Stores that would only be observable on the normal return path may
+    be elided.
+
+    Specifically, the behavior is as-if any memory written through the pointer
+    during the execution of the function is overwritten with a poison value
+    upon normal function return. The caller may access the memory, but any load
+    not preceded by a store will return poison.
+
+    This attribute does not imply aliasing properties. For pointer arguments that
+    do not alias other memory locations, ``noalias`` attribute may be used in
+    conjunction.
+
+    This attribute cannot be applied to return values.
+
 ``range(<ty> <a>, <b>)``
     This attribute expresses the possible range of the parameter or return value.
     If the value is not in the specified range, it is converted to poison.
diff --git a/llvm/include/llvm/Bitcode/LLVMBitCodes.h b/llvm/include/llvm/Bitcode/LLVMBitCodes.h
index b362a88963f6c..dc78eb4164acf 100644
--- a/llvm/include/llvm/Bitcode/LLVMBitCodes.h
+++ b/llvm/include/llvm/Bitcode/LLVMBitCodes.h
@@ -798,6 +798,7 @@ enum AttributeKindCodes {
   ATTR_KIND_NO_DIVERGENCE_SOURCE = 100,
   ATTR_KIND_SANITIZE_TYPE = 101,
   ATTR_KIND_CAPTURES = 102,
+  ATTR_KIND_DEAD_ON_RETURN = 103,
 };
 
 enum ComdatSelectionKindCodes {
diff --git a/llvm/include/llvm/IR/Argument.h b/llvm/include/llvm/IR/Argument.h
index 60854b17094bf..381627c715597 100644
--- a/llvm/include/llvm/IR/Argument.h
+++ b/llvm/include/llvm/IR/Argument.h
@@ -78,6 +78,9 @@ class Argument final : public Value {
   /// Return true if this argument has the byval attribute.
   LLVM_ABI bool hasByValAttr() const;
 
+  /// Return true if this argument has the dead_on_return attribute.
+  LLVM_ABI bool hasDeadOnReturnAttr() const;
+
   /// Return true if this argument has the byref attribute.
   LLVM_ABI bool hasByRefAttr() const;
 
@@ -87,9 +90,9 @@ class Argument final : public Value {
   /// Return true if this argument has the swifterror attribute.
   LLVM_ABI bool hasSwiftErrorAttr() const;
 
-  /// Return true if this argument has the byval, inalloca, or preallocated
-  /// attribute. These attributes represent arguments being passed by value,
-  /// with an associated copy between the caller and callee
+  /// Return true if this argument has the byval, inalloca, preallocated or
+  /// dead_on_return attribute. These attributes represent arguments being
+  /// passed by value, with an associated copy between the caller and callee.
   LLVM_ABI bool hasPassPointeeByValueCopyAttr() const;
 
   /// If this argument satisfies has hasPassPointeeByValueAttr, return the
diff --git a/llvm/include/llvm/IR/Attributes.td b/llvm/include/llvm/IR/Attributes.td
index d488c5f419b82..6a6510efa5ec4 100644
--- a/llvm/include/llvm/IR/Attributes.td
+++ b/llvm/include/llvm/IR/Attributes.td
@@ -198,6 +198,9 @@ def NoFree : EnumAttr<"nofree", IntersectAnd, [FnAttr, ParamAttr]>;
 /// Argument is dead if the call unwinds.
 def DeadOnUnwind : EnumAttr<"dead_on_unwind", IntersectAnd, [ParamAttr]>;
 
+/// Argument is dead upon function return.
+def DeadOnReturn : EnumAttr<"dead_on_return", IntersectAnd, [ParamAttr]>;
+
 /// Disable implicit floating point insts.
 def NoImplicitFloat : EnumAttr<"noimplicitfloat", IntersectPreserve, [FnAttr]>;
 
diff --git a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp
index 105edb943eb7f..b6da841e948fe 100644
--- a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp
+++ b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp
@@ -2246,6 +2246,8 @@ static Attribute::AttrKind getAttrFromCode(uint64_t Code) {
     return Attribute::NoExt;
   case bitc::ATTR_KIND_CAPTURES:
     return Attribute::Captures;
+  case bitc::ATTR_KIND_DEAD_ON_RETURN:
+    return Attribute::DeadOnReturn;
   }
 }
 
diff --git a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
index fad8ebfad9f9a..e33dc1d729389 100644
--- a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
+++ b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
@@ -940,6 +940,8 @@ static uint64_t getAttrKindEncoding(Attribute::AttrKind Kind) {
     return bitc::ATTR_KIND_NO_EXT;
   case Attribute::Captures:
     return bitc::ATTR_KIND_CAPTURES;
+  case Attribute::DeadOnReturn:
+    return bitc::ATTR_KIND_DEAD_ON_RETURN;
   case Attribute::EndAttrKinds:
     llvm_unreachable("Can not encode end-attribute kinds marker.");
   case Attribute::None:
diff --git a/llvm/lib/IR/Attributes.cpp b/llvm/lib/IR/Attributes.cpp
index ed485f9656996..78d00c05a90e7 100644
--- a/llvm/lib/IR/Attributes.cpp
+++ b/llvm/lib/IR/Attributes.cpp
@@ -2424,7 +2424,8 @@ AttributeMask AttributeFuncs::typeIncompatible(Type *Ty, AttributeSet AS,
           .addAttribute(Attribute::Writable)
           .addAttribute(Attribute::DeadOnUnwind)
           .addAttribute(Attribute::Initializes)
-          .addAttribute(Attribute::Captures);
+          .addAttribute(Attribute::Captures)
+          .addAttribute(Attribute::DeadOnReturn);
     if (ASK & ASK_UNSAFE_TO_DROP)
       Incompatible.addAttribute(Attribute::Nest)
           .addAttribute(Attribute::SwiftError)
diff --git a/llvm/lib/IR/Function.cpp b/llvm/lib/IR/Function.cpp
index dfffbbfcf5d2a..1c6c1d0e64f27 100644
--- a/llvm/lib/IR/Function.cpp
+++ b/llvm/lib/IR/Function.cpp
@@ -146,6 +146,12 @@ bool Argument::hasByValAttr() const {
   return hasAttribute(Attribute::ByVal);
 }
 
+bool Argument::hasDeadOnReturnAttr() const {
+  if (!getType()->isPointerTy())
+    return false;
+  return hasAttribute(Attribute::DeadOnReturn);
+}
+
 bool Argument::hasByRefAttr() const {
   if (!getType()->isPointerTy())
     return false;
diff --git a/llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp b/llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp
index 49a0c88922c3e..9874908bd8633 100644
--- a/llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp
@@ -1017,10 +1017,10 @@ struct DSEState {
       }
     }
 
-    // Treat byval or inalloca arguments the same as Allocas, stores to them are
-    // dead at the end of the function.
+    // Treat byval, inalloca or dead on return arguments the same as Allocas,
+    // stores to them are dead at the end of the function.
     for (Argument &AI : F.args())
-      if (AI.hasPassPointeeByValueCopyAttr())
+      if (AI.hasPassPointeeByValueCopyAttr() || AI.hasDeadOnReturnAttr())
         InvisibleToCallerAfterRet.insert({&AI, true});
 
     // Collect whether there is any irreducible control flow in the function.
diff --git a/llvm/lib/Transforms/Utils/CodeExtractor.cpp b/llvm/lib/Transforms/Utils/CodeExtractor.cpp
index c4894c90c127f..30f3e9ebab4e2 100644
--- a/llvm/lib/Transforms/Utils/CodeExtractor.cpp
+++ b/llvm/lib/Transforms/Utils/CodeExtractor.cpp
@@ -1025,6 +1025,7 @@ Function *CodeExtractor::constructFunctionDeclaration(
       case Attribute::EndAttrKinds:
       case Attribute::EmptyKey:
       case Attribute::TombstoneKey:
+      case Attribute::DeadOnReturn:
         llvm_unreachable("Not a function attribute");
       }
 
diff --git a/llvm/test/Bitcode/attributes.ll b/llvm/test/Bitcode/attributes.ll
index 7dd86a8c0eb16..8c1a76365e1b4 100644
--- a/llvm/test/Bitcode/attributes.ll
+++ b/llvm/test/Bitcode/attributes.ll
@@ -567,6 +567,11 @@ define void @captures(ptr captures(address) %p) {
   ret void
 }
 
+; CHECK: define void @dead_on_return(ptr dead_on_return %p)
+define void @dead_on_return(ptr dead_on_return %p) {
+  ret void
+}
+
 ; CHECK: attributes #0 = { noreturn }
 ; CHECK: attributes #1 = { nounwind }
 ; CHECK: attributes #2 = { memory(none) }
diff --git a/llvm/test/Transforms/DeadStoreElimination/simple.ll b/llvm/test/Transforms/DeadStoreElimination/simple.ll
index f8e594b5626a0..761090427f941 100644
--- a/llvm/test/Transforms/DeadStoreElimination/simple.ll
+++ b/llvm/test/Transforms/DeadStoreElimination/simple.ll
@@ -855,3 +855,11 @@ bb:
   store ptr null, ptr null, align 8
   ret void
 }
+
+define void @test50(ptr dead_on_return %p) {
+; CHECK-LABEL: @test50(
+; CHECK-NEXT:    ret void
+;
+  store i8 0, ptr %p
+  ret void
+}
diff --git a/llvm/test/Verifier/dead-on-return.ll b/llvm/test/Verifier/dead-on-return.ll
new file mode 100644
index 0000000000000..eb1d67fa319c2
--- /dev/null
+++ b/llvm/test/Verifier/dead-on-return.ll
@@ -0,0 +1,7 @@
+; RUN: not llvm-as -disable-output %s 2>&1 | FileCheck %s
+
+; CHECK: Attribute 'dead_on_return' applied to incompatible type!
+; CHECK-NEXT: ptr @arg_not_pointer
+define void @arg_not_pointer(i32 dead_on_return %arg) {
+  ret void
+}

>From 9bbe0d57bcc30ff299bd6bbf1417ab393d146ba1 Mon Sep 17 00:00:00 2001
From: Antonio Frighetto <me at antoniofrighetto.com>
Date: Tue, 10 Jun 2025 16:10:03 +0200
Subject: [PATCH 2/4] !fixup restore comment

---
 llvm/include/llvm/IR/Argument.h | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/llvm/include/llvm/IR/Argument.h b/llvm/include/llvm/IR/Argument.h
index 381627c715597..9d897bcc23f61 100644
--- a/llvm/include/llvm/IR/Argument.h
+++ b/llvm/include/llvm/IR/Argument.h
@@ -90,9 +90,9 @@ class Argument final : public Value {
   /// Return true if this argument has the swifterror attribute.
   LLVM_ABI bool hasSwiftErrorAttr() const;
 
-  /// Return true if this argument has the byval, inalloca, preallocated or
-  /// dead_on_return attribute. These attributes represent arguments being
-  /// passed by value, with an associated copy between the caller and callee.
+  /// Return true if this argument has the byval, inalloca, or preallocated
+  /// attribute. These attributes represent arguments being passed by value,
+  /// with an associated copy between the caller and callee.
   LLVM_ABI bool hasPassPointeeByValueCopyAttr() const;
 
   /// If this argument satisfies has hasPassPointeeByValueAttr, return the

>From b3a903e774414ec655d7349823b024f14559b9d4 Mon Sep 17 00:00:00 2001
From: Antonio Frighetto <me at antoniofrighetto.com>
Date: Tue, 10 Jun 2025 16:14:33 +0200
Subject: [PATCH 3/4] !fixup fix restore

---
 llvm/include/llvm/IR/Argument.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/include/llvm/IR/Argument.h b/llvm/include/llvm/IR/Argument.h
index 9d897bcc23f61..b9a73b3eb5fc2 100644
--- a/llvm/include/llvm/IR/Argument.h
+++ b/llvm/include/llvm/IR/Argument.h
@@ -92,7 +92,7 @@ class Argument final : public Value {
 
   /// Return true if this argument has the byval, inalloca, or preallocated
   /// attribute. These attributes represent arguments being passed by value,
-  /// with an associated copy between the caller and callee.
+  /// with an associated copy between the caller and callee
   LLVM_ABI bool hasPassPointeeByValueCopyAttr() const;
 
   /// If this argument satisfies has hasPassPointeeByValueAttr, return the

>From a8bff5ac21d5f325843a38b43bfaa2f27f0b5375 Mon Sep 17 00:00:00 2001
From: Antonio Frighetto <me at antoniofrighetto.com>
Date: Tue, 10 Jun 2025 18:57:44 +0200
Subject: [PATCH 4/4] !fixup add test, refine wording

---
 llvm/docs/LangRef.rst                          | 10 +++++-----
 .../Transforms/DeadStoreElimination/simple.ll  | 18 +++++++++++++++---
 2 files changed, 20 insertions(+), 8 deletions(-)

diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst
index 2be0e83506803..aa49f6de1289b 100644
--- a/llvm/docs/LangRef.rst
+++ b/llvm/docs/LangRef.rst
@@ -1743,18 +1743,18 @@ Currently, only the following parameter attributes are defined:
 
 ``dead_on_return``
     This attribute indicates that the memory pointed to by the argument is dead
-    upon normal function return, meaning that the caller will not depend on its
-    contents. Stores that would only be observable on the normal return path may
-    be elided.
+    upon function return, both upon normal return and if the calls unwinds, meaning
+    that the caller will not depend on its contents. Stores that would be observable
+    either on the return path or on the unwind path may be elided.
 
     Specifically, the behavior is as-if any memory written through the pointer
     during the execution of the function is overwritten with a poison value
-    upon normal function return. The caller may access the memory, but any load
+    upon function return. The caller may access the memory, but any load
     not preceded by a store will return poison.
 
     This attribute does not imply aliasing properties. For pointer arguments that
     do not alias other memory locations, ``noalias`` attribute may be used in
-    conjunction.
+    conjunction. Conversely, this attribute always implies ``dead_on_unwind``.
 
     This attribute cannot be applied to return values.
 
diff --git a/llvm/test/Transforms/DeadStoreElimination/simple.ll b/llvm/test/Transforms/DeadStoreElimination/simple.ll
index 761090427f941..72291382c1d82 100644
--- a/llvm/test/Transforms/DeadStoreElimination/simple.ll
+++ b/llvm/test/Transforms/DeadStoreElimination/simple.ll
@@ -425,7 +425,7 @@ define ptr @test25(ptr %p) nounwind {
 ; CHECK-NEXT:    [[P_4:%.*]] = getelementptr i8, ptr [[P:%.*]], i64 4
 ; CHECK-NEXT:    [[TMP:%.*]] = load i8, ptr [[P_4]], align 1
 ; CHECK-NEXT:    store i8 0, ptr [[P_4]], align 1
-; CHECK-NEXT:    [[Q:%.*]] = call ptr @strdup(ptr [[P]]) #[[ATTR13:[0-9]+]]
+; CHECK-NEXT:    [[Q:%.*]] = call ptr @strdup(ptr [[P]]) #[[ATTR14:[0-9]+]]
 ; CHECK-NEXT:    store i8 [[TMP]], ptr [[P_4]], align 1
 ; CHECK-NEXT:    ret ptr [[Q]]
 ;
@@ -856,10 +856,22 @@ bb:
   ret void
 }
 
-define void @test50(ptr dead_on_return %p) {
-; CHECK-LABEL: @test50(
+define void @test_dead_on_return(ptr dead_on_return %p) {
+; CHECK-LABEL: @test_dead_on_return(
 ; CHECK-NEXT:    ret void
 ;
   store i8 0, ptr %p
   ret void
 }
+
+define void @test_dead_on_return_maythrow(ptr dead_on_return %p) {
+; CHECK-LABEL: @test_dead_on_return_maythrow(
+; CHECK-NEXT:    call void @maythrow()
+; CHECK-NEXT:    ret void
+;
+  store i8 0, ptr %p
+  call void @maythrow()
+  ret void
+}
+
+declare void @maythrow() memory(none)



More information about the llvm-commits mailing list