Skip to content
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

[2025-02 CWG Motion 6] P3475R2 Defang and deprecate memory_order::consume #7682

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 6 additions & 111 deletions source/basic.tex
Original file line number Diff line number Diff line change
Expand Up @@ -6250,7 +6250,7 @@
operations on mutexes\iref{thread} that are specially identified as
synchronization operations. These operations play a special role in making
assignments in one thread visible to another. A synchronization operation on one
or more memory locations is either a consume operation, an acquire operation, a
or more memory locations is either an acquire operation, a
release operation, or both an acquire and release operation. A synchronization
operation without an associated memory location is a fence and can be either an
acquire fence, a release fence, or both an acquire and release fence. In
Expand Down Expand Up @@ -6306,122 +6306,17 @@
the value written'' by the last mutex release.
\end{note}

\pnum
An evaluation $A$ \defn{carries a dependency} to an evaluation $B$ if
\begin{itemize}
\item
the value of $A$ is used as an operand of $B$, unless:
\begin{itemize}
\item
$B$ is an invocation of any specialization of
\tcode{std::kill_dependency}\iref{atomics.order}, or
\item
$A$ is the left operand of a built-in logical \logop{and} (\tcode{\&\&},
see~\ref{expr.log.and}) or logical \logop{or} (\tcode{||}, see~\ref{expr.log.or})
operator, or
\item
$A$ is the left operand of a conditional (\tcode{?:}, see~\ref{expr.cond})
operator, or
\item
$A$ is the left operand of the built-in comma (\tcode{,})
operator\iref{expr.comma}; \end{itemize} or
\item
$A$ writes a scalar object or bit-field $M$, $B$ reads the value
written by $A$ from $M$, and $A$ is sequenced before $B$, or
\item
for some evaluation $X$, $A$ carries a dependency to $X$, and
$X$ carries a dependency to $B$.
\end{itemize}
\begin{note}
``Carries a dependency to'' is a subset of ``is sequenced before'',
and is similarly strictly intra-thread.
\end{note}

\pnum
An evaluation $A$ is \defn{dependency-ordered before} an evaluation
$B$ if
\begin{itemize}
\item
$A$ performs a release operation on an atomic object $M$, and, in
another thread, $B$ performs a consume operation on $M$ and reads
the value written by $A$, or

\item
for some evaluation $X$, $A$ is dependency-ordered before $X$ and
$X$ carries a dependency to $B$.

\end{itemize}
\begin{note}
The relation ``is dependency-ordered before'' is analogous to
``synchronizes with'', but uses release/consume in place of release/acquire.
\end{note}

\pnum
An evaluation $A$ \defn{inter-thread happens before} an evaluation $B$
if
\begin{itemize}
\item
$A$ synchronizes with $B$, or
\item
$A$ is dependency-ordered before $B$, or
\item
for some evaluation $X$
\begin{itemize}
\item
$A$ synchronizes with $X$ and $X$
is sequenced before $B$, or
\item
$A$ is sequenced before $X$ and $X$
inter-thread happens before $B$, or
\item
$A$ inter-thread happens before $X$ and $X$
inter-thread happens before $B$.
\end{itemize}
\end{itemize}
\begin{note}
The ``inter-thread happens before'' relation describes arbitrary
concatenations of ``sequenced before'', ``synchronizes with'' and
``dependency-ordered before'' relationships, with two exceptions. The first
exception is that a concatenation never ends with
``dependency-ordered before'' followed by ``sequenced before''. The reason for
this limitation is that a consume operation participating in a
``dependency-ordered before'' relationship provides ordering only with respect
to operations to which this consume operation actually carries a dependency. The
reason that this limitation applies only to the end of such a concatenation is
that any subsequent release operation will provide the required ordering for a
prior consume operation. The second exception is that a concatenation never
consist entirely of ``sequenced before''. The reasons for this
limitation are (1) to permit ``inter-thread happens before'' to be transitively
closed and (2) the ``happens before'' relation, defined below, provides for
relationships consisting entirely of ``sequenced before''.
\end{note}

