[llvm] 13822d0 - [RFC][LangRef] Specify that the accessed bytes of concurrent atomics must be either disjoint or the same (#204329)
via llvm-commits
llvm-commits at lists.llvm.org
Tue Jul 21 00:17:45 PDT 2026
Author: Fabian Ritter
Date: 2026-07-21T09:17:39+02:00
New Revision: 13822d0b1808b759dd4f4d237ea347729f93b3ed
URL: https://github.com/llvm/llvm-project/commit/13822d0b1808b759dd4f4d237ea347729f93b3ed
DIFF: https://github.com/llvm/llvm-project/commit/13822d0b1808b759dd4f4d237ea347729f93b3ed.diff
LOG: [RFC][LangRef] Specify that the accessed bytes of concurrent atomics must be either disjoint or the same (#204329)
So far, the LangRef hasn't been clear on the semantics of partially overlapping
concurrent atomics in LLVM IR (specifically: a set of accesses marked as
`atomic` that would be in a data race if they weren't `atomic` and not all of
them access the exact same set of bytes).
What loads read is defined in terms of individual bytes, but the memory
ordering constraints are formulated closely to the C/C++ (and Java for
`unordered`) memory model, where partially overlapping atomics are not
possible. It's not obvious how concepts like C/C++'s per-location total
modification order for `monotonic` accesses map to accesses that can partially
overlap. While C/C++ relies on the modification order to ensure that atomics
cannot tear (i.e., atomic reads return bytes from two or more atomic writes),
our IR semantics (as written) currently does not guarantee this in the presence
of partially overlapping accesses.
This PR proposes a solution to this problem: It specifies that concurrent
overlapping atomics must access the exact same set of bytes to act atomically.
If they don't, they form a data race (i.e., participating loads read `undef`
for affected bytes) and the atomic ordering constaints do not apply. This empowers the rest of the specification to imply that
`monotonic` (or stronger) accesses do not tear. The PR also adds a constraint
to ensure non-tearing for `unordered` atomic accesses.
This solution implies that transformations that merge adjacent atomic
loads/stores into wider atomic loads/stores are generally incorrect.
Related RFC: https://discourse.llvm.org/t/rfc-semantics-of-partially-overlapping-atomic-accesses/91092
Added:
Modified:
llvm/docs/LangRef.md
Removed:
################################################################################
diff --git a/llvm/docs/LangRef.md b/llvm/docs/LangRef.md
index 192597944101b..e1b351cc39928 100644
--- a/llvm/docs/LangRef.md
+++ b/llvm/docs/LangRef.md
@@ -4062,12 +4062,17 @@ Given that definition, R{sub}`byte` is defined as follows:
- Otherwise, if R{sub}`byte` may see exactly one write,
R{sub}`byte` returns the value written by that write.
- Otherwise, if R is atomic, and all the writes R{sub}`byte` may
- see are atomic, it chooses one of the values written. See the {ref}`Atomic
+ see are atomic, and R and the writes all access the exact same set of
+ bytes, it chooses one of the values written. See the {ref}`Atomic
Memory Ordering Constraints <ordering>` section for additional
constraints on how the choice is made. Targets may impose additional
requirements on R and the writes it may see based on their `syncscope`.
- Otherwise R{sub}`byte` returns `undef`.
+Atomic accesses cannot tear: If a byte subaccess R{sub}`byte1` of an
+atomic read R reads from an atomic write W, then all other byte
+subaccesses R{sub}`byte2` of R that may see W must also read from W.
+
R returns the value composed of the series of bytes it read. This
implies that some bytes within the value may be `undef` **without**
the entire value being `undef`. Note that this only defines the
@@ -4100,6 +4105,12 @@ address. See that instruction's documentation for details.
For a simpler introduction to the ordering constraints, see the
{doc}`Atomics`.
+For the following, we call two or more accesses *perfectly overlapping*
+if they all access the exact same set of bytes, i.e., they access the
+same address and have the same access size. By the constraints of the
+previous section, overlapping atomic accesses that are not ordered by
+happens-before must be perfectly overlapping to act atomically.
+
`unordered`
: The set of values that can be read is governed by the happens-before
partial order. A value cannot be read unless some operation wrote
@@ -4109,22 +4120,24 @@ For a simpler introduction to the ordering constraints, see the
to make them atomic in any interesting way.
`monotonic`
-: In addition to the guarantees of `unordered`, there is a single
- total order for modifications by `monotonic` operations on each
- address. All modification orders must be compatible with the
+: In addition to the guarantees of `unordered`, there is a total order
+ of modifications for each set of perfectly overlapping `monotonic`
+ operations.
+ All modification orders must be compatible with the
happens-before order. There is no guarantee that the modification
orders can be combined to a global total order for the whole program
(and this often will not be possible). If the read in an atomic
read-modify-write operation M ({ref}`cmpxchg <i_cmpxchg>` and
- {ref}`atomicrmw <i_atomicrmw>`) reads from a `monotonic` (or
- stronger) write W, W must be immediately before M in the address's
- modification order. If one atomic read happens before another atomic
- read of the same address and both are at least `monotonic`, the
- later read must not see an earlier value in the address's
- modification order. This disallows reordering of `monotonic` (or
- stronger) operations on the same address. If an address is written
- `monotonic`-ally by one thread, and other threads `monotonic`-ally
- read that address repeatedly, the other threads must eventually see
+ {ref}`atomicrmw <i_atomicrmw>`) reads from a perfectly overlapping
+ `monotonic` (or stronger) write W, W must be immediately before M in
+ the relevant modification order. If one atomic read happens before
+ another perfectly overlapping atomic read and both are at least
+ `monotonic`, the later read must not see an earlier value in the
+ address's modification order. This disallows reordering of perfectly
+ overlapping `monotonic` (or stronger) operations. If an address is
+ written `monotonic`-ally by one thread, and other threads
+ `monotonic`-ally read that address repeatedly with perfectly
+ overlapping accesses, the other threads must eventually see
the write. This corresponds to the C/C++ `memory_order_relaxed`.
`acquire`
@@ -4134,10 +4147,11 @@ For a simpler introduction to the ordering constraints, see the
`release`
: In addition to the guarantees of `monotonic`, if this operation
- writes a value which is subsequently read by an `acquire`
- operation, it *synchronizes-with* that operation. Furthermore,
- this occurs even if the value written by a `release` operation
- has been modified by a read-modify-write operation before being
+ writes a value which is subsequently read by a perfectly overlapping
+ `acquire` operation, it *synchronizes-with* that operation.
+ Furthermore, this occurs even if the value written by a `release`
+ operation has been modified by a perfectly overlapping
+ read-modify-write operation before being
read. (Such a set of operations comprises a *release
sequence*). This corresponds to the C/C++
`memory_order_release`.
@@ -4151,7 +4165,8 @@ For a simpler introduction to the ordering constraints, see the
operation that only reads, `release` for an operation that only
writes), there is a global total order on all
sequentially-consistent operations on all addresses. If an address
- is only accessed through sequentially-consistent operations, each
+ is only accessed through perfectly overlapping
+ sequentially-consistent operations, each
sequentially-consistent read of that address sees the last preceding
write to the same address in this global order. This corresponds to
the C/C++ `memory_order_seq_cst` and Java `volatile`.
@@ -11962,7 +11977,8 @@ defines what *synchronizes-with* edges they add. They can only be given
A fence A which has (at least) `release` ordering semantics
*synchronizes with* a fence B with (at least) `acquire` ordering
semantics if and only if there exist atomic operations X and Y, both
-operating on some atomic object M, such that A is sequenced before X, X
+operating on some atomic object M with the same address and access size,
+such that A is sequenced before X, X
modifies M (either directly or through some side effect of a sequence
headed by X), Y is sequenced before B, and Y observes M. This provides a
*happens-before* dependency between A and B. Rather than an explicit
More information about the llvm-commits
mailing list