-
Notifications
You must be signed in to change notification settings - Fork 6.2k
8333791: Fix memory barriers for @Stable fields #19635
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
8333791: Fix memory barriers for @Stable fields #19635
Conversation
|
👋 Welcome back shade! A progress list of the required criteria for merging this PR into |
|
@shipilev This change now passes all automated pre-integration checks. ℹ️ This project also has non-automated pre-integration requirements. Please see the file CONTRIBUTING.md for details. After integration, the commit message for the final commit will be: You can use pull request commands such as /summary, /contributor and /issue to adjust it as needed. At the time when this comment was updated there had been 10 new commits pushed to the
Please see this link for an up-to-date comparison between the source branch of this pull request and the ➡️ To integrate this PR with the above commit message to the |
|
I think we should remove https://bugs.openjdk.org/browse/JDK-8333791 Stable fields are in some ways “better finals”, in that they can be used to store lazy but effectively final states. But part of the “better” is that (correctly used) their race conditions are safe. Since racing is part of their nature, the fences are an unnecessary expense. So just removing that code would be the best outcome, unless I am missing something. We will want to run such a change through heavy testing. |
ce52047 to
128b986
Compare
506550e to
cca85b6
Compare
cca85b6 to
3892d37
Compare
|
Can the barrier issue be bypassed with this pattern: private @Stable Value field;
Value getter() {
var local = field; // avoid double read
if (local == null)
local = computeAndSet(); // avoid double read, no fence here in getter
return local;
}
private Value computeAndSet() {
var result = ... // compute value
field = result; // write must be here, or barrier will be in getter
return result; // to avoid double read
}And since you are still inserting barriers the same way constructor barriers are inserted, can I say that such a more usual pattern: private @Stable Value field;
Value getter() {
var local = field; // avoid double read
if (local == null)
local = field = ... // inserts StoreStore after
return local;
}will still suffer from the regression observed in #19433 (comment), or is that completely fixed? |
|
Do we still need separate _wrote_stable and _wrote_final flags, or could we combine them into _wrote_stable_or_final? |
|
If we merge the stable and final flags, won't this: Value getter() {
var local = field;
if (local == null)
local = field = ... // makes the getter final-writing
return local;
}be regarded the same as any final-writing constructor? Then every call to |
|
@liach, if I understand this PR correctly, it only adds barriers for final/stable fields in constructors. Previous code to emit barriers for stable fields outside of constructors is removed. |
|
Ah, so it's like a weaker version of always safe construction. Makes sense. |
|
I like this compromise. Let me see if I got it right: A stable write in a constructor is treated like a final write — it triggers a barrier at the end of the constructor. That’s a cheap move. No other barriers are added automatically, for reads or other writes, saving us from doing less cheap moves. The burden would be on users of stable vars (in fancy access patterns) to add more fences if needed, but we don’t see any important cases of that, at the moment. |
Nope. With this patch, we only care about stable field barriers in constructors. Methods are not affected. There are new IR tests that verify this directly. |
One of my previous iterations did this combination, but I thought it was: a) uglier; b) not future-proof, in case someone (probably me, later) would like to check the parser state for final fields writes specifically. So I thought to track final and stable field writes separately. |
Yes, pretty much. Looking at this performance/safety model another way, there is now no performance cost or safety risk for simple changes in user code like:
Users are still responsible for proper fencing if value is overwritten outside of constructor, and the data races on the field are not benign. For example when |
|
Still waiting for formal reviews, thanks! |
|
/reviewers 2 reviewer |
|
IIUC this means we can remove the explicit fence here: ? |
That said, I think we can change this to a StoreStore or Release:
|
I think so, but there is more to it: there are other fences around the |
Agreed, just wanted to test my understanding. |
|
Still waiting for formal reviews, please :) |
|
...and still looking for formal reviews, pretty please :) |
iwanowww
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good.
liach
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This updated patch to the new test framework looks clean.
I think current @Stable is a few distinct usages bundled together:
- lazy variables or arrays - addressed by
StableValuejep - frozen arrays - there's an inactive frozen array proposal
- constant folding outside of trusted packages - addressed by strict final fields (nullable types jep)
I hope we can gradually roll out the 3 features to benefit all java users.
|
Thanks! Last call for comments. If there are no other comments, I am going to integrate this with the next 24 hours. |
|
Actually, maybe anyone wants to run it through their testing pipelines as well? @iwanowww, @chhagedorn, @TobiHartmann? |
|
Already done, sorry forgot to report. Ran t1-4,hs_precheckin_comp,hs_comp_stress - looked good! |
|
Awesome, thanks! Here it goes, then. /integrate |
|
Going to push as commit 74fdd68.
Your commit was automatically rebased without conflicts. |
| if (field->is_final()) { | ||
| scope()->set_wrote_final(); | ||
| } | ||
| if (field->is_stable()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if the field is a field of another object, not the one in construction? For final fields the verifier ensures that we only write to it in the containing object constructor, but for final fields it is not guaranteed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't membar additions already checked with is_object_initializer() or method()->name() == ciSymbols::object_initializer_name()?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes we only emit membars at the end of constructors but we do not check if the stable fields being written are of the same objects as the ones being constructed there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That should be still fine, as this shouldn't affect performance-sensitive fast paths.
See bug for more discussion.
Currently, C2 puts a
Releasebarrier at exit of every method that writes a@Stablefield. This is a problem for high-performance code that initializes the stable field like this:jdk/src/java.base/share/classes/java/lang/Enum.java
Lines 182 to 193 in 79a2301
A more egregious example is here, which means that every
Stringconstructor actually doesReleasebarrier for@Stablefield write, while only aStoreStoreforfinalfield store would suffice:jdk/src/java.base/share/classes/java/lang/String.java
Lines 159 to 160 in 79a2301
AFAICS, the original intent for Release barrier in constructor for stable fields was to match the memory semantics of final fields better.
@Stableare in some sense "super-finals": they are foldable like static finals or non-static trusted finals, but can be written anywhere. The@Stablemachinery is intrinsically safe under races: either a compiler sees a component of stable subgraph in initialized state and folds it, or it sees a default value for the component and leaves it alone.I performed an audit of current
@Stableuses for fields that are not currentlyfinalorvolatile, and there are cases where we write into@Stablefields in constructors. AFAICS, they are covered by final-field-like semantics by accident of having adjacentfinalfields.Current PR implements Variant 2 from the discussion: makes sure stable fields are as memory-safe as finals, and that's it. I believe this is all-around a good compromise for both mainline and the backports: the performance is improved in one the path that matter, and we still have some safety margin in face of accidental removals of adjacent
final-s, or in case I missed some spots during the audit.C1 did not do anything special for
@Stablefields at all, fixed those to match C2. Both Zero and template interpreters for non-TSO arches put barriers at everyreturn(with notable exception of ARM32), which handles everything in an overkill manner.Additional testing:
allallProgress
Issue
Reviewers
Reviewing
Using
gitCheckout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/19635/head:pull/19635$ git checkout pull/19635Update a local copy of the PR:
$ git checkout pull/19635$ git pull https://git.openjdk.org/jdk.git pull/19635/headUsing Skara CLI tools
Checkout this PR locally:
$ git pr checkout 19635View PR using the GUI difftool:
$ git pr show -t 19635Using diff file
Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/19635.diff
Webrev
Link to Webrev Comment