\pnum
An evaluation $A$ \defn{happens before} an evaluation $B$
(or, equivalently, $B$ \defn{happens after} $A$) if
\begin{itemize}
\item $A$ is sequenced before $B$, or
\item $A$ inter-thread happens before $B$.
\end{itemize}
The implementation shall ensure that no program execution demonstrates a cycle
in the ``happens before'' relation.
\begin{note}
This cycle would otherwise be
possible only through the use of consume operations.
\end{note}

\pnum
An evaluation $A$ \defn{simply happens before} an evaluation $B$
(or, equivalently, $B$ happens after $A$)
if either
\begin{itemize}
\item $A$ is sequenced before $B$, or
\item $A$ synchronizes with $B$, or
\item $A$ simply happens before $X$ and
$X$ simply happens before $B$.
\item $A$ happens before $X$ and $X$ happens before $B$.
\end{itemize}
\begin{note}
In the absence of consume operations,
the happens before and simply happens before relations are identical.
An evaluation does not happen before itself.
\end{note}

\pnum
Expand All @@ -6434,7 +6329,7 @@
sequentially consistent atomic operations\iref{atomics.order}, or
\item there are evaluations $B$ and $C$
such that $A$ is sequenced before $B$,
$B$ simply happens before $C$, and
$B$ happens before $C$, and
$C$ is sequenced before $D$, or
\item there is an evaluation $B$ such that
$A$ strongly happens before $B$, and
Expand All @@ -6443,7 +6338,7 @@
\begin{note}
Informally, if $A$ strongly happens before $B$,
then $A$ appears to be evaluated before $B$
in all contexts. Strongly happens before excludes consume operations.
in all contexts.
\end{note}

\pnum
Expand Down
4 changes: 2 additions & 2 deletions source/compatibility.tex
Original file line number Diff line number Diff line change
Expand Up @@ -2368,8 +2368,8 @@
Avoid hard to diagnose or non-portable constructs.
\effect
Names of attribute identifiers may not be used as macro names. Valid \CppIII{}
code that defines \tcode{override}, \tcode{final},
\tcode{carries_dependency}, or \tcode{noreturn} as macros is invalid in this
code that defines \tcode{override}, \tcode{final}, or
\tcode{noreturn} as macros is invalid in this
revision of \Cpp{}.

