diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst --- a/llvm/docs/LangRef.rst +++ b/llvm/docs/LangRef.rst @@ -1608,21 +1608,17 @@ call is dead after inlining. ``nofree`` This function attribute indicates that the function does not, directly or - transitively, call a memory-deallocation function (``free``, for example) - on a memory allocation which existed before the call. + transitively, call a memory-deallocation function (``free``, for example), + or cause such a function to be called by another thread, on a memory + allocation which existed before the call. - As a result, uncaptured pointers that are known to be dereferenceable - prior to a call to a function with the ``nofree`` attribute are still - known to be dereferenceable after the call. The capturing condition is - necessary in environments where the function might communicate the - pointer to another thread which then deallocates the memory. Alternatively, - ``nosync`` would ensure such communication cannot happen and even captured - pointers cannot be freed by the function. + As a result, pointers that are known to be dereferenceable prior to a call + to a function with the ``nofree`` attribute are still known to be + dereferenceable after the call. A ``nofree`` function is explicitly allowed to free memory which it - allocated or (if not ``nosync``) arrange for another thread to free - memory on it's behalf. As a result, perhaps surprisingly, a ``nofree`` - function can return a pointer to a previously deallocated memory object. + allocated. As a result, perhaps surprisingly, a ``nofree`` function can + return a pointer to a previously deallocated memory object. ``noimplicitfloat`` This attributes disables implicit floating-point instructions. ``noinline`` diff --git a/llvm/lib/IR/Value.cpp b/llvm/lib/IR/Value.cpp --- a/llvm/lib/IR/Value.cpp +++ b/llvm/lib/IR/Value.cpp @@ -758,7 +758,7 @@ // allocations in existance before the call; a nofree function *is* allowed // to free memory it allocated. const Function *F = A->getParent(); - if (F->doesNotFreeMemory() && F->hasNoSync()) + if (F->doesNotFreeMemory()) return false; } diff --git a/llvm/lib/Transforms/IPO/AttributorAttributes.cpp b/llvm/lib/Transforms/IPO/AttributorAttributes.cpp --- a/llvm/lib/Transforms/IPO/AttributorAttributes.cpp +++ b/llvm/lib/Transforms/IPO/AttributorAttributes.cpp @@ -1410,6 +1410,38 @@ struct AANoFreeImpl : public AANoFree { AANoFreeImpl(const IRPosition &IRP, Attributor &A) : AANoFree(IRP, A) {} + bool isReleaseAtomic(Instruction *I) { + if (!I->isAtomic()) + return false; + + AtomicOrdering Ordering; + switch (I->getOpcode()) { + case Instruction::Fence: + Ordering = cast(I)->getOrdering(); + break; + case Instruction::AtomicCmpXchg: + // The failure ordering cannot have release semantics. + Ordering = cast(I)->getSuccessOrdering(); + break; + case Instruction::AtomicRMW: + Ordering = cast(I)->getOrdering(); + break; + case Instruction::Store: + Ordering = cast(I)->getOrdering(); + break; + case Instruction::Load: + // Loads cannot have release ordering. + return false; + default: + llvm_unreachable( + "New atomic operations need to be known in the attributor."); + } + + return Ordering == AtomicOrdering::Release || + Ordering == AtomicOrdering::AcquireRelease || + Ordering == AtomicOrdering::SequentiallyConsistent; + } + /// See AbstractAttribute::updateImpl(...). ChangeStatus updateImpl(Attributor &A) override { auto CheckForNoFree = [&](Instruction &I) { @@ -1422,7 +1454,21 @@ return NoFreeAA.isAssumedNoFree(); }; - if (!A.checkForAllCallLikeInstructions(CheckForNoFree, *this)) + auto CheckForRemoteFree = [&](Instruction &I) { + // If a function F contains a volatile memory operation or a release + // atomic, we must assume that this operation may trigger a free() from + // another thread. This means that F cannot be assumed `nofree`. + // + // The same argument does not apply to acquire atomics: if a release + // atomic in another thread synchronizes-with an acquire atomic A in F in + // such a way that a free(%p) in the other thread happens-before A, then + // %p cannot be dereferenceable before A anyway (dereferences of %p that + // occur just before A would race against or happen-after the free(%p)). + return !I.isVolatile() && !isReleaseAtomic(&I); + }; + + if (!A.checkForAllCallLikeInstructions(CheckForNoFree, *this) || + !A.checkForAllReadWriteInstructions(CheckForRemoteFree, *this)) return indicatePessimisticFixpoint(); return ChangeStatus::UNCHANGED; } diff --git a/llvm/test/Transforms/Attributor/nofree.ll b/llvm/test/Transforms/Attributor/nofree.ll --- a/llvm/test/Transforms/Attributor/nofree.ll +++ b/llvm/test/Transforms/Attributor/nofree.ll @@ -270,13 +270,13 @@ ; IS__TUNIT____: Function Attrs: nofree noinline nosync nounwind readnone uwtable willreturn ; IS__TUNIT____-LABEL: define {{[^@]+}}@call_floor2 ; IS__TUNIT____-SAME: (float [[A:%.*]]) #[[ATTR3]] { -; IS__TUNIT____-NEXT: [[C:%.*]] = tail call float @llvm.floor.f32(float [[A]]) #[[ATTR12:[0-9]+]] +; IS__TUNIT____-NEXT: [[C:%.*]] = tail call float @llvm.floor.f32(float [[A]]) #[[ATTR13:[0-9]+]] ; IS__TUNIT____-NEXT: ret float [[C]] ; ; IS__CGSCC____: Function Attrs: nofree noinline nosync nounwind readnone uwtable willreturn ; IS__CGSCC____-LABEL: define {{[^@]+}}@call_floor2 ; IS__CGSCC____-SAME: (float [[A:%.*]]) #[[ATTR7:[0-9]+]] { -; IS__CGSCC____-NEXT: [[C:%.*]] = tail call float @llvm.floor.f32(float [[A]]) #[[ATTR13:[0-9]+]] +; IS__CGSCC____-NEXT: [[C:%.*]] = tail call float @llvm.floor.f32(float [[A]]) #[[ATTR14:[0-9]+]] ; IS__CGSCC____-NEXT: ret float [[C]] ; %c = tail call float @llvm.floor.f32(float %a) @@ -390,18 +390,17 @@ ret void } -; TEST 16: Use a release atomic (positive) -; TODO: Should this be negative? See discussion on https://reviews.llvm.org/D100676 +; TEST 16: Use a release atomic (negative) define void @test16(i8* %p) { -; IS__TUNIT____: Function Attrs: argmemonly nofree nounwind willreturn +; IS__TUNIT____: Function Attrs: argmemonly nounwind willreturn ; IS__TUNIT____-LABEL: define {{[^@]+}}@test16 -; IS__TUNIT____-SAME: (i8* nocapture nofree noundef nonnull dereferenceable(1) [[P:%.*]]) #[[ATTR9]] { +; IS__TUNIT____-SAME: (i8* nocapture noundef nonnull dereferenceable(1) [[P:%.*]]) #[[ATTR10:[0-9]+]] { ; IS__TUNIT____-NEXT: [[X:%.*]] = atomicrmw add i8* [[P]], i8 1 release, align 1 ; IS__TUNIT____-NEXT: ret void ; -; IS__CGSCC____: Function Attrs: argmemonly nofree norecurse nounwind willreturn +; IS__CGSCC____: Function Attrs: argmemonly norecurse nounwind willreturn ; IS__CGSCC____-LABEL: define {{[^@]+}}@test16 -; IS__CGSCC____-SAME: (i8* nocapture nofree noundef nonnull dereferenceable(1) [[P:%.*]]) #[[ATTR10]] { +; IS__CGSCC____-SAME: (i8* nocapture noundef nonnull dereferenceable(1) [[P:%.*]]) #[[ATTR11:[0-9]+]] { ; IS__CGSCC____-NEXT: [[X:%.*]] = atomicrmw add i8* [[P]], i8 1 release, align 1 ; IS__CGSCC____-NEXT: ret void ; @@ -420,13 +419,13 @@ ; ; IS__TUNIT____-LABEL: define {{[^@]+}}@nonnull_assume_pos ; IS__TUNIT____-SAME: (i8* nofree [[ARG1:%.*]], i8* [[ARG2:%.*]], i8* nofree [[ARG3:%.*]], i8* [[ARG4:%.*]]) { -; IS__TUNIT____-NEXT: call void @llvm.assume(i1 noundef true) #[[ATTR13:[0-9]+]] [ "nofree"(i8* [[ARG1]]), "nofree"(i8* [[ARG3]]) ] +; IS__TUNIT____-NEXT: call void @llvm.assume(i1 noundef true) #[[ATTR14:[0-9]+]] [ "nofree"(i8* [[ARG1]]), "nofree"(i8* [[ARG3]]) ] ; IS__TUNIT____-NEXT: call void @unknown(i8* nofree [[ARG1]], i8* [[ARG2]], i8* nofree [[ARG3]], i8* [[ARG4]]) ; IS__TUNIT____-NEXT: ret void ; ; IS__CGSCC____-LABEL: define {{[^@]+}}@nonnull_assume_pos ; IS__CGSCC____-SAME: (i8* nofree [[ARG1:%.*]], i8* [[ARG2:%.*]], i8* nofree [[ARG3:%.*]], i8* [[ARG4:%.*]]) { -; IS__CGSCC____-NEXT: call void @llvm.assume(i1 noundef true) #[[ATTR14:[0-9]+]] [ "nofree"(i8* [[ARG1]]), "nofree"(i8* [[ARG3]]) ] +; IS__CGSCC____-NEXT: call void @llvm.assume(i1 noundef true) #[[ATTR15:[0-9]+]] [ "nofree"(i8* [[ARG1]]), "nofree"(i8* [[ARG3]]) ] ; IS__CGSCC____-NEXT: call void @unknown(i8* nofree [[ARG1]], i8* [[ARG2]], i8* nofree [[ARG3]], i8* [[ARG4]]) ; IS__CGSCC____-NEXT: ret void ; @@ -516,10 +515,11 @@ ; IS__TUNIT____: attributes #[[ATTR7]] = { nofree nounwind } ; IS__TUNIT____: attributes #[[ATTR8:[0-9]+]] = { nobuiltin nofree nounwind } ; IS__TUNIT____: attributes #[[ATTR9]] = { argmemonly nofree nounwind willreturn } -; IS__TUNIT____: attributes #[[ATTR10:[0-9]+]] = { inaccessiblememonly nofree nosync nounwind willreturn } -; IS__TUNIT____: attributes #[[ATTR11:[0-9]+]] = { nounwind willreturn } -; IS__TUNIT____: attributes #[[ATTR12]] = { readnone willreturn } -; IS__TUNIT____: attributes #[[ATTR13]] = { willreturn } +; IS__TUNIT____: attributes #[[ATTR10]] = { argmemonly nounwind willreturn } +; IS__TUNIT____: attributes #[[ATTR11:[0-9]+]] = { inaccessiblememonly nofree nosync nounwind willreturn } +; IS__TUNIT____: attributes #[[ATTR12:[0-9]+]] = { nounwind willreturn } +; IS__TUNIT____: attributes #[[ATTR13]] = { readnone willreturn } +; IS__TUNIT____: attributes #[[ATTR14]] = { willreturn } ;. ; IS__CGSCC_OPM: attributes #[[ATTR0]] = { nounwind } ; IS__CGSCC_OPM: attributes #[[ATTR1]] = { noinline nounwind uwtable } @@ -532,10 +532,11 @@ ; IS__CGSCC_OPM: attributes #[[ATTR8]] = { nofree nounwind } ; IS__CGSCC_OPM: attributes #[[ATTR9:[0-9]+]] = { nobuiltin nofree nounwind } ; IS__CGSCC_OPM: attributes #[[ATTR10]] = { argmemonly nofree norecurse nounwind willreturn } -; IS__CGSCC_OPM: attributes #[[ATTR11:[0-9]+]] = { inaccessiblememonly nofree nosync nounwind willreturn } -; IS__CGSCC_OPM: attributes #[[ATTR12:[0-9]+]] = { nounwind willreturn } -; IS__CGSCC_OPM: attributes #[[ATTR13]] = { readnone willreturn } -; IS__CGSCC_OPM: attributes #[[ATTR14]] = { willreturn } +; IS__CGSCC_OPM: attributes #[[ATTR11]] = { argmemonly norecurse nounwind willreturn } +; IS__CGSCC_OPM: attributes #[[ATTR12:[0-9]+]] = { inaccessiblememonly nofree nosync nounwind willreturn } +; IS__CGSCC_OPM: attributes #[[ATTR13:[0-9]+]] = { nounwind willreturn } +; IS__CGSCC_OPM: attributes #[[ATTR14]] = { readnone willreturn } +; IS__CGSCC_OPM: attributes #[[ATTR15]] = { willreturn } ;. ; IS__CGSCC_NPM: attributes #[[ATTR0]] = { nounwind } ; IS__CGSCC_NPM: attributes #[[ATTR1]] = { noinline nounwind uwtable } @@ -548,8 +549,9 @@ ; IS__CGSCC_NPM: attributes #[[ATTR8]] = { nofree nounwind } ; IS__CGSCC_NPM: attributes #[[ATTR9:[0-9]+]] = { nobuiltin nofree nounwind } ; IS__CGSCC_NPM: attributes #[[ATTR10]] = { argmemonly nofree norecurse nounwind willreturn } -; IS__CGSCC_NPM: attributes #[[ATTR11:[0-9]+]] = { inaccessiblememonly nofree nosync nounwind willreturn } -; IS__CGSCC_NPM: attributes #[[ATTR12:[0-9]+]] = { nounwind willreturn } -; IS__CGSCC_NPM: attributes #[[ATTR13]] = { readnone willreturn } -; IS__CGSCC_NPM: attributes #[[ATTR14]] = { willreturn } +; IS__CGSCC_NPM: attributes #[[ATTR11]] = { argmemonly norecurse nounwind willreturn } +; IS__CGSCC_NPM: attributes #[[ATTR12:[0-9]+]] = { inaccessiblememonly nofree nosync nounwind willreturn } +; IS__CGSCC_NPM: attributes #[[ATTR13:[0-9]+]] = { nounwind willreturn } +; IS__CGSCC_NPM: attributes #[[ATTR14]] = { readnone willreturn } +; IS__CGSCC_NPM: attributes #[[ATTR15]] = { willreturn } ;.