Skip to content

8356255: Add Stable Field Updaters to allow efficient lazy field evaluations #25040

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

Draft
wants to merge 27 commits into
base: master
Choose a base branch
from

Conversation

minborg
Copy link
Contributor

@minborg minborg commented May 5, 2025

This sketch shows how "Stable Updaters" can be used to create stable computations of @Stable fields. Only one updater is needed per class, similar to AtomicIntegerFieldUpdater.


Progress

  • Change must be properly reviewed (1 review required, with at least 1 Reviewer)
  • Change must not contain extraneous whitespace
  • Commit message must refer to an issue

Issue

  • JDK-8356255: Add Stable Field Updaters to allow efficient lazy field evaluations (Enhancement - P3)

Reviewing

Using git

Checkout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/25040/head:pull/25040
$ git checkout pull/25040

Update a local copy of the PR:
$ git checkout pull/25040
$ git pull https://git.openjdk.org/jdk.git pull/25040/head

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 25040

View PR using the GUI difftool:
$ git pr show -t 25040

Using diff file

Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/25040.diff

Using Webrev

Link to Webrev Comment

@bridgekeeper
Copy link

bridgekeeper bot commented May 5, 2025

👋 Welcome back pminborg! A progress list of the required criteria for merging this PR into master will be added to the body of your pull request. There are additional pull request commands available for use with this pull request.

@openjdk
Copy link

openjdk bot commented May 5, 2025

❗ This change is not yet ready to be integrated.
See the Progress checklist in the description for automated requirements.

@openjdk
Copy link

openjdk bot commented May 5, 2025

@minborg The following labels will be automatically applied to this pull request:

  • core-libs
  • net

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.

@minborg
Copy link
Contributor Author

minborg commented May 5, 2025

Benchmarks:

Benchmark                        Mode  Cnt  Score   Error  Units
StableUpdatersBenchmark.base     avgt   10  0.715 ± 0.026  ns/op
StableUpdatersBenchmark.updater  avgt   10  0.716 ± 0.079  ns/op

Which shows the updater has the same performance as imperative code which is good.

@minborg
Copy link
Contributor Author

minborg commented May 6, 2025

Updated benchmarks:

Benchmark                              Mode  Cnt  Score   Error  Units
StableUpdatersBenchmark.base           avgt   10  0.838 ± 0.041  ns/op
StableUpdatersBenchmark.baseStatic     avgt   10  0.759 ± 0.088  ns/op
StableUpdatersBenchmark.updater        avgt   10  0.888 ± 0.086  ns/op
StableUpdatersBenchmark.updaterStatic  avgt   10  0.753 ± 0.017  ns/op
StableUpdatersBenchmark.uri            avgt   10  0.781 ± 0.206  ns/op
StableUpdatersBenchmark.uriStatic      avgt   10  0.724 ± 0.048  ns/op

@minborg minborg changed the title Sketch: StableUpdaters 8356255: Add Stable Field Updaters to allow efficient lazy field evaluations May 6, 2025
@minborg minborg marked this pull request as ready for review May 6, 2025 11:42
@openjdk openjdk bot added the rfr Pull request is ready for review label May 6, 2025
@mlbridge
Copy link

mlbridge bot commented May 6, 2025

Copy link
Contributor

@david-beaumont david-beaumont left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Starting to look nicely idiomatic. If I saw this I would however want to add a thin wrapper specific for hash codes where the "zero replacement" and generic types are more hidden.

  • HashCoder.forIntField(Foo.class, "hash", ...)
  • HashCoder.forLongField(...)
    I like that there's an annotation on the field now though!

@liach
Copy link
Member

liach commented May 7, 2025

The MH+VH form should be much more lightweight: MH and VH are just MemberName references.

I wonder if we can advertise the MH+VH version as an indy BSM:

public static CallSite lazyAccessor(Lookup lookup, String unused, MethodType callType, VarHandle field, MethodHandle computer)

The CallSite would be constant, with StableIntFieldUpdaterVarHandle.applyAsInt bound to the specific updater. This way, we can even defer the creation of the updater.

@openjdk openjdk bot removed the rfr Pull request is ready for review label May 7, 2025
@openjdk openjdk bot added the rfr Pull request is ready for review label May 7, 2025
Copy link

@ExE-Boss ExE-Boss left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

StableFieldUpdater​.checkAndAdapt(…) also performs an implicit null‑check:

private final Baz baz;