\rSec2[diff.cpp03.language.support]{\ref{support}:
Expand Down
83 changes: 3 additions & 80 deletions source/declarations.tex
Original file line number Diff line number Diff line change
Expand Up @@ -9140,86 +9140,6 @@
\end{codeblock}
\end{example}

\rSec2[dcl.attr.depend]{Carries dependency attribute}%
\indextext{attribute!carries dependency}

\pnum
The \grammarterm{attribute-token} \tcode{carries_dependency} specifies
dependency propagation into and out of functions.
No
\grammarterm{attribute-argument-clause} shall be present. The attribute may be
applied to a parameter of a function or lambda, in
which case it specifies that the initialization of the parameter carries a
dependency to\iref{intro.multithread} each lvalue-to-rvalue
conversion\iref{conv.lval} of that object. The attribute may also be applied
to a function or a lambda call operator, in which case it
specifies that the return value, if any, carries a dependency to the evaluation
of the function call expression.

\pnum
The first declaration of a function shall specify the \tcode{carries_dependency} attribute for its
\grammarterm{declarator-id} if any declaration of the function specifies the
\tcode{carries_dependency} attribute. Furthermore, the first declaration of a function shall specify
the \tcode{carries_dependency} attribute for a parameter if any declaration of that function
specifies the \tcode{carries_dependency} attribute for that parameter. If a function or one of its
parameters is declared with the \tcode{carries_dependency} attribute in its first declaration in one
translation unit and the same function or one of its parameters is declared without the
\tcode{carries_dependency} attribute in its first declaration in another translation unit, the
program is ill-formed, no diagnostic required.

\pnum
\begin{note}
The \tcode{carries_dependency} attribute does not change the meaning of the
program, but might result in generation of more efficient code.
\end{note}

\pnum
\begin{example}
\begin{codeblock}
/* Translation unit A. */

struct foo { int* a; int* b; };
std::atomic<struct foo *> foo_head[10];
int foo_array[10][10];

[[carries_dependency]] struct foo* f(int i) {
return foo_head[i].load(memory_order::consume);
}

int g(int* x, int* y [[carries_dependency]]) {
return kill_dependency(foo_array[*x][*y]);
}

/* Translation unit B. */

[[carries_dependency]] struct foo* f(int i);
int g(int* x, int* y [[carries_dependency]]);

int c = 3;

void h(int i) {
struct foo* p;

p = f(i);
do_something_with(g(&c, p->a));
do_something_with(g(p->a, &c));
}
\end{codeblock}

The \tcode{carries_dependency} attribute on function \tcode{f} means that the
return value carries a dependency out of \tcode{f}, so that the implementation
need not constrain ordering upon return from \tcode{f}. Implementations of
\tcode{f} and its caller may choose to preserve dependencies instead of emitting
hardware memory ordering instructions (a.k.a.\ fences).
Function \tcode{g}'s second parameter has a \tcode{carries_dependency} attribute,
but its first parameter does not. Therefore, function \tcode{h}'s first call to
\tcode{g} carries a dependency into \tcode{g}, but its second call does not. The
implementation might need to insert a fence prior to the second call to
\tcode{g}.
\end{example}
\indextext{attribute|)}%
\indextext{declaration|)}

\rSec2[dcl.attr.deprecated]{Deprecated attribute}%
\indextext{attribute!deprecated}

Expand Down Expand Up @@ -9695,3 +9615,6 @@
could have the same address as \tcode{buckets}
if their respective types are all empty.
\end{example}

\indextext{attribute|)}%
\indextext{declaration|)}
24 changes: 24 additions & 0 deletions source/future.tex
Original file line number Diff line number Diff line change
Expand Up @@ -854,6 +854,9 @@
void atomic_init(volatile atomic<T>*, typename atomic<T>::value_type) noexcept;
template<class T>
void atomic_init(atomic<T>*, typename atomic<T>::value_type) noexcept;
template<class T>
constexpr T kill_dependency(T y) noexcept; // freestanding
inline constexpr memory_order memory_order_consume = memory_order::consume; // freestanding

#define @\libmacro{ATOMIC_VAR_INIT}@(value) @\seebelow@
}
Expand Down Expand Up @@ -924,3 +927,24 @@
\end{codeblock}
\end{example}
\end{itemdescr}

\rSec2[depr.atomics.order]{\tcode{memory_order::consume}}

\indexlibrarymember{consume}{memory_order}%
\pnum
The memory_order enumeration contains an additional enumerator:
\begin{codeblock}
consume = 1
\end{codeblock}
The \tcode{memory_order::consume} enumerator is allowed wherever
\tcode{memory_order::acquire} is allowed, and it has the same meaning.

\begin{itemdecl}
template<class T> constexpr T kill_dependency(T y) noexcept;
\end{itemdecl}

\begin{itemdescr}
\pnum
\returns
\tcode{y}.
\end{itemdescr}
1 change: 0 additions & 1 deletion source/preprocessor.tex
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,6 @@
\topline
\lhdr{Attribute} & \rhdr{Value} \\ \rowsep
\tcode{assume} & \tcode{202207L} \\
\tcode{carries_dependency} & \tcode{200809L} \\
\tcode{deprecated} & \tcode{201309L} \\
\tcode{fallthrough} & \tcode{201603L} \\
\tcode{likely} & \tcode{201803L} \\
Expand Down
Loading