-
Notifications
You must be signed in to change notification settings - Fork 14.1k
[IR] Add dead_on_return
attribute
#143271
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
[IR] Add dead_on_return
attribute
#143271
Conversation
@llvm/pr-subscribers-llvm-analysis @llvm/pr-subscribers-llvm-ir Author: Antonio Frighetto (antoniofrighetto) ChangesIntroduce Full diff: https://github.com/llvm/llvm-project/pull/143271.diff 13 Files Affected:
diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst
index 0958f6a4b729b..af1ae52e4e821 100644
--- a/llvm/docs/LangRef.rst
+++ b/llvm/docs/LangRef.rst
@@ -1741,6 +1741,20 @@ 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.
+
+ It is similar to ``byval`` in the regard that it is generally used to pass
+ structs and arrays by value, and the memory is caller-invisible when the
+ function returns. However, unlike ``byval``, it is intended for ABIs where the
+ *callee* allocates the hidden copy, rather than the caller. Stores that would
+ only be visible on the normal return path may be optimized out. Likewise,
+ optimizations may assume that the pointer does not alias any memory that
+ outlives the call.
+
+ 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..8e767a237c35b 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.
+ 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/Analysis/AliasAnalysis.cpp b/llvm/lib/Analysis/AliasAnalysis.cpp
index 3ec009ca4adde..924d97af79a17 100644
--- a/llvm/lib/Analysis/AliasAnalysis.cpp
+++ b/llvm/lib/Analysis/AliasAnalysis.cpp
@@ -818,7 +818,7 @@ bool llvm::isNoAliasCall(const Value *V) {
static bool isNoAliasOrByValArgument(const Value *V) {
if (const Argument *A = dyn_cast<Argument>(V))
- return A->hasNoAliasAttr() || A->hasByValAttr();
+ return A->hasNoAliasAttr() || A->hasByValAttr() || A->hasDeadOnReturnAttr();
return false;
}
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..aa09d7fa44812 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;
@@ -176,7 +182,8 @@ bool Argument::hasPassPointeeByValueCopyAttr() const {
AttributeList Attrs = getParent()->getAttributes();
return Attrs.hasParamAttr(getArgNo(), Attribute::ByVal) ||
Attrs.hasParamAttr(getArgNo(), Attribute::InAlloca) ||
- Attrs.hasParamAttr(getArgNo(), Attribute::Preallocated);
+ Attrs.hasParamAttr(getArgNo(), Attribute::Preallocated) ||
+ Attrs.hasParamAttr(getArgNo(), Attribute::DeadOnReturn);
}
bool Argument::hasPointeeInMemoryValueAttr() const {
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
+}
|
llvm/docs/LangRef.rst
Outdated
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nikic has opinions on poison in memory (namely, that there can't be poison in memory) that seem to contradict what this paragraph says.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is just an unavoidable consequence of the incoherence around uninitialized memory and poison memory. If we write "undef" here instead, someone is going to complain that this makes eliminating "store poison" incorrect, which is not UB per LangRef. I don't care which word is being used here. "poison" is consistent with the dead_on_unwind wording.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I did not realize that there's already docs that talk about storing poison for dead_on_unwind.
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.
389cb8a
to
bd4cc70
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good to me, but this should probably have a small RFC on discourse.
A small RFC can be found at: https://discourse.llvm.org/t/rfc-add-dead-on-return-attribute/86871. |
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 withbyval
, 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 adead_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.Related issue: #141200.