c++c++20stdatomicmemory-model

How is modification order of a variable defined in C++?


I've read this Q&A: What is the significance of 'strongly happens before' compared to '(simply) happens before'?

The author gives an outline of an interesting evaluation that was not possible until C++20 but apparently is possible starting C++20:

.-- T3 y.store(3, seq_cst);                   --.                 (2)
|        |                                      | strongly
|        | sequenced before                     | happens
|        V                                      | before
|   T3 a = x.load(seq_cst); // a = 0    --.   <-'                 (3)
|                                         : coherence-
|                                         : ordered
|                                         : before
|   T1 x.store(1, seq_cst);             <-'   --. --.             (4)
|        |                                      |st |
|        | sequenced before                     |h  |
|        V                                      |b  |
| . T1 y.store(1, release);                   <-'   |             (x)
| |      :                                          | strongly
| |      : synchronizes with                        | happens
| |      V                                          | before
| > T2 b = y.fetch_add(1, seq_cst); // b = 1  --.   |             (1)
|        |                                      |st |
|        | sequenced before                     |h  |
|        V                                      |b  |
'-> T2 c = y.load(relaxed); // c = 3          <-' <-'

The numbers on the right denote a possible seq_cst order (and I added (x) for convenience; this line does not participate in SC order since it's not an SC operation).

I was trying to understand what is the modification order of y in this example, but I don't know how to determine it. (Or are there multiple possible modification orders of y for this evaluation?..)

More generally, how is modification order of an atomic variable defined in C++? For example there's this: https://en.cppreference.com/w/cpp/atomic/memory_order

Write-write coherence: If evaluation A that modifies some atomic M (a write) happens-before evaluation B that modifies M, then A appears earlier than B in the modification order of M

So it seems that modification order has to be consistent with a write-write happens-before one.

Is it the only thing that defines modification order?

In the above example, AFAIU there's no happens-before between (2) and (1); so which one is first in the modification order of y?

Is the mod order of y (x 1 2) (for this evaluation)?

I believe it might also help reasoning about seq_cst order...


Solution

  • The modification order for an object is the order a thread would see if it was spinning in a tight loop running while(1) { y.load(relaxed); }, and happened to see every every change. (Not that that's a useful way to actually observe it, but every object has its own modification order that all threads can always agree on, like on real CPUs thanks to MESI exclusive ownership being required to commit a store to L1d cache. Or see it early via private store-forwarding between SMT threads on POWER CPUs.)

    Some random facts:


    I think this evaluation is showing actual effective order, so the mod order for y is just reading top to bottom, the values stored by operations (2) (x) (1) in that order. (Because it's using enough seq_cst operations that all threads can agree on the order, and showing that some other things end up ordering the release store (x) after the seq_cst store (2).)

    This eval order is saying that the (2) store did become visible before the (x) store, so the (x) store replaced it.

    And the dust has settled on that before the y.fetch_add (1), otherwise it would have synced-with (2) instead of (x).

    Correction, apparently they're showing that a modification order of (x) (1) (2) is legal, despite the chain of different flavours of happens-before between (2) (storing 3) and the final y.load(). So that store becomes visible to T2 after the fetch_add, before the load.