private static final ToIntFunction<LazyFoo> HASH_UPDATER =
StableFieldUpdater.ofInt(LazyFoo.class, "hash",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very clean now.

@minborg minborg marked this pull request as draft May 13, 2025 13:37
@openjdk openjdk bot removed the rfr Pull request is ready for review label May 13, 2025
Comment on lines +344 to +347
public static CallSite lazyAtMostOnce(MethodHandles.Lookup lookup,
String unused,
VarHandle accessor,
MethodHandle underlying) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not a valid bootstrap method signature, as that needs a MethodType for an invokedynamic or Class<?> for a dynamic constant:

Suggested change
public static CallSite lazyAtMostOnce(MethodHandles.Lookup lookup,
String unused,
VarHandle accessor,
MethodHandle underlying) {
public static CallSite lazyAtMostOnce(MethodHandles.Lookup lookup,
String unused,
MethodType type,
VarHandle accessor,
MethodHandle underlying) {

Comment on lines +203 to +207
* {@return a function that lazily sets the field accessible via the provided
* {@code accessor} by invoking the provided {@code underlying} function
* if the field has its default value (e.g. zero). Otherwise, the
* returned function returns the set field value}
* <p>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The inline form of @return is not indented elsewhere in the JDK:

Suggested change
* {@return a function that lazily sets the field accessible via the provided
* {@code accessor} by invoking the provided {@code underlying} function
* if the field has its default value (e.g. zero). Otherwise, the
* returned function returns the set field value}
* <p>
* {@return a function that lazily sets the field accessible via the provided
* {@code accessor} by invoking the provided {@code underlying} function
* if the field has its default value (e.g. zero). Otherwise, the returned
* function returns the set field value}

Comment on lines +216 to +217
* @throws IllegalArgumentException if the provided {@code underlying} function does
* take exactly one parameter of a reference type.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @throws IllegalArgumentException if the provided {@code underlying} function does
* take exactly one parameter of a reference type.
* @throws IllegalArgumentException if the provided {@code underlying} function does
* not take the same parameter types as the
* {@code accessor} var handle takes coordinate types.

Comment on lines +52 to +56
final List<Class<?>> shapeCoordinateTypes = new ArrayList<>(varHandle.coordinateTypes().size());
for (Class<?> coordinate: varHandle.coordinateTypes()) {
shapeCoordinateTypes.add(toObjectIfReference(coordinate));
}
return new Shape(toObjectIfReference(varHandle.varType()), shapeCoordinateTypes);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should construct an unmodifiable list:

Suggested change
final List<Class<?>> shapeCoordinateTypes = new ArrayList<>(varHandle.coordinateTypes().size());
for (Class<?> coordinate: varHandle.coordinateTypes()) {
shapeCoordinateTypes.add(toObjectIfReference(coordinate));
}
return new Shape(toObjectIfReference(varHandle.varType()), shapeCoordinateTypes);
final var coordinateTypes = varHandle.coordinateTypes();
final var shapeCoordinateTypes = new Object[coordinateTypes.size()];
for (int i = 0; i < shapeCoordinateTypes.length; i++) {
shapeCoordinateTypes[i] = toObjectIfReference(coordinateTypes.get(i));
}
return new Shape(toObjectIfReference(varHandle.varType()),
SharedSecrets.getJavaUtilCollectionAccess().listFromTrustedArray(shapeCoordinateTypes));

Comment on lines +238 to +243
// Allow `invokeExact()` of the `apply(Object)` method
final MethodHandle adaptedUnderlying = underlyingType.parameterType(0).equals(Object.class)
|| underlyingType.parameterType(0).isArray()
? underlying
: underlying.asType(underlyingType.changeParameterType(0, Object.class));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This procedure is repeated in StableFieldUpdaterGenerator​::handle(…):

Suggested change
// Allow `invokeExact()` of the `apply(Object)` method
final MethodHandle adaptedUnderlying = underlyingType.parameterType(0).equals(Object.class)
|| underlyingType.parameterType(0).isArray()
? underlying
: underlying.asType(underlyingType.changeParameterType(0, Object.class));

v = (Object) accessor.getAcquire(t);
if (v == null) {
try {
v = (int) underlying.invokeExact(t);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can’t invokeExact here, as the return type of underlying isn’t guaranteed to be Object:

Suggested change
v = (int) underlying.invokeExact(t);
v = (Object) underlying.invoke(t);

Comment on lines +350 to +352
var handle = MhUtil.findStatic(LOCAL_LOOKUP,
"atMostOnce", MethodType.methodType(MethodHandle.class, VarHandle.class, MethodHandle.class));
return new ConstantCallSite(MethodHandles.insertArguments(handle, 0, accessor, underlying));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes no sense as a bootstrap for a dynamic call site, as that’ll simply be equivalent to a call to atMostOnce.

I presume thou meant something like the following, which would actually create the stable field updater for use at the call site:

Suggested change
var handle = MhUtil.findStatic(LOCAL_LOOKUP,
"atMostOnce", MethodType.methodType(MethodHandle.class, VarHandle.class, MethodHandle.class));
return new ConstantCallSite(MethodHandles.insertArguments(handle, 0, accessor, underlying));
return new ConstantCallSite(atMostOnce(accessor, underlying).asType(type));

or by using the createTargetHook of ConstantCallSite:

Suggested change
var handle = MhUtil.findStatic(LOCAL_LOOKUP,
"atMostOnce", MethodType.methodType(MethodHandle.class, VarHandle.class, MethodHandle.class));
return new ConstantCallSite(MethodHandles.insertArguments(handle, 0, accessor, underlying));
var handle = MhUtil.findStatic(LOCAL_LOOKUP,
"atMostOnce", MethodType.methodType(MethodHandle.class, VarHandle.class, MethodHandle.class));
return new ConstantCallSite(type, MethodHandles.dropArguments(
MethodHandles.insertArguments(handle, 0, accessor, underlying),
0, ConstantCallSite.class));

@bridgekeeper
Copy link

bridgekeeper bot commented Jul 9, 2025

@minborg This pull request has been inactive for more than 8 weeks and will be automatically closed if another 8 weeks passes without any activity. To avoid this, simply issue a /touch or /keepalive command to the pull request. Feel free to ask for assistance if you need help with progressing this pull request towards integration!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging this pull request may close these issues.

8 participants