[IR] Add nofreeobj parameter/return attribute#206445
Conversation
|
@llvm/pr-subscribers-llvm-transforms @llvm/pr-subscribers-llvm-analysis Author: Nikita Popov (nikic) ChangesAlternative to #203491. This adds a This means that the old Also rename The motivation here is intrinsincs like Full diff: https://github.com/llvm/llvm-project/pull/206445.diff 16 Files Affected:
diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst
index 5640eec987133..f8b1ced5837d0 100644
--- a/llvm/docs/LangRef.rst
+++ b/llvm/docs/LangRef.rst
@@ -1574,6 +1574,17 @@ Currently, only the following parameter attributes are defined:
This is not a valid attribute for return values.
+``nofreeobj``
+ On arguments, this indicates that the underlying object of the argument
+ cannot be freed during the execution of the function (where "during" is in
+ the same sense as for ``nofree``).
+
+ On return values, this indicates that the underlying object of the return
+ value cannot be freed at all after this point.
+
+ Unlike ``nofree``, it is not possible to free the underlying object through
+ a different pointer either.
+
.. _nest:
``nest``
@@ -13341,7 +13352,7 @@ Syntax:
::
- <result> = inttoptr <ty> <value> to <ty2>[, !dereferenceable !<deref_bytes_node>][, !dereferenceable_or_null !<deref_bytes_node>][, !nofree !<empty_node>] ; yields ty2
+ <result> = inttoptr <ty> <value> to <ty2>[, !dereferenceable !<deref_bytes_node>][, !dereferenceable_or_null !<deref_bytes_node>][, !nofreeobj !<empty_node>] ; yields ty2
Overview:
"""""""""
@@ -13366,10 +13377,11 @@ metadata name ``<deref_bytes_node>`` corresponding to a metadata node with one
``i64`` entry.
See ``dereferenceable_or_null`` metadata.
-The optional ``!nofree`` metadata must reference a single metadata name
+The optional ``!nofreeobj`` metadata must reference a single metadata name
``<empty_node>`` corresponding to a metadata node with no entries.
-The existence of the ``!nofree`` metadata on the instruction tells the optimizer
-that the memory pointed by the pointer will not be freed after this point.
+The existence of the ``!nofreeobj`` metadata on the instruction tells the
+optimizer that the underlying object of the pointer will not be freed after
+this point.
Semantics:
""""""""""
diff --git a/llvm/docs/ReleaseNotes.md b/llvm/docs/ReleaseNotes.md
index d1448eb469614..0544fe5fafd2d 100644
--- a/llvm/docs/ReleaseNotes.md
+++ b/llvm/docs/ReleaseNotes.md
@@ -102,6 +102,11 @@ Makes programs 10x faster by doing Special New Thing.
outlining. Add the `noinline` and `nooutline` attributes as well in cases
where inlining and outlining should additionally be disabled.
+* Added `nofreeobj` attribute for attributes and returns, which forbids
+ freeing the underlying object (as opposed to only frees through that specific
+ pointer). Renamed `!nofree` metadata to `!nofreeobj`, as it has the same
+ semantics.
+
### Changes to LLVM infrastructure
* Removed ``Constant::isZeroValue``. It was functionally identical to
diff --git a/llvm/include/llvm/Bitcode/LLVMBitCodes.h b/llvm/include/llvm/Bitcode/LLVMBitCodes.h
index af4cac996a78c..97c47b09ab9ad 100644
--- a/llvm/include/llvm/Bitcode/LLVMBitCodes.h
+++ b/llvm/include/llvm/Bitcode/LLVMBitCodes.h
@@ -821,6 +821,7 @@ enum AttributeKindCodes {
ATTR_KIND_NOOUTLINE = 107,
ATTR_KIND_FLATTEN = 108,
ATTR_KIND_NOIPA = 109,
+ ATTR_KIND_NOFREEOBJ = 110,
};
enum ComdatSelectionKindCodes {
diff --git a/llvm/include/llvm/IR/Attributes.td b/llvm/include/llvm/IR/Attributes.td
index 4e45100b54d38..ea523cad69cd5 100644
--- a/llvm/include/llvm/IR/Attributes.td
+++ b/llvm/include/llvm/IR/Attributes.td
@@ -201,8 +201,12 @@ def NoDuplicate : EnumAttr<"noduplicate", IntersectPreserve, [FnAttr]>;
def NoExt : EnumAttr<"noext", IntersectPreserve, [ParamAttr, RetAttr]>;
/// Function does not deallocate memory.
+/// Argument cannot be freed based on the argument pointer.
def NoFree : EnumAttr<"nofree", IntersectAnd, [FnAttr, ParamAttr]>;
+/// Underlying object of argument/return cannot be freed.
+def NoFreeObj : EnumAttr<"nofreeobj", IntersectAnd, [ParamAttr, RetAttr]>;
+
/// Argument is dead if the call unwinds.
def DeadOnUnwind : EnumAttr<"dead_on_unwind", IntersectAnd, [ParamAttr]>;
diff --git a/llvm/include/llvm/IR/FixedMetadataKinds.def b/llvm/include/llvm/IR/FixedMetadataKinds.def
index 552c1f60f2d38..ece7d8ae87b56 100644
--- a/llvm/include/llvm/IR/FixedMetadataKinds.def
+++ b/llvm/include/llvm/IR/FixedMetadataKinds.def
@@ -54,7 +54,7 @@ LLVM_FIXED_MD_KIND(MD_coro_outside_frame, "coro.outside.frame", 39)
LLVM_FIXED_MD_KIND(MD_mmra, "mmra", 40)
LLVM_FIXED_MD_KIND(MD_noalias_addrspace, "noalias.addrspace", 41)
LLVM_FIXED_MD_KIND(MD_callee_type, "callee_type", 42)
-LLVM_FIXED_MD_KIND(MD_nofree, "nofree", 43)
+LLVM_FIXED_MD_KIND(MD_nofreeobj, "nofreeobj", 43)
LLVM_FIXED_MD_KIND(MD_captures, "captures", 44)
LLVM_FIXED_MD_KIND(MD_alloc_token, "alloc_token", 45)
LLVM_FIXED_MD_KIND(MD_implicit_ref, "implicit.ref", 46)
diff --git a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp
index f6366061c9b52..6ab187db5e9ae 100644
--- a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp
+++ b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp
@@ -2152,6 +2152,8 @@ static Attribute::AttrKind getAttrFromCode(uint64_t Code) {
return Attribute::NoDuplicate;
case bitc::ATTR_KIND_NOFREE:
return Attribute::NoFree;
+ case bitc::ATTR_KIND_NOFREEOBJ:
+ return Attribute::NoFreeObj;
case bitc::ATTR_KIND_NO_IMPLICIT_FLOAT:
return Attribute::NoImplicitFloat;
case bitc::ATTR_KIND_NO_INLINE:
diff --git a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
index c4058fe66ff0b..430878dbfa839 100644
--- a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
+++ b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
@@ -862,6 +862,8 @@ static uint64_t getAttrKindEncoding(Attribute::AttrKind Kind) {
return bitc::ATTR_KIND_NO_DUPLICATE;
case Attribute::NoFree:
return bitc::ATTR_KIND_NOFREE;
+ case Attribute::NoFreeObj:
+ return bitc::ATTR_KIND_NOFREEOBJ;
case Attribute::NoImplicitFloat:
return bitc::ATTR_KIND_NO_IMPLICIT_FLOAT;
case Attribute::NoInline:
diff --git a/llvm/lib/IR/Attributes.cpp b/llvm/lib/IR/Attributes.cpp
index 4087b25951a1c..e69b848c0eb66 100644
--- a/llvm/lib/IR/Attributes.cpp
+++ b/llvm/lib/IR/Attributes.cpp
@@ -2520,7 +2520,9 @@ AttributeMask AttributeFuncs::typeIncompatible(Type *Ty, AttributeSet AS,
.addAttribute(Attribute::DeadOnUnwind)
.addAttribute(Attribute::Initializes)
.addAttribute(Attribute::Captures)
- .addAttribute(Attribute::DeadOnReturn);
+ .addAttribute(Attribute::DeadOnReturn)
+ .addAttribute(Attribute::NoFree)
+ .addAttribute(Attribute::NoFreeObj);
if (ASK & ASK_UNSAFE_TO_DROP)
Incompatible.addAttribute(Attribute::Nest)
.addAttribute(Attribute::SwiftError)
diff --git a/llvm/lib/IR/Value.cpp b/llvm/lib/IR/Value.cpp
index d3f90bd81bbe2..6bbb601a6c08e 100644
--- a/llvm/lib/IR/Value.cpp
+++ b/llvm/lib/IR/Value.cpp
@@ -861,12 +861,21 @@ bool Value::canBeFreed() const {
// another pointer to the same allocation. Readonly implies nofree.
if ((A->hasNoFreeAttr() || A->onlyReadsMemory()) && A->hasNoAliasAttr())
return false;
+
+ // nofreeobj means that the underlying object cannot be freed, even
+ // through a different pointer.
+ if (A->hasAttribute(Attribute::NoFreeObj))
+ return false;
}
if (auto *ITP = dyn_cast<IntToPtrInst>(this);
- ITP && ITP->hasMetadata(LLVMContext::MD_nofree))
+ ITP && ITP->hasMetadata(LLVMContext::MD_nofreeobj))
return false;
+ if (auto *CB = dyn_cast<CallBase>(this))
+ if (CB->hasRetAttr(Attribute::NoFreeObj))
+ return false;
+
const Function *F = nullptr;
if (auto *I = dyn_cast<Instruction>(this))
F = I->getFunction();
diff --git a/llvm/lib/IR/Verifier.cpp b/llvm/lib/IR/Verifier.cpp
index 648446555793b..12d16565b11f1 100644
--- a/llvm/lib/IR/Verifier.cpp
+++ b/llvm/lib/IR/Verifier.cpp
@@ -356,7 +356,7 @@ class Verifier : public InstVisitor<Verifier>, VerifierSupport {
void visitNoFPClassMetadata(Instruction &I, MDNode *Range, Type *Ty);
void visitNoaliasAddrspaceMetadata(Instruction &I, MDNode *Range, Type *Ty);
void visitDereferenceableMetadata(Instruction &I, MDNode *MD);
- void visitNofreeMetadata(Instruction &I, MDNode *MD);
+ void visitNoFreeObjMetadata(Instruction &I, MDNode *MD);
void visitProfMetadata(Instruction &I, MDNode *MD);
void visitCallStackMetadata(MDNode *MD);
void visitMemProfMetadata(Instruction &I, MDNode *MD);
@@ -5179,11 +5179,12 @@ void Verifier::visitDereferenceableMetadata(Instruction& I, MDNode* MD) {
&I);
}
-void Verifier::visitNofreeMetadata(Instruction &I, MDNode *MD) {
- Check(I.getType()->isPointerTy(), "nofree applies only to pointer types", &I);
- Check((isa<IntToPtrInst>(I)), "nofree applies only to inttoptr instruction",
+void Verifier::visitNoFreeObjMetadata(Instruction &I, MDNode *MD) {
+ Check(I.getType()->isPointerTy(), "nofreeobj applies only to pointer types",
&I);
- Check(MD->getNumOperands() == 0, "nofree metadata must be empty", &I);
+ Check((isa<IntToPtrInst>(I)),
+ "nofreeobj applies only to inttoptr instruction", &I);
+ Check(MD->getNumOperands() == 0, "nofreeobj metadata must be empty", &I);
}
void Verifier::visitProfMetadata(Instruction &I, MDNode *MD) {
@@ -5787,8 +5788,8 @@ void Verifier::visitInstruction(Instruction &I) {
if (MDNode *MD = I.getMetadata(LLVMContext::MD_dereferenceable_or_null))
visitDereferenceableMetadata(I, MD);
- if (MDNode *MD = I.getMetadata(LLVMContext::MD_nofree))
- visitNofreeMetadata(I, MD);
+ if (MDNode *MD = I.getMetadata(LLVMContext::MD_nofreeobj))
+ visitNoFreeObjMetadata(I, MD);
if (MDNode *TBAA = I.getMetadata(LLVMContext::MD_tbaa))
TBAAVerifyHelper.visitTBAAMetadata(&I, TBAA);
diff --git a/llvm/lib/Transforms/Utils/CodeExtractor.cpp b/llvm/lib/Transforms/Utils/CodeExtractor.cpp
index 975294ab00f73..c612135f44fc2 100644
--- a/llvm/lib/Transforms/Utils/CodeExtractor.cpp
+++ b/llvm/lib/Transforms/Utils/CodeExtractor.cpp
@@ -1052,6 +1052,7 @@ Function *CodeExtractor::constructFunctionDeclaration(
case Attribute::Range:
case Attribute::Initializes:
case Attribute::NoExt:
+ case Attribute::NoFreeObj:
// These are not really attributes.
case Attribute::None:
case Attribute::EndAttrKinds:
diff --git a/llvm/test/Analysis/ValueTracking/memory-dereferenceable.ll b/llvm/test/Analysis/ValueTracking/memory-dereferenceable.ll
index f7e69b0c1f04f..09c8e74a5c675 100644
--- a/llvm/test/Analysis/ValueTracking/memory-dereferenceable.ll
+++ b/llvm/test/Analysis/ValueTracking/memory-dereferenceable.ll
@@ -301,6 +301,23 @@ define void @infer_missing_noalias2(ptr dereferenceable(8) readonly %p) {
ret void
}
+; CHECK-LABEL: 'nofreeobj_arg'
+; CHECK: %p
+define void @nofreeobj_arg(ptr dereferenceable(8) nofreeobj %p) {
+ call void @mayfree()
+ %v = load i32, ptr %p
+ ret void
+}
+
+; CHECK-LABEL: 'nofreeobj_ret'
+; CHECK: %p
+define void @nofreeobj_ret() {
+ %p = call dereferenceable(8) nofreeobj ptr @foo()
+ call void @mayfree()
+ %v = load i32, ptr %p
+ ret void
+}
+
; Just check that we don't crash.
; CHECK-LABEL: 'opaque_type_crasher'
define void @opaque_type_crasher(ptr dereferenceable(16) %a, i1 %arg) {
diff --git a/llvm/test/Bitcode/attributes.ll b/llvm/test/Bitcode/attributes.ll
index f696f2dd12323..3d5272f053251 100644
--- a/llvm/test/Bitcode/attributes.ll
+++ b/llvm/test/Bitcode/attributes.ll
@@ -597,6 +597,11 @@ define void @noipa() noipa {
ret void
}
+; CHECK: define nofreeobj ptr @nofreeobj(ptr nofreeobj %p)
+define nofreeobj ptr @nofreeobj(ptr nofreeobj %p) {
+ ret ptr %p
+}
+
; CHECK: attributes #0 = { noreturn }
; CHECK: attributes #1 = { nounwind }
; CHECK: attributes #2 = { memory(none) }
diff --git a/llvm/test/Transforms/LICM/hoist-speculatable-load.ll b/llvm/test/Transforms/LICM/hoist-speculatable-load.ll
index 31236e8f29d60..ab754a1f9a9d6 100644
--- a/llvm/test/Transforms/LICM/hoist-speculatable-load.ll
+++ b/llvm/test/Transforms/LICM/hoist-speculatable-load.ll
@@ -4,7 +4,7 @@
define void @f(i32 %ptr_i, ptr %ptr2, i1 %cond) {
; CHECK-LABEL: @f(
; CHECK-NEXT: entry:
-; CHECK-NEXT: [[PTR:%.*]] = inttoptr i32 [[PTR_I:%.*]] to ptr, !nofree [[META0:![0-9]+]]
+; CHECK-NEXT: [[PTR:%.*]] = inttoptr i32 [[PTR_I:%.*]] to ptr, !nofreeobj [[META0:![0-9]+]]
; CHECK-NEXT: call void @llvm.assume(i1 true) [ "align"(ptr [[PTR]], i32 16), "dereferenceable"(ptr [[PTR]], i32 16) ]
; CHECK-NEXT: br i1 [[COND:%.*]], label [[FOR_BODY_LR_PH:%.*]], label [[IF0:%.*]]
; CHECK: if0:
@@ -27,7 +27,7 @@ define void @f(i32 %ptr_i, ptr %ptr2, i1 %cond) {
; CHECK-NEXT: ret void
;
entry:
- %ptr = inttoptr i32 %ptr_i to ptr, !nofree !{}
+ %ptr = inttoptr i32 %ptr_i to ptr, !nofreeobj !{}
call void @llvm.assume(i1 true) [ "align"(ptr %ptr, i32 16), "dereferenceable"(ptr %ptr, i32 16) ]
br i1 %cond, label %for.body.lr.ph, label %if0
diff --git a/llvm/test/Verifier/nofree.ll b/llvm/test/Verifier/nofree.ll
new file mode 100644
index 0000000000000..2775613c75426
--- /dev/null
+++ b/llvm/test/Verifier/nofree.ll
@@ -0,0 +1,16 @@
+; RUN: not opt -S < %s 2>&1 | FileCheck %s
+
+; CHECK: Attribute 'nofree' applied to incompatible type!
+define void @test(i32 nofree %p) {
+ ret void
+}
+
+; CHECK: Attribute 'nofreeobj' applied to incompatible type!
+define void @test2(i32 nofreeobj %p) {
+ ret void
+}
+
+; CHECK: Attribute 'nofreeobj' applied to incompatible type!
+define nofreeobj i32 @test3() {
+ ret i32 0
+}
diff --git a/llvm/test/Verifier/nofree_metadata.ll b/llvm/test/Verifier/nofree_metadata.ll
index e04f5b9f1c522..40a4d3e28bcd1 100644
--- a/llvm/test/Verifier/nofree_metadata.ll
+++ b/llvm/test/Verifier/nofree_metadata.ll
@@ -2,14 +2,14 @@
declare ptr @dummy()
-; CHECK: nofree applies only to inttoptr instruction
+; CHECK: nofreeobj applies only to inttoptr instruction
define void @test_not_inttoptr() {
- call ptr @dummy(), !nofree !{}
+ call ptr @dummy(), !nofreeobj !{}
ret void
}
-; CHECK: nofree metadata must be empty
+; CHECK: nofreeobj metadata must be empty
define void @test_invalid_arg(i32 %p) {
- inttoptr i32 %p to ptr, !nofree !{i32 0}
+ inttoptr i32 %p to ptr, !nofreeobj !{i32 0}
ret void
}
|
| the same sense as for ``nofree``). | ||
|
|
||
| On return values, this indicates that the underlying object of the return | ||
| value cannot be freed at all after this point. |
There was a problem hiding this comment.
Should you like this better:
| value cannot be freed at all after this point. | |
| value cannot be freed from this point forward, throughout program's execution. |
| This is not a valid attribute for return values. | ||
|
|
||
| ``nofreeobj`` | ||
| On arguments, this indicates that the underlying object of the argument |
There was a problem hiding this comment.
Should we mention explicitly nofreeobj strictly implies nofree for arguments?
| value cannot be freed at all after this point. | ||
|
|
||
| Unlike ``nofree``, it is not possible to free the underlying object through | ||
| a different pointer either. |
There was a problem hiding this comment.
Following up on #195658 (comment), should we be explicit and mention the interaction with noalias (nofreeobj being primarily useful in cases noalias does not apply)?
There was a problem hiding this comment.
I added some discussion on the relationship between nofreeobj and nofree/noalias.
|
Rebased over the LangRef migration to Markdown. |
efriedma-quic
left a comment
There was a problem hiding this comment.
Do we need autoupgrade? I guess if existing IR uses !nofree, we just ignore it, so it's not strictly necessary.
Right -- we effectively drop the metadata, which is safe, just suboptimal. Looking back at #153149 this was added for use by a GPU compiler, so I doubt bitcode upgrade is relevant. It would be easy to add though. |
Alternative to #203491.
This adds a
nofreeobjattribute, which says that the underlying object cannot be freed. For arguments during the execution of the function, for returns from that point forward. This is unlikenofreewhich only prevents freed through the specific pointer, and as such requiresnoaliasto be useful for function-internal reasoning.This means that the old
dereferenceablebecomes strictly equivalent todereferenceable+nofreeobjunder dereferenceable-at-point semantics.Also rename
!nofreeto!nofreeobj, because it's semantics match thenofreeobjattribute, notnofree.The motivation here is intrinsics like
@llvm.amdgcn.kernarg.segment.ptrwhich aredereferenceable+nofreeobj, but notnoalias.