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

CODETOOLS-7903018: jcstress: Add more Read-Modify-Write examples #92

Merged
merged 5 commits into from
Sep 6, 2021
Merged
Changes from 2 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
Original file line number Diff line number Diff line change
@@ -46,9 +46,7 @@ public class RMW_02_ContendedSuccess {
/*
----------------------------------------------------------------------------------------------------------

This test explores the behaviors of atomic RMW instructions.

A more advanced property of CASes:
This tests explores a more advanced property of CASes:
- for strong CASes, exactly one operation should succeed
- for weak CASes, at most one operation should succeed (accept spurious failures)

Original file line number Diff line number Diff line change
@@ -49,11 +49,12 @@ public class RMW_04_AcquireOnSuccess {
/*
----------------------------------------------------------------------------------------------------------

This test explores the behaviors of atomic RMW instructions.
This test shows that CAS provides "acquire" semantics on success. This is similar
to other tests, for example BasicJMM_06_Causality: once we observe something
"release"-d by another thread, using any primitive with "acquire" semantics,
we are guaranteed to see things that happened before that release.

This shows that CAS provides "acquire" semantics on success.

x86_64, AArch64:
Indeed, on both x86_64 and AArch64 this would happen:
RESULT SAMPLES FREQ EXPECT DESCRIPTION
0, 0 138,542,022 42.99% Acceptable Trivial
0, 1 3,232,097 1.00% Acceptable Trivial
Original file line number Diff line number Diff line change
@@ -50,13 +50,13 @@ public class RMW_05_ReleaseOnSuccess {
/*
----------------------------------------------------------------------------------------------------------

This test explores the behaviors of atomic RMW instructions.
This test shows that CAS provides "release" semantics on success. This is similar
to other tests, for example BasicJMM_06_Causality: once we observe something
"release"-d by another thread using any primitive with "release" semantics,
by using any primitive with "acquire" semantics, we are guaranteed to see
things that happened before that release.

This shows the important caveat about the notion of conflict. Even if there is an intervening
write to the same variable _that keeps the value the same_, the CAS is still guaranteed
to succeed.

x86_64, AArch64:
Indeed, on both x86_64 and AArch64:
RESULT SAMPLES FREQ EXPECT DESCRIPTION
0, 0 138,542,022 42.99% Acceptable Trivial
0, 1 3,232,097 1.00% Acceptable Trivial
Original file line number Diff line number Diff line change
@@ -33,8 +33,7 @@
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;

import static org.openjdk.jcstress.annotations.Expect.ACCEPTABLE;
import static org.openjdk.jcstress.annotations.Expect.FORBIDDEN;
import static org.openjdk.jcstress.annotations.Expect.*;

@JCStressTest
@Outcome(id = {"0, 0", "1, 1", "0, 1"}, expect = ACCEPTABLE, desc = "Trivial")
@@ -50,7 +49,8 @@ public class RMW_06_AcquireOnFailure {
/*
----------------------------------------------------------------------------------------------------------

This test explores the behaviors of atomic RMW instructions.
This test shows that even a failing CAS provides the "acquire" semantics:
it still observes the value regardless of the subsequent CAS result.

x86_64, AArch64:
RESULT SAMPLES FREQ EXPECT DESCRIPTION
@@ -79,7 +79,9 @@ public void actor1(II_Result r) {

@Actor
public void actor2(II_Result r) {
r.r1 = VH.compareAndSet(this, 0, 1) ? 0 : 1; // fails if (g == 1)
// This CAS fails when it observes "1".
// Ternary operator converts that failure to "1" explicitly.
r.r1 = VH.compareAndSet(this, 0, 1) ? 0 : 1;
r.r2 = x;
}

Original file line number Diff line number Diff line change
@@ -50,7 +50,9 @@ public class RMW_07_ReleaseOnFailure {
/*
----------------------------------------------------------------------------------------------------------

This test explores the behaviors of atomic RMW instructions.
This test naively tries to show that a failing CAS does not provide "release" semantics.
But there are no observable results, because failing CAS does not write anything.
As far as reader side is concerned, no writes of "g" had been published.

x86_64, AArch64:
RESULT SAMPLES FREQ EXPECT DESCRIPTION
@@ -74,7 +76,8 @@ public class RMW_07_ReleaseOnFailure {
@Actor
public void actor1(II_Result r) {
x = 1;
VH.compareAndSet(this, 1, 0); // always fails
// This CAS always fails: no release semantics.
VH.compareAndSet(this, 1, 0);
}

@Actor
Original file line number Diff line number Diff line change
@@ -39,21 +39,24 @@
import static org.openjdk.jcstress.annotations.Expect.*;


public class RMW_08_AtomicityEffects {
public class RMW_08_GAS_Effects {

/*
How to run this test:
$ java -jar jcstress-samples/target/jcstress.jar -t RMW_08_AtomicBound[.SubTestName]
$ java -jar jcstress-samples/target/jcstress.jar -t RMW_08_AtomicityEffects[.SubTestName]
*/

/*
----------------------------------------------------------------------------------------------------------

This test explores the behaviors of atomic RMW instructions. Since failing RMW operations do not
produce observable writes, the tests are complicated, and have to test the memory semantics
in a round-about way, gradually building up the test case.
This test construct a rather complicated example when the failing CAS semantics
matters a bit, and why a stronger primitives might be needed. Since failing RMW
operations do not produce observable writes, the tests are complicated, and have
to test the memory semantics in a round-about way.

First, a very basic test. This test produces (0, 0), and the justifying execution is:
We shall build up the test case gradually. First, a very basic test.

This test produces (0, 0), and the justifying execution is:

w(x, 1) --po/hb--> r(y):0
|
@@ -113,9 +116,10 @@ public void actor2(II_Result r) {

...where CAS actions are "indivisible" in "[ ]".

The fact these are atomic CASes changes nothing (yet).
The fact these are atomic CASes changes nothing (yet): there is no store,
and therefore no memory semantics can be assumed.

AArch64:
Indeed, this still happens on AArch64:
RESULT SAMPLES FREQ EXPECT DESCRIPTION
0, 0 868 <0.01% Interesting Interesting
0, 1 13,122,195 63.29% Acceptable Trivial
@@ -157,9 +161,8 @@ public void actor2(II_Result r) {
/*
----------------------------------------------------------------------------------------------------------



This test produces (0, 0), and the justifying execution is:
Doing the store to provide the release on one side still produces (0, 0),
and the justifying execution is:

w(x,1) --po/hb--> r(y):0 --po/hb--> w(y,0)
| ^
@@ -173,7 +176,7 @@ changes nothing (yet).
Note that the order over "y" is still linearizable, as required for synchronization
actions: r(y):0 --> r(y):0 --> w(y,1) --> w(y, 0).

AArch64:
Indeed, this is still possible on AArch64:
RESULT SAMPLES FREQ EXPECT DESCRIPTION
0, 0 2,087 <0.01% Interesting Interesting
0, 1 11,400,418 52.20% Acceptable Trivial
@@ -217,8 +220,11 @@ public void actor2(II_Result r) {
/*
----------------------------------------------------------------------------------------------------------

This test cannot produce (0, 0), because (drum roll, please). We basically need to
fill in the blanks in between the actions in GAS and CAS:
Now to the final test. This test cannot produce (0, 0), because it uses a much stronger
primitive: Get-And-Set (GAS).

To reason whether we can produce (0, 0), we basically need to fill in the blanks
in between the actions in GAS and CAS:

w(x,1) --po/hb--> [ r(y):0 ; w(y,0) ]

@@ -248,7 +254,6 @@ This execution is invalid, because r(y):0 should have observed w(y,1), which
|
[ r(y):0 ; w(y,1) ] --po/hb--> r(x):0


This execution is invalid, because r(y) observes w(y), which means there
is a synchronizes-with between them, which hooks w(x) and r(x), which
fails happens-before consistency: r(x) should see 1.
@@ -262,13 +267,13 @@ This execution is invalid, because r(y) observes w(y), which means there
In the end, there is no execution that justifies (0, 0).

Note that it is an effect of all three:
- GAS being atomic;
- GAS carrying "volatile" semantics;
- GAS performing the unconditional store that links the sw;
- GAS is being atomic;
- GAS is carrying "release" semantics;
- GAS is performing the unconditional store is detectable by CAS;

Previous examples show how failing any of these prerequisites exposes (0, 0).

AArch64:
Indeed, this does not happen on AArch64 anymore:
RESULT SAMPLES FREQ EXPECT DESCRIPTION
0, 0 0 0.00% Forbidden Nope
0, 1 9,899,632 53.12% Acceptable Trivial