[llvm] [IR] Add `dead_on_return` attribute (PR #143271)
Antonio Frighetto via llvm-commits
llvm-commits at lists.llvm.org
Wed Jul 2 00:30:03 PDT 2025
https://github.com/antoniofrighetto updated https://github.com/llvm/llvm-project/pull/143271
>From f1cc0b607b03548028db3ca57bb057b2599b1711 Mon Sep 17 00:00:00 2001
From: Antonio Frighetto <me at antoniofrighetto.com>
Date: Wed, 2 Jul 2025 09:23:22 +0200
Subject: [PATCH] [IR] Introduce `dead_on_return` attribute
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Add `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 implies that the copy
already exists in the IR, 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.
RFC: https://discourse.llvm.org/t/rfc-add-dead-on-return-attribute/86871.
---
llvm/docs/LangRef.rst | 17 +++++++++
llvm/include/llvm/Bitcode/LLVMBitCodes.h | 1 +
llvm/include/llvm/IR/Argument.h | 3 ++
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 ++++
.../Scalar/DeadStoreElimination.cpp | 6 ++--
llvm/lib/Transforms/Utils/CodeExtractor.cpp | 1 +
llvm/test/Bitcode/attributes.ll | 5 +++
.../Transforms/DeadStoreElimination/simple.ll | 35 ++++++++++++++++++-
llvm/test/Verifier/dead-on-return.ll | 7 ++++
13 files changed, 86 insertions(+), 5 deletions(-)
create mode 100644 llvm/test/Verifier/dead-on-return.ll
diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst
index d77c659055236..2a45f4f2dac47 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 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 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. Conversely, this attribute always implies ``dead_on_unwind``.
+
+ 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..b9a73b3eb5fc2 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;
diff --git a/llvm/include/llvm/IR/Attributes.td b/llvm/include/llvm/IR/Attributes.td
index 0bcd15eeed879..112853965407c 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 c6549e72d48c0..de7bf9b7f4d79 100644
--- a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp
+++ b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp
@@ -2244,6 +2244,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 2a2dd085a9461..da00eec049d36 100644
--- a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
+++ b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
@@ -938,6 +938,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 bfb32ff9995d1..d1fbcb9e893a7 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 3e7fcbb983738..7a03663e129dc 100644
--- a/llvm/lib/IR/Function.cpp
+++ b/llvm/lib/IR/Function.cpp
@@ -130,6 +130,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 4a2eb9284a6ea..85dd9a1bf7161 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 242cf6d811b66..e28a1b4c27432 100644
--- a/llvm/lib/Transforms/Utils/CodeExtractor.cpp
+++ b/llvm/lib/Transforms/Utils/CodeExtractor.cpp
@@ -1020,6 +1020,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..6c04e15edc374 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]]
;
@@ -855,3 +855,36 @@ bb:
store ptr null, ptr null, align 8
ret void
}
+
+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
+}
+
+define ptr @test_dead_on_return_ptr_returned(ptr dead_on_return %p) {
+; CHECK-LABEL: @test_dead_on_return_ptr_returned(
+; CHECK-NEXT: [[LOCAL_VAR:%.*]] = alloca ptr, align 8
+; CHECK-NEXT: call void @opaque(ptr [[LOCAL_VAR]])
+; CHECK-NEXT: ret ptr [[P:%.*]]
+;
+ %local.var = alloca ptr
+ call void @opaque(ptr %local.var)
+ store ptr %local.var, ptr %p
+ ret ptr %p
+}
+
+declare void @opaque(ptr)
+declare void @maythrow() memory(none)
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
+}
More information about the llvm-commits
mailing list