[llvm] [LangRef] Do not allow free via synchronization in nofree (PR #195658)
Nikita Popov via llvm-commits
llvm-commits at lists.llvm.org
Wed May 6 09:17:59 PDT 2026
https://github.com/nikic updated https://github.com/llvm/llvm-project/pull/195658
>From fe869e57b0d1020f0d2c6a01d814e8222168f7fc Mon Sep 17 00:00:00 2001
From: Nikita Popov <npopov at redhat.com>
Date: Mon, 4 May 2026 15:54:09 +0200
Subject: [PATCH 1/4] [LangRef] Do not allow free via synchronization in nofree
The nofree attribute is currently specified to only forbid direct
free calls inside the function. A nofree function is still allowed
to compel a pointer to be freed by a different thread through
synchronization.
This is currently only spelled out for the function-level nofree
attribute, but I assume the same semantics also hold for argument
nofree (and this matches how the Attributor implementation infers
it).
The original motivation for this definition was to keep the
attributes orthogonal and independently inferable. However, the
problem is that nosync is a too strong condition: It excludes *any*
synchronization, not just synchronization that results in the
free of a pointer.
Some frontends like Rust can guarantee that most pointer arguments
cannot be freed for the duration of a function call, including via
synchronization. However, they cannot guarantee that no
synchronization takes place at all. The current definition of
nofree makes this inexpressible.
This PR makes a few changes to the nofree spec:
* Function-level nofree now excludes free via synchronization.
* Argument-level nofree now also excludes free via synchronization
(it previously made not statement in either direction).
* Argument-level nofree is now specified to only apply to the
particular pointer (similar to `captures` etc). The underlying
object may still be freed via a different argument to which the
same pointer is passed. (I'm open to changing this, but that
would essentially limit inference to noalias arguments.)
Note: This PR doesn't include the necessary inference changes to
comply with the new semantics yet. I'd like to make sure we're
aligned on the direction first.
---
llvm/docs/LangRef.rst | 26 +++++++++++++-------------
1 file changed, 13 insertions(+), 13 deletions(-)
diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst
index 912cca72e1ad5..57e5743dde39e 100644
--- a/llvm/docs/LangRef.rst
+++ b/llvm/docs/LangRef.rst
@@ -1543,8 +1543,11 @@ Currently, only the following parameter attributes are defined:
is null is captured in some other way.
``nofree``
- This indicates that the callee does not free the pointer argument. This is not
- a valid attribute for return values.
+ This indicates that the callee does not free the pointer argument, or
+ cause it to be freed through synchronization.
+
+ This is not a valid attribute for return values. This attribute applies
+ only to the particular copy of the pointer passed in this argument.
.. _nest:
@@ -2328,20 +2331,17 @@ For example:
``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.
+ on a memory allocation which existed before the call, or make such a call
+ visible through synchronization.
- 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 its behalf. As a result, perhaps surprisingly, a ``nofree``
- function can return a pointer to a previously deallocated
+ allocated or arrange for another thread to free such memory on its behalf.
+ As a result, perhaps surprisingly, a ``nofree`` function can return a
+ pointer to a previously deallocated
:ref:`allocated object<allocatedobjects>`.
``noimplicitfloat``
Disallows implicit floating-point code. This inhibits optimizations that
>From c11fc0a911fb8b3a929d4479f5584867cecccc6b Mon Sep 17 00:00:00 2001
From: Nikita Popov <npopov at redhat.com>
Date: Tue, 5 May 2026 11:57:22 +0200
Subject: [PATCH 2/4] Provenance based definition, happens-before
---
llvm/docs/LangRef.rst | 27 +++++++++++++++++++--------
1 file changed, 19 insertions(+), 8 deletions(-)
diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst
index 57e5743dde39e..028ad92293f10 100644
--- a/llvm/docs/LangRef.rst
+++ b/llvm/docs/LangRef.rst
@@ -1543,11 +1543,20 @@ Currently, only the following parameter attributes are defined:
is null is captured in some other way.
``nofree``
- This indicates that the callee does not free the pointer argument, or
- cause it to be freed through synchronization.
+ This indicates that a pointer based on this argument cannot be freed during
+ the execution of the function.
- This is not a valid attribute for return values. This attribute applies
- only to the particular copy of the pointer passed in this argument.
+ More formally, a ``nofree`` argument provides the callee with a new pointer
+ with the same address and a derived provenance, where the derived
+ provenance has the same permissions as the original, except that the
+ underlying object cannot be freed until the function returns (or unwinds),
+ otherwise the behavior is undefined. This includes frees in nested function
+ calls and on other threads.
+
+ Notably, it is still possible to free the underlying object through a
+ pointer that is not based on the argument.
+
+ This is not a valid attribute for return values.
.. _nest:
@@ -2329,10 +2338,12 @@ For example:
internal linkage and only has one call site, so the original
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, or make such a call
- visible through synchronization.
+ This function attribute indicates that the function does not free any memory
+ allocation which existed before the call, either through direct calls to
+ a memory-deallocation function like ``free``, or through synchronization.
+ Freeing through synchronization here means that a deallocation
+ happens-before the function exit but does not happens-before the function
+ entry.
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
>From 8ac7ca7f107b55fe2a6cc6f50269bcdcebcf5375 Mon Sep 17 00:00:00 2001
From: Nikita Popov <npopov at redhat.com>
Date: Wed, 6 May 2026 10:44:33 +0200
Subject: [PATCH 3/4] Italicize happens-before
---
llvm/docs/LangRef.rst | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst
index 028ad92293f10..92fb1e115d0d5 100644
--- a/llvm/docs/LangRef.rst
+++ b/llvm/docs/LangRef.rst
@@ -2342,8 +2342,8 @@ For example:
allocation which existed before the call, either through direct calls to
a memory-deallocation function like ``free``, or through synchronization.
Freeing through synchronization here means that a deallocation
- happens-before the function exit but does not happens-before the function
- entry.
+ *happens-before* the function exit but does not *happens-before* the
+ function entry.
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
>From f98f0dd25a8473403abdd044b7664187a90d0e97 Mon Sep 17 00:00:00 2001
From: Nikita Popov <npopov at redhat.com>
Date: Wed, 6 May 2026 17:32:53 +0200
Subject: [PATCH 4/4] Try to clarify synchronization interaction
---
llvm/docs/LangRef.rst | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst
index 92fb1e115d0d5..e358bcc621064 100644
--- a/llvm/docs/LangRef.rst
+++ b/llvm/docs/LangRef.rst
@@ -1550,8 +1550,8 @@ Currently, only the following parameter attributes are defined:
with the same address and a derived provenance, where the derived
provenance has the same permissions as the original, except that the
underlying object cannot be freed until the function returns (or unwinds),
- otherwise the behavior is undefined. This includes frees in nested function
- calls and on other threads.
+ otherwise the behavior is undefined. This includes frees of the pointer on
+ other threads if the free *happens-before* the function return.
Notably, it is still possible to free the underlying object through a
pointer that is not based on the argument.
More information about the llvm-commits
mailing list