[llvm] [RFC][LangRef] Specify that the accessed bytes of concurrent atomics must be either disjoint or the same (PR #204329)

Fabian Ritter via llvm-commits llvm-commits at lists.llvm.org
Mon Jul 20 00:40:14 PDT 2026


https://github.com/ritter-x2a updated https://github.com/llvm/llvm-project/pull/204329

>From d8f7ce063cf04133f446e078e6f02126ac8dc79f Mon Sep 17 00:00:00 2001
From: Fabian Ritter <fabian.ritter at amd.com>
Date: Wed, 17 Jun 2026 05:59:01 -0400
Subject: [PATCH 1/5] [RFC][LangRef] Specify that the accessed bytes of
 concurrent atomics must be either disjoint or the same

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). 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.
---
 llvm/docs/LangRef.md | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/llvm/docs/LangRef.md b/llvm/docs/LangRef.md
index 9526cd020d724..3f793364df384 100644
--- a/llvm/docs/LangRef.md
+++ b/llvm/docs/LangRef.md
@@ -4062,12 +4062,16 @@ 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`.
 
+Defined atomic accesses cannot tear: An atomic read cannot read bytes
+from more than one atomic write.
+
 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

>From 29a08b541c739bb671757d9312658f9209ac0573 Mon Sep 17 00:00:00 2001
From: Fabian Ritter <fabian.ritter at amd.com>
Date: Thu, 18 Jun 2026 03:17:37 -0400
Subject: [PATCH 2/5] "un-simplify" the no-tearing constraint

---
 llvm/docs/LangRef.md | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/llvm/docs/LangRef.md b/llvm/docs/LangRef.md
index 3f793364df384..d58eb4e39da9f 100644
--- a/llvm/docs/LangRef.md
+++ b/llvm/docs/LangRef.md
@@ -4069,8 +4069,10 @@ Given that definition, R{sub}`byte` is defined as follows:
    requirements on R and the writes it may see based on their `syncscope`.
 -  Otherwise R{sub}`byte` returns `undef`.
 
-Defined atomic accesses cannot tear: An atomic read cannot read bytes
-from more than one atomic write.
+Defined atomic accesses cannot tear: Two byte subaccesses R{sub}`byte1`,
+R{sub}`byte2` of an atomic read R cannot read from different atomic
+writes W{sub}`1`, W{sub}`2` if both read subaccesses, R{sub}`byte1`,
+R{sub}`byte2`, may see both writes, W{sub}`1`, W{sub}`2`.
 
 R returns the value composed of the series of bytes it read. This
 implies that some bytes within the value may be `undef` **without**

>From dcb93429a67e45c971a0b1b21ac1636bb33bada4 Mon Sep 17 00:00:00 2001
From: Fabian Ritter <fabian.ritter at amd.com>
Date: Tue, 14 Jul 2026 03:42:08 -0400
Subject: [PATCH 3/5] Adapt Ralf's simpler no-tearing formulation.

---
 llvm/docs/LangRef.md | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/llvm/docs/LangRef.md b/llvm/docs/LangRef.md
index d58eb4e39da9f..f722cb72d2418 100644
--- a/llvm/docs/LangRef.md
+++ b/llvm/docs/LangRef.md
@@ -4069,10 +4069,9 @@ Given that definition, R{sub}`byte` is defined as follows:
    requirements on R and the writes it may see based on their `syncscope`.
 -  Otherwise R{sub}`byte` returns `undef`.
 
-Defined atomic accesses cannot tear: Two byte subaccesses R{sub}`byte1`,
-R{sub}`byte2` of an atomic read R cannot read from different atomic
-writes W{sub}`1`, W{sub}`2` if both read subaccesses, R{sub}`byte1`,
-R{sub}`byte2`, may see both writes, W{sub}`1`, W{sub}`2`.
+Defined 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**

>From 222d484e6820cb4ee665f6012aeb61c83108068c Mon Sep 17 00:00:00 2001
From: Fabian Ritter <fabian.ritter at amd.com>
Date: Wed, 15 Jul 2026 02:50:05 -0400
Subject: [PATCH 4/5] Remove vague use of "defined"

---
 llvm/docs/LangRef.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/llvm/docs/LangRef.md b/llvm/docs/LangRef.md
index f722cb72d2418..0089d4e47a3e6 100644
--- a/llvm/docs/LangRef.md
+++ b/llvm/docs/LangRef.md
@@ -4069,8 +4069,8 @@ Given that definition, R{sub}`byte` is defined as follows:
    requirements on R and the writes it may see based on their `syncscope`.
 -  Otherwise R{sub}`byte` returns `undef`.
 
-Defined 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
+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

>From 56e13dc6b06516cad33d4c0dd389b94b517ec0e5 Mon Sep 17 00:00:00 2001
From: Fabian Ritter <fabian.ritter at amd.com>
Date: Mon, 20 Jul 2026 03:36:54 -0400
Subject: [PATCH 5/5] Explicitly restrict the atomic ordering constraints to
 perfectly overlapping accesses.

---
 llvm/docs/LangRef.md | 47 +++++++++++++++++++++++++++-----------------
 1 file changed, 29 insertions(+), 18 deletions(-)

diff --git a/llvm/docs/LangRef.md b/llvm/docs/LangRef.md
index 0089d4e47a3e6..8dac361cc5176 100644
--- a/llvm/docs/LangRef.md
+++ b/llvm/docs/LangRef.md
@@ -4105,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, racing atomic accesses 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
@@ -4114,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`
@@ -4139,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`.
@@ -4156,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`.
@@ -11967,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