-
Notifications
You must be signed in to change notification settings - Fork 6.3k
8366178: Implement JEP 526: Lazy Constants (Second Preview) #27605
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
base: master
Are you sure you want to change the base?
Conversation
👋 Welcome back pminborg! A progress list of the required criteria for merging this PR into |
❗ This change is not yet ready to be integrated. |
@minborg The following labels will be automatically applied to this pull request:
When this pull request is ready to be reviewed, an "RFR" email will be sent to the corresponding mailing lists. If you would like to change these labels, use the /label pull request command. |
* lazy constant may block indefinitely; no timeouts or cancellations are provided. | ||
* | ||
* <h2 id="performance">Performance</h2> | ||
* As a lazy constant can never change after it has been initialized. Therefore, |
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.
* As a lazy constant can never change after it has been initialized. Therefore, | |
* A lazy constant can never change after it has been initialized. Therefore, |
super(); | ||
} | ||
|
||
@Override public boolean isEmpty() { return size == 0;} |
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.
@Override public boolean isEmpty() { return size == 0;} | |
@Override public boolean isEmpty() { return size == 0; } |
} | ||
|
||
private static final StableValue<Boolean> SHUTDOWN_WRITE_BEFORE_CLOSE = StableValue.of(); | ||
private static final LazyConstant<Boolean> SHUTDOWN_WRITE_BEFORE_CLOSE = LazyConstant.of(new Supplier<Boolean>() { |
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.
I wonder if there is a reason for not using BooleanSupplier
.
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.
Unfortunately, BooleanSupplier does not extend Supplier.
/** | ||
* If non-null, the value; if null, indicates no value is present | ||
*/ | ||
@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.
Shouldn’t this also add a null sentinel to allow the folding of the empty case? Or is that irrelevant because empty would be the terminator of a chain.
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.
Usually a null sentinel is used when null indicates something different. I think maybe to allow folding the empty case, we should probably add a new annotation or a new element-value to indicate this status and properly handle it in C1/C2 (similar to the field trusting in ciField)
// Factory | ||
|
||
public static <T> LazyConstantImpl<T> ofLazy(Supplier<? extends T> computingFunction) { | ||
return new LazyConstantImpl<>(computingFunction); |
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.
We might return the function if it is already a LazyConstantImpl.
I’m gonna miss Stable Values, as it has some use cases which aren’t served by Lazy Constants, and on which I depend on in some of my code, so I’m stuck with using regular non‑ Also, in the JEP 526 table under “Flexible initialization with lazy constants”:
The “Update location” of Similarly, concurrent updates are prevented for |
Getting access to the underlying StableVar.java/*
* Any copyright is dedicated to the Public Domain.
* https://creativecommons.org/publicdomain/zero/1.0/
*/
import java.util.NoSuchElementException;
import java.util.function.Supplier;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import static java.lang.System.identityHashCode;
import static java.util.Objects.requireNonNull;
/// Horrible awful hack to get access to raw stable values in JDK 26+.
@NullMarked
public sealed interface StableVar<T> permits StableHacks.StableVarImpl {
boolean trySet(final T contents) throws NullPointerException, IllegalStateException;
T orNull();
T orElse(final T other) throws NullPointerException;
T orElseThrow() throws NoSuchElementException;
boolean isSet();
T orElseSet(final Supplier<? extends T> supplier) throws NullPointerException, IllegalStateException;
void setOrThrow(final T contents) throws NullPointerException, IllegalStateException;
static <T> StableVar<T> of() {
return StableHacks.newInstance();
}
}
/// Encapsulates the actual implementation of `StableValue` on `LazyConstant`
///
/// @author ExE Boss
@NullMarked
/*package*/ final @Namespace class StableHacks {
private StableHacks() throws InstantiationException { throw new InstantiationException(StableHacks.class.getName()); }
private static final String UNSET_SUFFIX = ".unset";
private static final Object UNSET = new Object() {
@Override
public int hashCode() {
return 0;
}
@Override
public String toString() {
return "unset";
}
};
private static final ScopedValue<?> SCOPE = ScopedValue.newInstance();
private static final Supplier<?> SCOPE_GETTER = SCOPE::get;
/*package*/ static final <T> StableVarImpl<T> newInstance() {
return new StableValue<>();
}
/*package*/ sealed interface StableVarImpl<T> extends StableVar<T> {
}
private record StableValue<T>(
// Implemented as a record so that the JVM treats this as a trusted final field
// even when `-XX:+TrustFinalNonStaticFields` is not set
LazyConstant<T> contents
) implements StableVarImpl<T> {
@SuppressWarnings("unchecked")
private StableValue() {
this(LazyConstant.<T>of((Supplier) SCOPE_GETTER));
}
private StableValue {
if (contents.isInitialized()) throw new InternalError();
}
@SuppressWarnings("unchecked")
private final ScopedValue<T> scope() {
return (ScopedValue<T>) SCOPE;
}
private final void preventReentry() throws IllegalStateException {
if (Thread.holdsLock(this)) {
throw new IllegalStateException("Recursive initialization of a stable value is illegal");
}
}
@Override
public boolean trySet(final T contents) throws NullPointerException, IllegalStateException {
requireNonNull(contents);
if (this.contents.isInitialized()) return false;
preventReentry();
synchronized (this) {
return this.setImpl(contents);
}
}
@Override
@SuppressWarnings("unchecked")
public final @Nullable T orNull() {
return unwrapUnset(((LazyConstant) this.contents).orElse(UNSET));
}
@Override
public T orElse(T other) throws NullPointerException {
return this.contents.orElse(other);
}
@Override
public T orElseThrow() throws NoSuchElementException {
{ final T contents; if ((contents = this.orNull()) != null) {
return contents;
} }
throw new NoSuchElementException();
}
@Override
public boolean isSet() {
return this.contents.isInitialized();
}
@Override
public T orElseSet(final Supplier<? extends T> supplier) throws NullPointerException, IllegalStateException {
requireNonNull(supplier);
{ final T contents; if ((contents = this.orNull()) != null) {
return contents;
} }
return orElseSetSlowPath(supplier);
}
@Override
public void setOrThrow(final T contents) throws NullPointerException, IllegalStateException {
if (!trySet(contents)) {
throw new IllegalStateException();
}
}
private final T orElseSetSlowPath(
final Supplier<? extends T> supplier
) throws NullPointerException, IllegalStateException {
preventReentry();
synchronized (this) {
{ final T contents; if ((contents = this.orNull()) != null) {
return contents;
} }
final T newValue;
this.setImpl(newValue = requireNonNull(supplier.get()));
return newValue;
}
}
private final boolean setImpl(final T contents) {
assert Thread.holdsLock(this);
if (this.contents.isInitialized()) {
return false;
}
ScopedValue.where(this.scope(), contents).run(this.contents::get);
return true;
}
@Override
public final boolean equals(final Object obj) {
return this == obj;
}
@Override
public final int hashCode() {
return identityHashCode(this);
}
@Override
public String toString() {
final Object contents;
return renderValue(
"StableValue",
(contents = this.orNull()) != this
? contents
: "(this StableValue)"
);
}
}
@SuppressWarnings("unchecked")
private static final <T> T unwrapUnset(final Object obj) {
return (obj == UNSET) ? null : (T) obj;
}
private static final String renderValue(
final String type,
final @Nullable Object value
) throws NullPointerException {
return (value == null)
? type.concat(UNSET_SUFFIX)
: (type + '[' + value + ']');
}
} |
Hi @ExE-Boss, this new JEP describes how this functionality will be provided in the future:
This would be necessary, as there are usage patterns (such as nominal descriptors in ClassFile API) that would benefit from multiple assignment and a stable promoted read. |
It's essential to provide a low-level API as those in StableValue. Completely migrating to factory pattern may forcing users to build their own LazyConstant wrapper as ExeBoss has down above. |
Implement JEP 526: Lazy Constants (Second Preview)
The lazy list/map implementations are broken out from
ImmutableCollections
to a separate class.The old benchmarks are not moved/renamed to allow comparison with previous releases.
java.util.Optional
is updated so that its field is annotated with@Stable
. This is to allowOptional
instances to be held in lazy constants and still provide constant folding.Progress
Issues
Reviewing
Using
git
Checkout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/27605/head:pull/27605
$ git checkout pull/27605
Update a local copy of the PR:
$ git checkout pull/27605
$ git pull https://git.openjdk.org/jdk.git pull/27605/head
Using Skara CLI tools
Checkout this PR locally:
$ git pr checkout 27605
View PR using the GUI difftool:
$ git pr show -t 27605
Using diff file
Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/27605.diff