From 0e8a854688102a09c798e157e820047b061eb7af Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 5 May 2025 15:21:41 +0200 Subject: [PATCH 01/24] Initial commit --- .../classes/java/lang/reflect/Method.java | 23 +- src/java.base/share/classes/java/net/URI.java | 45 ++-- .../internal/lang/stable/StableUpdater.java | 211 ++++++++++++++++++ .../jdk/internal/lang/stable/StableUtil.java | 13 ++ .../internal/lang/stable/StableValueImpl.java | 12 +- .../lang/StableValue/StableUpdaterTest.java | 126 +++++++++++ .../java/lang/foreign/SegmentBulkHash.java | 2 +- .../lang/stable/StableUpdatersBenchmark.java | 122 ++++++++++ 8 files changed, 518 insertions(+), 36 deletions(-) create mode 100644 src/java.base/share/classes/jdk/internal/lang/stable/StableUpdater.java create mode 100644 test/jdk/java/lang/StableValue/StableUpdaterTest.java create mode 100644 test/micro/org/openjdk/bench/java/lang/stable/StableUpdatersBenchmark.java diff --git a/src/java.base/share/classes/java/lang/reflect/Method.java b/src/java.base/share/classes/java/lang/reflect/Method.java index 07616206075e7..6c017512f8459 100644 --- a/src/java.base/share/classes/java/lang/reflect/Method.java +++ b/src/java.base/share/classes/java/lang/reflect/Method.java @@ -26,6 +26,8 @@ package java.lang.reflect; import jdk.internal.access.SharedSecrets; +import jdk.internal.lang.stable.StableUpdater; +import jdk.internal.misc.Unsafe; import jdk.internal.misc.VM; import jdk.internal.reflect.CallerSensitive; import jdk.internal.reflect.CallerSensitiveAdapter; @@ -47,6 +49,7 @@ import java.lang.annotation.AnnotationFormatError; import java.nio.ByteBuffer; import java.util.StringJoiner; +import java.util.function.ToIntFunction; /** * A {@code Method} provides information about, and access to, a single method @@ -93,7 +96,19 @@ public final class Method extends Executable { private Method root; private transient volatile MethodRepository genericInfo; private @Stable MethodAccessor methodAccessor; + + private static final ToIntFunction HASH_UPDATER = + StableUpdater.ofIntRaw(Method.class, Unsafe.getUnsafe().objectFieldOffset(Method.class, "hash"), new ToIntFunction() { + @Override + public int applyAsInt(Method method) { + return method.getDeclaringClass().getName().hashCode() ^ method.getName() + .hashCode(); + } + }, 0); // The `hashCode()` is specified so, zeroReplacement is 0 // End shared states + + // Used reflectively via HASH_UPDATER + @Stable private int hash; // not shared right now, eligible if expensive // Generics infrastructure @@ -360,13 +375,7 @@ public boolean equals(Object obj) { * method's declaring class name and the method's name. */ public int hashCode() { - int hc = hash; - - if (hc == 0) { - hc = hash = getDeclaringClass().getName().hashCode() ^ getName() - .hashCode(); - } - return hc; + return HASH_UPDATER.applyAsInt(this); } /** diff --git a/src/java.base/share/classes/java/net/URI.java b/src/java.base/share/classes/java/net/URI.java index cd116f3877bcb..b5f9f69434933 100644 --- a/src/java.base/share/classes/java/net/URI.java +++ b/src/java.base/share/classes/java/net/URI.java @@ -40,8 +40,12 @@ import java.nio.charset.CharacterCodingException; import java.nio.file.Path; import java.text.Normalizer; +import java.util.function.ToIntFunction; + import jdk.internal.access.JavaNetUriAccess; import jdk.internal.access.SharedSecrets; +import jdk.internal.lang.stable.StableUpdater; +import jdk.internal.vm.annotation.ForceInline; import sun.nio.cs.UTF_8; /** @@ -544,6 +548,12 @@ public final class URI // The remaining fields may be computed on demand, which is safe even in // the face of multiple threads racing to initialize them private transient String schemeSpecificPart; + + private static final ToIntFunction HASH_UPDATER = StableUpdater.ofInt( + URI.class, "hash", new ToIntFunction<>() { + @ForceInline @Override public int applyAsInt(URI uri) { return uri.hashCode0(); }}, -1); + + // Used reflectively by HASH_UPDATER private transient int hash; // Zero ==> undefined private transient String decodedUserInfo; @@ -1575,25 +1585,24 @@ public boolean equals(Object ob) { * @return A hash-code value for this URI */ public int hashCode() { - int h = hash; - if (h == 0) { - h = hashIgnoringCase(0, scheme); - h = hash(h, fragment); - if (isOpaque()) { - h = hash(h, schemeSpecificPart); + return HASH_UPDATER.applyAsInt(this); + } + + private int hashCode0() { + int h = 0; + h = hashIgnoringCase(0, scheme); + h = hash(h, fragment); + if (isOpaque()) { + h = hash(h, schemeSpecificPart); + } else { + h = hash(h, path); + h = hash(h, query); + if (host != null) { + h = hash(h, userInfo); + h = hashIgnoringCase(h, host); + h += 1949 * port; } else { - h = hash(h, path); - h = hash(h, query); - if (host != null) { - h = hash(h, userInfo); - h = hashIgnoringCase(h, host); - h += 1949 * port; - } else { - h = hash(h, authority); - } - } - if (h != 0) { - hash = h; + h = hash(h, authority); } } return h; diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableUpdater.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableUpdater.java new file mode 100644 index 0000000000000..ac3f63adf09d6 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableUpdater.java @@ -0,0 +1,211 @@ +package jdk.internal.lang.stable; + +import jdk.internal.misc.Unsafe; +import jdk.internal.reflect.CallerSensitive; +import jdk.internal.reflect.Reflection; +import jdk.internal.util.Architecture; +import jdk.internal.vm.annotation.ForceInline; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.Objects; +import java.util.function.ToIntFunction; +import java.util.function.ToLongFunction; + +/** + * Stable field updaters. + *

+ * The provided {@code underlying} function must not recurse or the result of the + * operation is unspecified. + */ +public final class StableUpdater { + + private StableUpdater() {} + + private static final Unsafe UNSAFE = Unsafe.getUnsafe(); + + @CallerSensitive + public static ToIntFunction ofInt(Class holderType, + String fieldName, + ToIntFunction underlying, + int zeroReplacement) { + Objects.requireNonNull(holderType); + Objects.requireNonNull(fieldName); + Objects.requireNonNull(underlying); + + final long offset = offset(holderType, fieldName, int.class, Reflection.getCallerClass()); + return new StableIntUpdater<>(holderType, offset, underlying, zeroReplacement); + } + + // Only to be used by classes that are used "early" in the init sequence. + public static ToIntFunction ofIntRaw(Class holderType, + long offset, + ToIntFunction underlying, + int zeroReplacement) { + Objects.requireNonNull(holderType); + if (offset < 0) { + throw new IllegalArgumentException(); + } + Objects.requireNonNull(underlying); + return new StableIntUpdater<>(holderType, offset, underlying, zeroReplacement); + } + + @CallerSensitive + public static ToLongFunction ofLong(Class holderType, + String fieldName, + ToLongFunction underlying, + long zeroReplacement) { + Objects.requireNonNull(holderType); + Objects.requireNonNull(fieldName); + Objects.requireNonNull(underlying); + + final long offset = offset(holderType, fieldName, long.class, Reflection.getCallerClass()); + if (Architecture.is64bit()) { + return new StableLongUpdater<>(holderType, offset, underlying, zeroReplacement); + } else { + return new TearingStableLongUpdater<>(holderType, offset, underlying, zeroReplacement); + } + } + + private record StableIntUpdater(Class holderType, + long offset, + ToIntFunction underlying, + int zeroReplacement) implements ToIntFunction { + + @ForceInline + @Override + public int applyAsInt(T t) { + checkType(holderType, t); + // Plain semantics suffice here as we are not dealing with a reference (for + // a reference, the internal state initialization can be reordered with + // other store ops). JLS (24) 17.4 states that 64-bit fields tear under + // plain memory semantics. But, `int` is 32-bit. + int v = UNSAFE.getInt(t, offset); + if (v == 0) { + // StableUtil.preventReentry(t); + synchronized (t) { + v = UNSAFE.getIntAcquire(t, offset); + if (v == 0) { + v = underlying.applyAsInt(t); + if (v == 0) { + v = zeroReplacement; + } + UNSAFE.putIntRelease(t, offset, v); + } + } + } + return v; + } + } + + private record StableLongUpdater(Class holderType, + long offset, + ToLongFunction underlying, + long zeroReplacement) implements ToLongFunction { + + @ForceInline + @Override + public long applyAsLong(T t) { + checkType(holderType, t); + // Plain semantics suffice here as we are not dealing with a reference (for + // a reference, the internal state initialization can be reordered with + // other store ops). JLS (24) 17.4 states that 64-bit fields tear under + // plain memory semantics. But, the VM is 64-bit. + long v = UNSAFE.getLong(t, offset); + if (v == 0) { + // StableUtil.preventReentry(t); + synchronized (t) { + v = UNSAFE.getLongAcquire(t, offset); + if (v == 0) { + v = underlying.applyAsLong(t); + if (v == 0) { + v = zeroReplacement; + } + UNSAFE.putLongRelease(t, offset, v); + } + } + } + return v; + } + } + + private record TearingStableLongUpdater(Class holderType, + long offset, + ToLongFunction underlying, + long zeroReplacement) implements ToLongFunction { + + @ForceInline + @Override + public long applyAsLong(T t) { + checkType(holderType, t); + // Plain semantics suffice here as we are not dealing with a reference (for + // a reference, the internal state initialization can be reordered with + // other store ops). JLS (24) 17.4 states that 64-bit fields tear under + // plain memory semantics and this VM is not 64-bit. + long v = UNSAFE.getLongOpaque(t, offset); + if (v == 0) { + // StableUtil.preventReentry(t); + synchronized (t) { + v = UNSAFE.getLongAcquire(t, offset); + if (v == 0) { + v = underlying.applyAsLong(t); + if (v == 0) { + v = zeroReplacement; + } + UNSAFE.putLongRelease(t, offset, v); + } + } + } + return v; + } + } + + // Static support functions + + @ForceInline + private static void checkType(Class holderType, Object t) { + if (!holderType.isInstance(t)) { + throw new IllegalArgumentException("The provided t is not an instance of " + holderType); + } + } + + private static long offset(Class holderType, + String fieldName, + Class fieldType, + Class caller) { + final Field field; + try { + field = findField(holderType, fieldName); + int modifiers = field.getModifiers(); + if (Modifier.isFinal(modifiers)) { + throw illegalField("non final fields", field); + } + sun.reflect.misc.ReflectUtil.ensureMemberAccess( + caller, holderType, null, modifiers); + if (field.getType() != fieldType) { + throw illegalField("fields of type '" + fieldType + "'", field); + } + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new IllegalArgumentException(e); + } + return UNSAFE.objectFieldOffset(field); + } + + private static IllegalArgumentException illegalField(String msg, Field field) { + return new IllegalArgumentException("Only " + msg + " are supported. The provided field is '" + field + "'"); + } + + private static Field findField(Class holderType, String fieldName) throws NoSuchFieldException { + if (holderType.equals(Object.class)) { + throw new NoSuchFieldException("'" + fieldName + "' in '" + holderType + "'"); + } + final Field[] fields = holderType.getDeclaredFields(); + for (Field f : fields) { + if (f.getName().equals(fieldName)) { + return f; + } + } + return findField(holderType.getSuperclass(), fieldName); + } + +} diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableUtil.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableUtil.java index f6f33f9b1e86b..9336deea33ddf 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableUtil.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableUtil.java @@ -25,10 +25,15 @@ package jdk.internal.lang.stable; +import jdk.internal.misc.Unsafe; + +import java.lang.reflect.Field; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.StringJoiner; +import java.util.function.Function; +import java.util.function.ToIntFunction; public final class StableUtil { @@ -96,6 +101,14 @@ public static Map> map(Set keys) { return Map.ofEntries(entries); } + // This method is not annotated with @ForceInline as it is always called + // in a slow path. + static void preventReentry(Object obj) { + if (Thread.holdsLock(obj)) { + throw new IllegalStateException("Recursive initialization of a stable value is illegal"); + } + } + public static void assertSizeNonNegative(int size) { if (size < 0) { throw new IllegalArgumentException("size can not be negative: " + size); diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableValueImpl.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableValueImpl.java index 88c80eb6b395f..1d1a8578dbbcb 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableValueImpl.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableValueImpl.java @@ -81,7 +81,7 @@ public boolean trySet(T contents) { return false; } // Prevent reentry via an orElseSet(supplier) - preventReentry(); + StableUtil.preventReentry(this); // Mutual exclusion is required here as `orElseSet` might also // attempt to modify the `wrappedValue` synchronized (this) { @@ -132,7 +132,7 @@ public T orElseSet(Supplier supplier) { @DontInline private T orElseSetSlowPath(Supplier supplier) { - preventReentry(); + StableUtil.preventReentry(this); synchronized (this) { final Object t = contents; // Plain semantics suffice here if (t == null) { @@ -168,14 +168,6 @@ static String renderWrapped(Object t) { // Private methods - // This method is not annotated with @ForceInline as it is always called - // in a slow path. - private void preventReentry() { - if (Thread.holdsLock(this)) { - throw new IllegalStateException("Recursive initialization of a stable value is illegal"); - } - } - /** * Wraps the provided {@code newValue} and tries to set the contents. *

diff --git a/test/jdk/java/lang/StableValue/StableUpdaterTest.java b/test/jdk/java/lang/StableValue/StableUpdaterTest.java new file mode 100644 index 0000000000000..e947115524912 --- /dev/null +++ b/test/jdk/java/lang/StableValue/StableUpdaterTest.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* @test + * @summary Basic tests for StableUpdaters implementations + * @modules java.base/jdk.internal.lang.stable + * @run junit StableUpdatersTest + */ + +import jdk.internal.lang.stable.StableUpdater; +import org.junit.jupiter.api.Test; + +import java.util.function.ToIntFunction; + +import static org.junit.jupiter.api.Assertions.*; + +final class StableUpdaterTest { + + private static final int ZERO_REPLACEMENT = 42; + private static final String STRING = "Abc"; + private static final ToIntFunction STRING_HASH_CODE = String::hashCode; + + @Test + void invariants() { + assertThrows(NullPointerException.class, () -> StableUpdater.ofInt(null, "a", _ -> 0, ZERO_REPLACEMENT)); + assertThrows(NullPointerException.class, () -> StableUpdater.ofInt(String.class, null, _ -> 0, ZERO_REPLACEMENT)); + assertThrows(NullPointerException.class, () -> StableUpdater.ofInt(Foo.class, "hash", null, ZERO_REPLACEMENT)); + var x = assertThrows(IllegalArgumentException.class, () -> StableUpdater.ofInt(Foo.class, "dummy", _ -> 0, ZERO_REPLACEMENT)); + assertEquals("Only fields of type 'int' are supported. The provided field is 'long StableUpdatersTest$Foo.dummy'", x.getMessage()); + } + + @Test + void foo() { + var foo = new Foo(STRING); + assertEquals(0, foo.hash); + assertEquals(STRING.hashCode(), foo.hashCode()); + assertEquals(STRING.hashCode(), foo.hash); + } + + @Test + void recordFoo() { + var recordFoo = new RecordFoo(STRING, 0); + // The field is `final` + var x = assertThrows(IllegalArgumentException.class, + () -> StableUpdater.ofInt(RecordFoo.class, "hash", _ -> 0, ZERO_REPLACEMENT)); + assertEquals("Only non final fields are supported. " + + "The provided field is 'private final int StableUpdatersTest$RecordFoo.hash'", x.getMessage()); + } + + @Test + void barInherit() { + var bar = new Bar(STRING); + assertEquals(0, bar.hash); + assertEquals(STRING.hashCode(), bar.hashCode()); + assertEquals(STRING.hashCode(), bar.hash); + } + + + // Todo: Cast to T + + static final class Foo { + + private static final ToIntFunction UPDATER = + StableUpdater.ofInt(Foo.class, "hash", f -> f.string.hashCode(), ZERO_REPLACEMENT); + private final String string; + + int hash; + long dummy; + + public Foo(String string) { + this.string = string; + } + @Override + public int hashCode() { + return UPDATER.applyAsInt(this); + } + + } + + record RecordFoo(String string, int hash) {} + + static final class Bar extends AbstractBar { + private static final ToIntFunction UPDATER = + StableUpdater.ofInt(Bar.class, "hash", f -> f.string.hashCode(), ZERO_REPLACEMENT); + + public Bar(String string) { + super(string); + } + + @Override + public int hashCode() { + return UPDATER.applyAsInt(this); + } + } + + static abstract class AbstractBar { + final String string; + int hash; + + public AbstractBar(String string) { + this.string = string; + } + } + + +} diff --git a/test/micro/org/openjdk/bench/java/lang/foreign/SegmentBulkHash.java b/test/micro/org/openjdk/bench/java/lang/foreign/SegmentBulkHash.java index 927a7d3fb1f72..52c6c31e1cebc 100644 --- a/test/micro/org/openjdk/bench/java/lang/foreign/SegmentBulkHash.java +++ b/test/micro/org/openjdk/bench/java/lang/foreign/SegmentBulkHash.java @@ -46,6 +46,7 @@ import static java.lang.foreign.ValueLayout.JAVA_BYTE; + @BenchmarkMode(Mode.AverageTime) @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) @Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS) @@ -98,4 +99,3 @@ public int nativeSegmentJava() { } } - diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableUpdatersBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableUpdatersBenchmark.java new file mode 100644 index 0000000000000..db9cd0bc518ac --- /dev/null +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableUpdatersBenchmark.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.bench.java.lang.stable; + +import jdk.internal.lang.stable.StableUpdater; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Threads; +import org.openjdk.jmh.annotations.Warmup; + +import java.util.concurrent.TimeUnit; +import java.util.function.ToIntFunction; + +/** + * Benchmark measuring StableValue performance + */ +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@State(Scope.Benchmark) // Share the same state instance (for contention) +@Warmup(iterations = 5, time = 1) +@Measurement(iterations = 5, time = 2) +@Fork(value = 2, jvmArgs = {"--enable-preview", "--add-exports", "java.base/jdk.internal.lang.stable=ALL-UNNAMED"}) +@Threads(Threads.MAX) // Benchmark under contention +public class StableUpdatersBenchmark { + + private static final String STRING = "Abc"; + + private static final Base BASE = new Base(STRING); + private static final Updater UPDATER = new Updater(STRING); + + private final Base base = new Base(STRING); + private final Updater updater = new Updater(STRING); + + @Benchmark + public int baseStatic() { + return BASE.hashCode(); + } + + @Benchmark + public int updaterStatic() { + return UPDATER.hashCode(); + } + + @Benchmark + public int base() { + return base.hashCode(); + } + + @Benchmark + public int updater() { + return updater.hashCode(); + } + + static final class Base extends Abstract { + Base(String string) { super(string); } + + @Override + public int hashCode() { + int h = hashCode; + if (h == 0) { + hashCode = h = string.hashCode(); + } + return h; + } + } + + static final class Updater extends Abstract { + + private static final ToIntFunction HASH_CODE_UPDATER = + StableUpdater.ofInt(Updater.class, "hashCode", new ToIntFunction<>() { + @Override + public int applyAsInt(Updater updater) { + return updater.string.hashCode(); + } + }, -1); + + Updater(String string) { super(string); } + + @Override + public int hashCode() { + return HASH_CODE_UPDATER.applyAsInt(this); + } + } + + static class Abstract { + + final String string; + int hashCode; + + Abstract(String string) { + this.string = string; + } + } + +} From 5ecee278476e3194c0a72fc15a29ed25d0ee0eab Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 5 May 2025 15:28:35 +0200 Subject: [PATCH 02/24] Rename classes --- .../classes/java/lang/reflect/Method.java | 4 +-- src/java.base/share/classes/java/net/URI.java | 4 +-- ...leUpdater.java => StableFieldUpdater.java} | 36 +++++++++---------- ...rTest.java => StableFieldUpdaterTest.java} | 18 +++++----- .../lang/stable/StableUpdatersBenchmark.java | 4 +-- 5 files changed, 33 insertions(+), 33 deletions(-) rename src/java.base/share/classes/jdk/internal/lang/stable/{StableUpdater.java => StableFieldUpdater.java} (84%) rename test/jdk/java/lang/StableValue/{StableUpdaterTest.java => StableFieldUpdaterTest.java} (80%) diff --git a/src/java.base/share/classes/java/lang/reflect/Method.java b/src/java.base/share/classes/java/lang/reflect/Method.java index 6c017512f8459..3a28fcafac49d 100644 --- a/src/java.base/share/classes/java/lang/reflect/Method.java +++ b/src/java.base/share/classes/java/lang/reflect/Method.java @@ -26,7 +26,7 @@ package java.lang.reflect; import jdk.internal.access.SharedSecrets; -import jdk.internal.lang.stable.StableUpdater; +import jdk.internal.lang.stable.StableFieldUpdater; import jdk.internal.misc.Unsafe; import jdk.internal.misc.VM; import jdk.internal.reflect.CallerSensitive; @@ -98,7 +98,7 @@ public final class Method extends Executable { private @Stable MethodAccessor methodAccessor; private static final ToIntFunction HASH_UPDATER = - StableUpdater.ofIntRaw(Method.class, Unsafe.getUnsafe().objectFieldOffset(Method.class, "hash"), new ToIntFunction() { + StableFieldUpdater.ofIntRaw(Method.class, Unsafe.getUnsafe().objectFieldOffset(Method.class, "hash"), new ToIntFunction() { @Override public int applyAsInt(Method method) { return method.getDeclaringClass().getName().hashCode() ^ method.getName() diff --git a/src/java.base/share/classes/java/net/URI.java b/src/java.base/share/classes/java/net/URI.java index b5f9f69434933..ef864cdf0cf29 100644 --- a/src/java.base/share/classes/java/net/URI.java +++ b/src/java.base/share/classes/java/net/URI.java @@ -44,7 +44,7 @@ import jdk.internal.access.JavaNetUriAccess; import jdk.internal.access.SharedSecrets; -import jdk.internal.lang.stable.StableUpdater; +import jdk.internal.lang.stable.StableFieldUpdater; import jdk.internal.vm.annotation.ForceInline; import sun.nio.cs.UTF_8; @@ -549,7 +549,7 @@ public final class URI // the face of multiple threads racing to initialize them private transient String schemeSpecificPart; - private static final ToIntFunction HASH_UPDATER = StableUpdater.ofInt( + private static final ToIntFunction HASH_UPDATER = StableFieldUpdater.ofInt( URI.class, "hash", new ToIntFunction<>() { @ForceInline @Override public int applyAsInt(URI uri) { return uri.hashCode0(); }}, -1); diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableUpdater.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java similarity index 84% rename from src/java.base/share/classes/jdk/internal/lang/stable/StableUpdater.java rename to src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java index ac3f63adf09d6..86075f5ce05de 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableUpdater.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java @@ -18,9 +18,9 @@ * The provided {@code underlying} function must not recurse or the result of the * operation is unspecified. */ -public final class StableUpdater { +public final class StableFieldUpdater { - private StableUpdater() {} + private StableFieldUpdater() {} private static final Unsafe UNSAFE = Unsafe.getUnsafe(); @@ -34,7 +34,7 @@ public static ToIntFunction ofInt(Class holderType, Objects.requireNonNull(underlying); final long offset = offset(holderType, fieldName, int.class, Reflection.getCallerClass()); - return new StableIntUpdater<>(holderType, offset, underlying, zeroReplacement); + return new StableIntFieldUpdater<>(holderType, offset, underlying, zeroReplacement); } // Only to be used by classes that are used "early" in the init sequence. @@ -47,7 +47,7 @@ public static ToIntFunction ofIntRaw(Class holderType, throw new IllegalArgumentException(); } Objects.requireNonNull(underlying); - return new StableIntUpdater<>(holderType, offset, underlying, zeroReplacement); + return new StableIntFieldUpdater<>(holderType, offset, underlying, zeroReplacement); } @CallerSensitive @@ -61,16 +61,16 @@ public static ToLongFunction ofLong(Class holderType, final long offset = offset(holderType, fieldName, long.class, Reflection.getCallerClass()); if (Architecture.is64bit()) { - return new StableLongUpdater<>(holderType, offset, underlying, zeroReplacement); + return new StableLongFieldUpdater<>(holderType, offset, underlying, zeroReplacement); } else { - return new TearingStableLongUpdater<>(holderType, offset, underlying, zeroReplacement); + return new TearingStableLongFieldUpdater<>(holderType, offset, underlying, zeroReplacement); } } - private record StableIntUpdater(Class holderType, - long offset, - ToIntFunction underlying, - int zeroReplacement) implements ToIntFunction { + private record StableIntFieldUpdater(Class holderType, + long offset, + ToIntFunction underlying, + int zeroReplacement) implements ToIntFunction { @ForceInline @Override @@ -98,10 +98,10 @@ public int applyAsInt(T t) { } } - private record StableLongUpdater(Class holderType, - long offset, - ToLongFunction underlying, - long zeroReplacement) implements ToLongFunction { + private record StableLongFieldUpdater(Class holderType, + long offset, + ToLongFunction underlying, + long zeroReplacement) implements ToLongFunction { @ForceInline @Override @@ -129,10 +129,10 @@ public long applyAsLong(T t) { } } - private record TearingStableLongUpdater(Class holderType, - long offset, - ToLongFunction underlying, - long zeroReplacement) implements ToLongFunction { + private record TearingStableLongFieldUpdater(Class holderType, + long offset, + ToLongFunction underlying, + long zeroReplacement) implements ToLongFunction { @ForceInline @Override diff --git a/test/jdk/java/lang/StableValue/StableUpdaterTest.java b/test/jdk/java/lang/StableValue/StableFieldUpdaterTest.java similarity index 80% rename from test/jdk/java/lang/StableValue/StableUpdaterTest.java rename to test/jdk/java/lang/StableValue/StableFieldUpdaterTest.java index e947115524912..7d3211e0adf4a 100644 --- a/test/jdk/java/lang/StableValue/StableUpdaterTest.java +++ b/test/jdk/java/lang/StableValue/StableFieldUpdaterTest.java @@ -27,14 +27,14 @@ * @run junit StableUpdatersTest */ -import jdk.internal.lang.stable.StableUpdater; +import jdk.internal.lang.stable.StableFieldUpdater; import org.junit.jupiter.api.Test; import java.util.function.ToIntFunction; import static org.junit.jupiter.api.Assertions.*; -final class StableUpdaterTest { +final class StableFieldUpdaterTest { private static final int ZERO_REPLACEMENT = 42; private static final String STRING = "Abc"; @@ -42,10 +42,10 @@ final class StableUpdaterTest { @Test void invariants() { - assertThrows(NullPointerException.class, () -> StableUpdater.ofInt(null, "a", _ -> 0, ZERO_REPLACEMENT)); - assertThrows(NullPointerException.class, () -> StableUpdater.ofInt(String.class, null, _ -> 0, ZERO_REPLACEMENT)); - assertThrows(NullPointerException.class, () -> StableUpdater.ofInt(Foo.class, "hash", null, ZERO_REPLACEMENT)); - var x = assertThrows(IllegalArgumentException.class, () -> StableUpdater.ofInt(Foo.class, "dummy", _ -> 0, ZERO_REPLACEMENT)); + assertThrows(NullPointerException.class, () -> StableFieldUpdater.ofInt(null, "a", _ -> 0, ZERO_REPLACEMENT)); + assertThrows(NullPointerException.class, () -> StableFieldUpdater.ofInt(String.class, null, _ -> 0, ZERO_REPLACEMENT)); + assertThrows(NullPointerException.class, () -> StableFieldUpdater.ofInt(Foo.class, "hash", null, ZERO_REPLACEMENT)); + var x = assertThrows(IllegalArgumentException.class, () -> StableFieldUpdater.ofInt(Foo.class, "dummy", _ -> 0, ZERO_REPLACEMENT)); assertEquals("Only fields of type 'int' are supported. The provided field is 'long StableUpdatersTest$Foo.dummy'", x.getMessage()); } @@ -62,7 +62,7 @@ void recordFoo() { var recordFoo = new RecordFoo(STRING, 0); // The field is `final` var x = assertThrows(IllegalArgumentException.class, - () -> StableUpdater.ofInt(RecordFoo.class, "hash", _ -> 0, ZERO_REPLACEMENT)); + () -> StableFieldUpdater.ofInt(RecordFoo.class, "hash", _ -> 0, ZERO_REPLACEMENT)); assertEquals("Only non final fields are supported. " + "The provided field is 'private final int StableUpdatersTest$RecordFoo.hash'", x.getMessage()); } @@ -81,7 +81,7 @@ void barInherit() { static final class Foo { private static final ToIntFunction UPDATER = - StableUpdater.ofInt(Foo.class, "hash", f -> f.string.hashCode(), ZERO_REPLACEMENT); + StableFieldUpdater.ofInt(Foo.class, "hash", f -> f.string.hashCode(), ZERO_REPLACEMENT); private final String string; int hash; @@ -101,7 +101,7 @@ record RecordFoo(String string, int hash) {} static final class Bar extends AbstractBar { private static final ToIntFunction UPDATER = - StableUpdater.ofInt(Bar.class, "hash", f -> f.string.hashCode(), ZERO_REPLACEMENT); + StableFieldUpdater.ofInt(Bar.class, "hash", f -> f.string.hashCode(), ZERO_REPLACEMENT); public Bar(String string) { super(string); diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableUpdatersBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableUpdatersBenchmark.java index db9cd0bc518ac..7d1006dc764a2 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/StableUpdatersBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableUpdatersBenchmark.java @@ -23,7 +23,7 @@ package org.openjdk.bench.java.lang.stable; -import jdk.internal.lang.stable.StableUpdater; +import jdk.internal.lang.stable.StableFieldUpdater; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; @@ -94,7 +94,7 @@ public int hashCode() { static final class Updater extends Abstract { private static final ToIntFunction HASH_CODE_UPDATER = - StableUpdater.ofInt(Updater.class, "hashCode", new ToIntFunction<>() { + StableFieldUpdater.ofInt(Updater.class, "hashCode", new ToIntFunction<>() { @Override public int applyAsInt(Updater updater) { return updater.string.hashCode(); From 6c95aac78bd52a0532ff352cca6590e8244fe9aa Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 5 May 2025 15:40:47 +0200 Subject: [PATCH 03/24] Add @Stable for URI and add benchmark --- src/java.base/share/classes/java/net/URI.java | 2 ++ .../lang/stable/StableUpdatersBenchmark.java | 23 +++++++++++++++---- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/java.base/share/classes/java/net/URI.java b/src/java.base/share/classes/java/net/URI.java index ef864cdf0cf29..f60ac8b884bdf 100644 --- a/src/java.base/share/classes/java/net/URI.java +++ b/src/java.base/share/classes/java/net/URI.java @@ -46,6 +46,7 @@ import jdk.internal.access.SharedSecrets; import jdk.internal.lang.stable.StableFieldUpdater; import jdk.internal.vm.annotation.ForceInline; +import jdk.internal.vm.annotation.Stable; import sun.nio.cs.UTF_8; /** @@ -554,6 +555,7 @@ public final class URI @ForceInline @Override public int applyAsInt(URI uri) { return uri.hashCode0(); }}, -1); // Used reflectively by HASH_UPDATER + @Stable private transient int hash; // Zero ==> undefined private transient String decodedUserInfo; diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableUpdatersBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableUpdatersBenchmark.java index 7d1006dc764a2..dc6fe5824510d 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/StableUpdatersBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableUpdatersBenchmark.java @@ -35,6 +35,7 @@ import org.openjdk.jmh.annotations.Threads; import org.openjdk.jmh.annotations.Warmup; +import java.net.URI; import java.util.concurrent.TimeUnit; import java.util.function.ToIntFunction; @@ -50,13 +51,15 @@ @Threads(Threads.MAX) // Benchmark under contention public class StableUpdatersBenchmark { - private static final String STRING = "Abc"; + private static final String STRING = "https://some.site.com"; private static final Base BASE = new Base(STRING); private static final Updater UPDATER = new Updater(STRING); + private static final URI U_R_I = URI.create(STRING); private final Base base = new Base(STRING); private final Updater updater = new Updater(STRING); + private static final URI uri = URI.create(STRING); @Benchmark public int baseStatic() { @@ -64,13 +67,13 @@ public int baseStatic() { } @Benchmark - public int updaterStatic() { - return UPDATER.hashCode(); + public int base() { + return base.hashCode(); } @Benchmark - public int base() { - return base.hashCode(); + public int updaterStatic() { + return UPDATER.hashCode(); } @Benchmark @@ -78,6 +81,16 @@ public int updater() { return updater.hashCode(); } + @Benchmark + public int uriStatic() { + return U_R_I.hashCode(); + } + + @Benchmark + public int uri() { + return uri.hashCode(); + } + static final class Base extends Abstract { Base(String string) { super(string); } From 2bc67ccb5764b91cea8bd372568e38f596d3345b Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 6 May 2025 10:45:57 +0200 Subject: [PATCH 04/24] Add examples to doc and fix tests --- make/test/BuildMicrobenchmark.gmk | 1 + .../lang/stable/StableFieldUpdater.java | 102 ++++++++++ .../StableFieldUpdaterExampleTest.java | 189 ++++++++++++++++++ .../StableValue/StableFieldUpdaterTest.java | 9 +- 4 files changed, 296 insertions(+), 5 deletions(-) create mode 100644 test/jdk/java/lang/StableValue/StableFieldUpdaterExampleTest.java diff --git a/make/test/BuildMicrobenchmark.gmk b/make/test/BuildMicrobenchmark.gmk index 92f40472c3cb9..94543a4289039 100644 --- a/make/test/BuildMicrobenchmark.gmk +++ b/make/test/BuildMicrobenchmark.gmk @@ -92,6 +92,7 @@ $(eval $(call SetupJavaCompilation, BUILD_JDK_MICROBENCHMARK, \ --add-exports java.base/jdk.internal.classfile.impl=ALL-UNNAMED \ --add-exports java.base/jdk.internal.event=ALL-UNNAMED \ --add-exports java.base/jdk.internal.foreign=ALL-UNNAMED \ + --add-exports java.base/jdk.internal.lang.stable=ALL-UNNAMED \ --add-exports java.base/jdk.internal.misc=ALL-UNNAMED \ --add-exports java.base/jdk.internal.util=ALL-UNNAMED \ --add-exports java.base/jdk.internal.vm=ALL-UNNAMED \ diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java index 86075f5ce05de..c54332531e215 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java @@ -15,6 +15,108 @@ /** * Stable field updaters. *

+ * This class allows, for example, effortless conversion of immutable classes to use lazy + * {@link Object#hashCode()}. + *

+ * Here is an example of how to convert + * + * {@snippet lang=java: + * public final class Foo { + * + * private final Bar bar; + * private final Baz baz; + * + * public Foo(Bar bar, Baz baz) { + * this.bar = bar; + * this.baz = baz; + * } + * + * @Override + * public boolean equals(Object o) { + * return o instanceof Foo that && + * Objects.equals(this.bar, that.bar) && + * Objects.equals(this.baz, that.baz); + * } + * + * @Override + * public int hashCode() { + * return Objects.hash(bar, baz); + * } + * } + * } + * to use {@code @Stable} lazy hashing: + * + * {@snippet lang=java: + * public final class LazyFoo { + * + * private final Bar bar; + * private final Baz baz; + * + * private static final ToIntFunction HASH_UPDATER = + * StableFieldUpdater.ofInt(LazyFoo.class, "hash", + * l -> Objects.hash(l.bar, l.baz), -1); + * + * @Stable + * private int hash; + * + * public LazyFoo(Bar bar, Baz baz) { + * this.bar = bar; + * this.baz = baz; + * } + * + * @Override + * public boolean equals(Object o) { + * return o instanceof Foo that && + * Objects.equals(this.bar, that.bar) && + * Objects.equals(this.baz, that.baz); + * } + * + * @Override + * public int hashCode() { + * return HASH_UPDATER.applyAsInt(this); + * } + * } + * } + *

+ * If the underlying hash lamba returns zero, it is replaced with {@code -1}. + * + * In cases where the entire range of hash codes are strictly specified (as it is for + * {@code String}), a {@code long} field can be used instead, and then we can use + * {@code 1 << 32} as a token for zero (as the lower 32 bits are zero) and then just + * cast to an {@code int} as shown in this example: + * + * {@snippet lang=java: + public final class LazySpecifiedFoo { + + private final Bar bar; + private final Baz baz; + + private static final ToLongFunction HASH_UPDATER = + StableFieldUpdater.ofLong(LazySpecifiedFoo.class, "hash", + l -> Objects.hash(l.bar, l.baz), 1L << 32); + + @Stable + private long hash; + + public LazySpecifiedFoo(Bar bar, Baz baz) { + this.bar = bar; + this.baz = baz; + } + + @Override + public boolean equals(Object o) { + return (o instanceof Foo that) && + Objects.equals(this.bar, that.bar) && + Objects.equals(this.baz, that.baz); + } + + @Override + public int hashCode() { + return (int) HASH_UPDATER.applyAsLong(this); + } + } + * } + *

* The provided {@code underlying} function must not recurse or the result of the * operation is unspecified. */ diff --git a/test/jdk/java/lang/StableValue/StableFieldUpdaterExampleTest.java b/test/jdk/java/lang/StableValue/StableFieldUpdaterExampleTest.java new file mode 100644 index 0000000000000..0a59f8ae14506 --- /dev/null +++ b/test/jdk/java/lang/StableValue/StableFieldUpdaterExampleTest.java @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* @test + * @summary Basic tests for StableFieldUpdaterExampleTest examples in javadoc + * @modules java.base/jdk.internal.lang.stable + * @run junit StableFieldUpdaterExampleTest + */ + +import jdk.internal.lang.stable.StableFieldUpdater; +import org.junit.jupiter.api.Test; + +import java.lang.Override; +import java.util.Objects; +import java.util.function.ToIntFunction; +import java.util.function.ToLongFunction; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +final class StableFieldUpdaterExampleTest { + + @interface Stable {} // No access to the real @Stable + + public static final class Bar extends Base { + public Bar(int hash) { + super(hash); + } + } + + public static final class Baz extends Base { + public Baz(int hash) { + super(hash); + } + } + + public static abstract class Base { + + final int hash; + + public Base(int hash) { + this.hash = hash; + } + + @Override + public boolean equals(Object obj) { + return obj instanceof Base that && + this.getClass().equals(that.getClass()) && + this.hash == that.hash; + } + + @Override + public int hashCode() { + return hash; + } + + @Override + public String toString() { + return getClass().getSimpleName() + "[hash=" + hash + ']'; + } + } + + + static // Intentionally in unblessed order to allow the example to look nice + + public final class Foo { + + private final Bar bar; + private final Baz baz; + + public Foo(Bar bar, Baz baz) { + this.bar = bar; + this.baz = baz; + } + + @Override + public boolean equals(Object o) { + return o instanceof Foo that && + Objects.equals(this.bar, that.bar) && + Objects.equals(this.baz, that.baz); + } + + @Override + public int hashCode() { + return Objects.hash(bar, baz); + } + } + + static + + public final class LazyFoo { + + private final Bar bar; + private final Baz baz; + + private static final ToIntFunction HASH_UPDATER = + StableFieldUpdater.ofInt(LazyFoo.class, "hash", + l -> Objects.hash(l.bar, l.baz), -1); + + @Stable + private int hash; + + public LazyFoo(Bar bar, Baz baz) { + this.bar = bar; + this.baz = baz; + } + + @Override + public boolean equals(Object o) { + return o instanceof Foo that && + Objects.equals(this.bar, that.bar) && + Objects.equals(this.baz, that.baz); + } + + @Override + public int hashCode() { + return HASH_UPDATER.applyAsInt(this); + } + } + + static + + public final class LazySpecifiedFoo { + + private final Bar bar; + private final Baz baz; + + private static final ToLongFunction HASH_UPDATER = + StableFieldUpdater.ofLong(LazySpecifiedFoo.class, "hash", + l -> Objects.hash(l.bar, l.baz), 1L << 32); + + @Stable + private long hash; + + public LazySpecifiedFoo(Bar bar, Baz baz) { + this.bar = bar; + this.baz = baz; + } + + @Override + public boolean equals(Object o) { + return (o instanceof Foo that) && + Objects.equals(this.bar, that.bar) && + Objects.equals(this.baz, that.baz); + } + + @Override + public int hashCode() { + return (int) HASH_UPDATER.applyAsLong(this); + } + } + + @Test + void lazy() { + var bar = new Bar(1); + var baz = new Baz(2); + + var foo = new LazyFoo(bar, baz); + + assertEquals(Objects.hash(1, 2), foo.hashCode()); + } + + @Test + void lazySpec() { + var foo = new LazySpecifiedFoo(null, null); + assertEquals(0, foo.hashCode()); + assertEquals(0, foo.hashCode()); + } + +} diff --git a/test/jdk/java/lang/StableValue/StableFieldUpdaterTest.java b/test/jdk/java/lang/StableValue/StableFieldUpdaterTest.java index 7d3211e0adf4a..ced76be9b4db5 100644 --- a/test/jdk/java/lang/StableValue/StableFieldUpdaterTest.java +++ b/test/jdk/java/lang/StableValue/StableFieldUpdaterTest.java @@ -22,9 +22,9 @@ */ /* @test - * @summary Basic tests for StableUpdaters implementations + * @summary Basic tests for StableFieldUpdater implementations * @modules java.base/jdk.internal.lang.stable - * @run junit StableUpdatersTest + * @run junit StableFieldUpdaterTest */ import jdk.internal.lang.stable.StableFieldUpdater; @@ -46,7 +46,7 @@ void invariants() { assertThrows(NullPointerException.class, () -> StableFieldUpdater.ofInt(String.class, null, _ -> 0, ZERO_REPLACEMENT)); assertThrows(NullPointerException.class, () -> StableFieldUpdater.ofInt(Foo.class, "hash", null, ZERO_REPLACEMENT)); var x = assertThrows(IllegalArgumentException.class, () -> StableFieldUpdater.ofInt(Foo.class, "dummy", _ -> 0, ZERO_REPLACEMENT)); - assertEquals("Only fields of type 'int' are supported. The provided field is 'long StableUpdatersTest$Foo.dummy'", x.getMessage()); + assertEquals("Only fields of type 'int' are supported. The provided field is 'long StableFieldUpdaterTest$Foo.dummy'", x.getMessage()); } @Test @@ -63,8 +63,7 @@ void recordFoo() { // The field is `final` var x = assertThrows(IllegalArgumentException.class, () -> StableFieldUpdater.ofInt(RecordFoo.class, "hash", _ -> 0, ZERO_REPLACEMENT)); - assertEquals("Only non final fields are supported. " + - "The provided field is 'private final int StableUpdatersTest$RecordFoo.hash'", x.getMessage()); + assertEquals("Only non final fields are supported. The provided field is 'private final int StableFieldUpdaterTest$RecordFoo.hash'", x.getMessage()); } @Test From 2548d7fc96c5c5332b970012fa033246fcf3480b Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 6 May 2025 11:19:47 +0200 Subject: [PATCH 05/24] Add copyright header and revert unintended change --- .../lang/stable/StableFieldUpdater.java | 25 +++++++++++++++++++ .../java/lang/foreign/SegmentBulkHash.java | 1 - 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java index c54332531e215..d57e5001776fd 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java @@ -1,3 +1,28 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + package jdk.internal.lang.stable; import jdk.internal.misc.Unsafe; diff --git a/test/micro/org/openjdk/bench/java/lang/foreign/SegmentBulkHash.java b/test/micro/org/openjdk/bench/java/lang/foreign/SegmentBulkHash.java index 52c6c31e1cebc..7e702f3e096ec 100644 --- a/test/micro/org/openjdk/bench/java/lang/foreign/SegmentBulkHash.java +++ b/test/micro/org/openjdk/bench/java/lang/foreign/SegmentBulkHash.java @@ -46,7 +46,6 @@ import static java.lang.foreign.ValueLayout.JAVA_BYTE; - @BenchmarkMode(Mode.AverageTime) @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) @Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS) From 2fb0cb3ab1089d815f2dddc801937bdfe2f795b2 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 6 May 2025 12:42:40 +0200 Subject: [PATCH 06/24] Move raw factories into inner class Raw --- .../classes/java/lang/reflect/Method.java | 2 +- .../lang/stable/StableFieldUpdater.java | 46 +++++++++++++------ .../StableFieldUpdaterExampleTest.java | 3 +- 3 files changed, 36 insertions(+), 15 deletions(-) diff --git a/src/java.base/share/classes/java/lang/reflect/Method.java b/src/java.base/share/classes/java/lang/reflect/Method.java index 3a28fcafac49d..0ec44fc9401d6 100644 --- a/src/java.base/share/classes/java/lang/reflect/Method.java +++ b/src/java.base/share/classes/java/lang/reflect/Method.java @@ -98,7 +98,7 @@ public final class Method extends Executable { private @Stable MethodAccessor methodAccessor; private static final ToIntFunction HASH_UPDATER = - StableFieldUpdater.ofIntRaw(Method.class, Unsafe.getUnsafe().objectFieldOffset(Method.class, "hash"), new ToIntFunction() { + StableFieldUpdater.Raw.ofInt(Method.class, Unsafe.getUnsafe().objectFieldOffset(Method.class, "hash"), new ToIntFunction() { @Override public int applyAsInt(Method method) { return method.getDeclaringClass().getName().hashCode() ^ method.getName() diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java index d57e5001776fd..fc111afa44000 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java @@ -164,19 +164,6 @@ public static ToIntFunction ofInt(Class holderType, return new StableIntFieldUpdater<>(holderType, offset, underlying, zeroReplacement); } - // Only to be used by classes that are used "early" in the init sequence. - public static ToIntFunction ofIntRaw(Class holderType, - long offset, - ToIntFunction underlying, - int zeroReplacement) { - Objects.requireNonNull(holderType); - if (offset < 0) { - throw new IllegalArgumentException(); - } - Objects.requireNonNull(underlying); - return new StableIntFieldUpdater<>(holderType, offset, underlying, zeroReplacement); - } - @CallerSensitive public static ToLongFunction ofLong(Class holderType, String fieldName, @@ -194,6 +181,39 @@ public static ToLongFunction ofLong(Class holderType, } } + + // Only to be used by classes that are used "early" in the init sequence. + public static final class Raw { + + private Raw() {} + + public static ToIntFunction ofInt(Class holderType, + long offset, + ToIntFunction underlying, + int zeroReplacement) { + Objects.requireNonNull(holderType); + if (offset < 0) { + throw new IllegalArgumentException(); + } + Objects.requireNonNull(underlying); + return new StableIntFieldUpdater<>(holderType, offset, underlying, zeroReplacement); + } + + public static ToLongFunction ofLong(Class holderType, + long offset, + ToLongFunction underlying, + long zeroReplacement) { + Objects.requireNonNull(holderType); + if (offset < 0) { + throw new IllegalArgumentException(); + } + Objects.requireNonNull(underlying); + return new StableLongFieldUpdater<>(holderType, offset, underlying, zeroReplacement); + } + + } + + private record StableIntFieldUpdater(Class holderType, long offset, ToIntFunction underlying, diff --git a/test/jdk/java/lang/StableValue/StableFieldUpdaterExampleTest.java b/test/jdk/java/lang/StableValue/StableFieldUpdaterExampleTest.java index 0a59f8ae14506..f9dffe94c2fd8 100644 --- a/test/jdk/java/lang/StableValue/StableFieldUpdaterExampleTest.java +++ b/test/jdk/java/lang/StableValue/StableFieldUpdaterExampleTest.java @@ -146,7 +146,7 @@ public final class LazySpecifiedFoo { private static final ToLongFunction HASH_UPDATER = StableFieldUpdater.ofLong(LazySpecifiedFoo.class, "hash", - l -> Objects.hash(l.bar, l.baz), 1L << 32); + l -> (l.bar == null && l.baz == null) ? 0 : Objects.hash(l.bar, l.baz), 1L << 32); @Stable private long hash; @@ -184,6 +184,7 @@ void lazySpec() { var foo = new LazySpecifiedFoo(null, null); assertEquals(0, foo.hashCode()); assertEquals(0, foo.hashCode()); + assertEquals(1L << 32, foo.hash); } } From 94b4023d255654b75c949cfe1279db275c3f1b5d Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 6 May 2025 12:51:11 +0200 Subject: [PATCH 07/24] Add unhecked call test --- .../java/lang/StableValue/StableFieldUpdaterTest.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/test/jdk/java/lang/StableValue/StableFieldUpdaterTest.java b/test/jdk/java/lang/StableValue/StableFieldUpdaterTest.java index ced76be9b4db5..e865cac5a9f2e 100644 --- a/test/jdk/java/lang/StableValue/StableFieldUpdaterTest.java +++ b/test/jdk/java/lang/StableValue/StableFieldUpdaterTest.java @@ -75,7 +75,15 @@ void barInherit() { } - // Todo: Cast to T + @SuppressWarnings({"rawtypes", "unchecked"}) + @Test + void uncheckedCall() { + // Use a raw type + ToIntFunction updater = StableFieldUpdater.ofInt(Foo.class, "hash", f -> f.string.hashCode(), ZERO_REPLACEMENT); + var object = new Object(); + var x = assertThrows(IllegalArgumentException.class, () -> updater.applyAsInt(object)); + assertEquals("The provided t is not an instance of class StableFieldUpdaterTest$Foo", x.getMessage()); + } static final class Foo { From a3546a6ce793bdf042bb6afb522fd434f9a53324 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 6 May 2025 13:18:57 +0200 Subject: [PATCH 08/24] Document field alignment assumption --- .../classes/jdk/internal/lang/stable/StableFieldUpdater.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java index fc111afa44000..c2b4784e7dc16 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java @@ -175,6 +175,8 @@ public static ToLongFunction ofLong(Class holderType, final long offset = offset(holderType, fieldName, long.class, Reflection.getCallerClass()); if (Architecture.is64bit()) { + // We are also relying on the fact that the VM will not place 64-bit + // instance fields that can cross cache lines. return new StableLongFieldUpdater<>(holderType, offset, underlying, zeroReplacement); } else { return new TearingStableLongFieldUpdater<>(holderType, offset, underlying, zeroReplacement); From 33e1779c000ac5aecef0311e2665b8a84e78a83e Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 6 May 2025 13:26:29 +0200 Subject: [PATCH 09/24] Address comments --- .../classes/java/lang/reflect/Method.java | 4 +-- .../lang/stable/StableFieldUpdater.java | 25 +++++++++---------- .../StableFieldUpdaterExampleTest.java | 18 ++++++------- 3 files changed, 23 insertions(+), 24 deletions(-) diff --git a/src/java.base/share/classes/java/lang/reflect/Method.java b/src/java.base/share/classes/java/lang/reflect/Method.java index 0ec44fc9401d6..4494591ec5032 100644 --- a/src/java.base/share/classes/java/lang/reflect/Method.java +++ b/src/java.base/share/classes/java/lang/reflect/Method.java @@ -101,8 +101,8 @@ public final class Method extends Executable { StableFieldUpdater.Raw.ofInt(Method.class, Unsafe.getUnsafe().objectFieldOffset(Method.class, "hash"), new ToIntFunction() { @Override public int applyAsInt(Method method) { - return method.getDeclaringClass().getName().hashCode() ^ method.getName() - .hashCode(); + return method.getDeclaringClass().getName().hashCode() ^ + method.getName().hashCode(); } }, 0); // The `hashCode()` is specified so, zeroReplacement is 0 // End shared states diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java index c2b4784e7dc16..efbaf5369945b 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java @@ -30,6 +30,7 @@ import jdk.internal.reflect.Reflection; import jdk.internal.util.Architecture; import jdk.internal.vm.annotation.ForceInline; +import sun.reflect.misc.ReflectUtil; import java.lang.reflect.Field; import java.lang.reflect.Modifier; @@ -58,9 +59,9 @@ * * @Override * public boolean equals(Object o) { - * return o instanceof Foo that && - * Objects.equals(this.bar, that.bar) && - * Objects.equals(this.baz, that.baz); + * return o instanceof Foo that + * && Objects.equals(this.bar, that.bar) + * && Objects.equals(this.baz, that.baz); * } * * @Override @@ -91,9 +92,9 @@ * * @Override * public boolean equals(Object o) { - * return o instanceof Foo that && - * Objects.equals(this.bar, that.bar) && - * Objects.equals(this.baz, that.baz); + * return o instanceof Foo that + * && Objects.equals(this.bar, that.bar) + * && Objects.equals(this.baz, that.baz); * } * * @Override @@ -130,9 +131,9 @@ public LazySpecifiedFoo(Bar bar, Baz baz) { @Override public boolean equals(Object o) { - return (o instanceof Foo that) && - Objects.equals(this.bar, that.bar) && - Objects.equals(this.baz, that.baz); + return (o instanceof Foo that) + && Objects.equals(this.bar, that.bar) + && Objects.equals(this.baz, that.baz); } @Override @@ -329,8 +330,7 @@ private static long offset(Class holderType, if (Modifier.isFinal(modifiers)) { throw illegalField("non final fields", field); } - sun.reflect.misc.ReflectUtil.ensureMemberAccess( - caller, holderType, null, modifiers); + ReflectUtil.ensureMemberAccess(caller, holderType, null, modifiers); if (field.getType() != fieldType) { throw illegalField("fields of type '" + fieldType + "'", field); } @@ -348,8 +348,7 @@ private static Field findField(Class holderType, String fieldName) throws NoS if (holderType.equals(Object.class)) { throw new NoSuchFieldException("'" + fieldName + "' in '" + holderType + "'"); } - final Field[] fields = holderType.getDeclaredFields(); - for (Field f : fields) { + for (Field f : holderType.getDeclaredFields()) { if (f.getName().equals(fieldName)) { return f; } diff --git a/test/jdk/java/lang/StableValue/StableFieldUpdaterExampleTest.java b/test/jdk/java/lang/StableValue/StableFieldUpdaterExampleTest.java index f9dffe94c2fd8..99f6b0ace12a5 100644 --- a/test/jdk/java/lang/StableValue/StableFieldUpdaterExampleTest.java +++ b/test/jdk/java/lang/StableValue/StableFieldUpdaterExampleTest.java @@ -94,9 +94,9 @@ public Foo(Bar bar, Baz baz) { @Override public boolean equals(Object o) { - return o instanceof Foo that && - Objects.equals(this.bar, that.bar) && - Objects.equals(this.baz, that.baz); + return o instanceof Foo that + && Objects.equals(this.bar, that.bar) + && Objects.equals(this.baz, that.baz); } @Override @@ -126,9 +126,9 @@ public LazyFoo(Bar bar, Baz baz) { @Override public boolean equals(Object o) { - return o instanceof Foo that && - Objects.equals(this.bar, that.bar) && - Objects.equals(this.baz, that.baz); + return o instanceof Foo that + && Objects.equals(this.bar, that.bar) + && Objects.equals(this.baz, that.baz); } @Override @@ -158,9 +158,9 @@ public LazySpecifiedFoo(Bar bar, Baz baz) { @Override public boolean equals(Object o) { - return (o instanceof Foo that) && - Objects.equals(this.bar, that.bar) && - Objects.equals(this.baz, that.baz); + return (o instanceof Foo that) + && Objects.equals(this.bar, that.bar) + && Objects.equals(this.baz, that.baz); } @Override From 52e96c521ed345617ec580570eb41dbf475398cd Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 6 May 2025 16:37:50 +0200 Subject: [PATCH 10/24] Fix raw long updater under 32-bit mode --- .../lang/stable/StableFieldUpdater.java | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java index efbaf5369945b..182db05bd94d2 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java @@ -175,16 +175,9 @@ public static ToLongFunction ofLong(Class holderType, Objects.requireNonNull(underlying); final long offset = offset(holderType, fieldName, long.class, Reflection.getCallerClass()); - if (Architecture.is64bit()) { - // We are also relying on the fact that the VM will not place 64-bit - // instance fields that can cross cache lines. - return new StableLongFieldUpdater<>(holderType, offset, underlying, zeroReplacement); - } else { - return new TearingStableLongFieldUpdater<>(holderType, offset, underlying, zeroReplacement); - } + return makeLong(holderType, offset, underlying, zeroReplacement); } - // Only to be used by classes that are used "early" in the init sequence. public static final class Raw { @@ -211,11 +204,23 @@ public static ToLongFunction ofLong(Class holderType, throw new IllegalArgumentException(); } Objects.requireNonNull(underlying); - return new StableLongFieldUpdater<>(holderType, offset, underlying, zeroReplacement); + return makeLong(holderType, offset, underlying, zeroReplacement); } } + private static ToLongFunction makeLong(Class holderType, + long offset, + ToLongFunction underlying, + long zeroReplacement) { + if (Architecture.is64bit()) { + // We are also relying on the fact that the VM will not place 64-bit + // instance fields that can cross cache lines. + return new StableLongFieldUpdater<>(holderType, offset, underlying, zeroReplacement); + } else { + return new TearingStableLongFieldUpdater<>(holderType, offset, underlying, zeroReplacement); + } + } private record StableIntFieldUpdater(Class holderType, long offset, From 83ce9ac1cdf16b2fd3b0f024c594aa214dd4baae Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 7 May 2025 09:43:51 +0200 Subject: [PATCH 11/24] Add a method handle based field updater --- .../lang/stable/StableFieldUpdater.java | 55 +++++++++++++++++++ .../StableValue/StableFieldUpdaterTest.java | 40 ++++++++++++++ 2 files changed, 95 insertions(+) diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java index 182db05bd94d2..02448f1e41053 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java @@ -32,6 +32,10 @@ import jdk.internal.vm.annotation.ForceInline; import sun.reflect.misc.ReflectUtil; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.invoke.VarHandle; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.Objects; @@ -152,6 +156,24 @@ private StableFieldUpdater() {} private static final Unsafe UNSAFE = Unsafe.getUnsafe(); + public static ToIntFunction ofInt(VarHandle accessor, + MethodHandle underlying, + int zeroReplacement) { + Objects.requireNonNull(accessor); + Objects.requireNonNull(underlying); + + if (accessor.varType() != int.class) { + throw new IllegalArgumentException("Illegal accessor: " + accessor); + } + if (underlying.type().returnType() != int.class || underlying.type().parameterCount() != 1) { + throw new IllegalArgumentException("Illegal underlying function: " + underlying); + } + if (!underlying.type().parameterType(0).equals(Object.class)) { + underlying = underlying.asType(underlying.type().changeParameterType(0, Object.class)); + } + return new StableIntFieldUpdaterVarHandle<>(accessor, underlying, zeroReplacement); + } + @CallerSensitive public static ToIntFunction ofInt(Class holderType, String fieldName, @@ -253,6 +275,39 @@ public int applyAsInt(T t) { } } + + private record StableIntFieldUpdaterVarHandle(VarHandle accessor, + MethodHandle underlying, + int zeroReplacement) implements ToIntFunction { + + @ForceInline + @Override + public int applyAsInt(T t) { + // Plain semantics suffice here as we are not dealing with a reference (for + // a reference, the internal state initialization can be reordered with + // other store ops). JLS (24) 17.4 states that 64-bit fields tear under + // plain memory semantics. But, `int` is 32-bit. + int v = (int) accessor.get(t); + if (v == 0) { + synchronized (t) { + v = (int) accessor.getAcquire(t); + if (v == 0) { + try { + v = (int) underlying.invokeExact(t); + } catch (Throwable e) { + throw new RuntimeException(e); + } + if (v == 0) { + v = zeroReplacement; + } + accessor.setRelease(t, v); + } + } + } + return v; + } + } + private record StableLongFieldUpdater(Class holderType, long offset, ToLongFunction underlying, diff --git a/test/jdk/java/lang/StableValue/StableFieldUpdaterTest.java b/test/jdk/java/lang/StableValue/StableFieldUpdaterTest.java index e865cac5a9f2e..2a9dac3bd335f 100644 --- a/test/jdk/java/lang/StableValue/StableFieldUpdaterTest.java +++ b/test/jdk/java/lang/StableValue/StableFieldUpdaterTest.java @@ -24,12 +24,18 @@ /* @test * @summary Basic tests for StableFieldUpdater implementations * @modules java.base/jdk.internal.lang.stable + * @modules java.base/jdk.internal.invoke * @run junit StableFieldUpdaterTest */ +import jdk.internal.invoke.MhUtil; import jdk.internal.lang.stable.StableFieldUpdater; import org.junit.jupiter.api.Test; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.invoke.VarHandle; import java.util.function.ToIntFunction; import static org.junit.jupiter.api.Assertions.*; @@ -55,6 +61,15 @@ void foo() { assertEquals(0, foo.hash); assertEquals(STRING.hashCode(), foo.hashCode()); assertEquals(STRING.hashCode(), foo.hash); + + } + + @Test + void mhFoo() { + var foo = new MhFoo(STRING); + assertEquals(0, foo.hash); + assertEquals(STRING.hashCode(), foo.hashCode()); + assertEquals(STRING.hashCode(), foo.hash); } @Test @@ -129,5 +144,30 @@ public AbstractBar(String string) { } } + static final class MhFoo { + + private static final MethodHandle HASH_MH = MhUtil.findVirtual(MethodHandles.lookup(), "hash0", MethodType.methodType(int.class)); + + private static final ToIntFunction UPDATER = + StableFieldUpdater.ofInt(MhUtil.findVarHandle(MethodHandles.lookup(), "hash", int.class), HASH_MH, ZERO_REPLACEMENT); + private final String string; + + int hash; + long dummy; + + public MhFoo(String string) { + this.string = string; + } + + @Override + public int hashCode() { + return UPDATER.applyAsInt(this); + } + + public int hash0() { + return string.hashCode(); + } + + } } From 0353ff9edd45f0f2837940e0b846ea817fd3b716 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 7 May 2025 11:18:54 +0200 Subject: [PATCH 12/24] Revert changes in public classes --- .../classes/java/lang/reflect/Method.java | 23 +++------- src/java.base/share/classes/java/net/URI.java | 46 ++++++++----------- 2 files changed, 25 insertions(+), 44 deletions(-) diff --git a/src/java.base/share/classes/java/lang/reflect/Method.java b/src/java.base/share/classes/java/lang/reflect/Method.java index 4494591ec5032..c44493d5f76ef 100644 --- a/src/java.base/share/classes/java/lang/reflect/Method.java +++ b/src/java.base/share/classes/java/lang/reflect/Method.java @@ -26,8 +26,6 @@ package java.lang.reflect; import jdk.internal.access.SharedSecrets; -import jdk.internal.lang.stable.StableFieldUpdater; -import jdk.internal.misc.Unsafe; import jdk.internal.misc.VM; import jdk.internal.reflect.CallerSensitive; import jdk.internal.reflect.CallerSensitiveAdapter; @@ -49,7 +47,6 @@ import java.lang.annotation.AnnotationFormatError; import java.nio.ByteBuffer; import java.util.StringJoiner; -import java.util.function.ToIntFunction; /** * A {@code Method} provides information about, and access to, a single method @@ -97,18 +94,6 @@ public final class Method extends Executable { private transient volatile MethodRepository genericInfo; private @Stable MethodAccessor methodAccessor; - private static final ToIntFunction HASH_UPDATER = - StableFieldUpdater.Raw.ofInt(Method.class, Unsafe.getUnsafe().objectFieldOffset(Method.class, "hash"), new ToIntFunction() { - @Override - public int applyAsInt(Method method) { - return method.getDeclaringClass().getName().hashCode() ^ - method.getName().hashCode(); - } - }, 0); // The `hashCode()` is specified so, zeroReplacement is 0 - // End shared states - - // Used reflectively via HASH_UPDATER - @Stable private int hash; // not shared right now, eligible if expensive // Generics infrastructure @@ -375,7 +360,13 @@ public boolean equals(Object obj) { * method's declaring class name and the method's name. */ public int hashCode() { - return HASH_UPDATER.applyAsInt(this); + int hc = hash; + + if (hc == 0) { + hc = hash = getDeclaringClass().getName().hashCode() ^ getName() + .hashCode(); + } + return hc; } /** diff --git a/src/java.base/share/classes/java/net/URI.java b/src/java.base/share/classes/java/net/URI.java index f60ac8b884bdf..2f609801f2378 100644 --- a/src/java.base/share/classes/java/net/URI.java +++ b/src/java.base/share/classes/java/net/URI.java @@ -40,13 +40,9 @@ import java.nio.charset.CharacterCodingException; import java.nio.file.Path; import java.text.Normalizer; -import java.util.function.ToIntFunction; import jdk.internal.access.JavaNetUriAccess; import jdk.internal.access.SharedSecrets; -import jdk.internal.lang.stable.StableFieldUpdater; -import jdk.internal.vm.annotation.ForceInline; -import jdk.internal.vm.annotation.Stable; import sun.nio.cs.UTF_8; /** @@ -549,13 +545,6 @@ public final class URI // The remaining fields may be computed on demand, which is safe even in // the face of multiple threads racing to initialize them private transient String schemeSpecificPart; - - private static final ToIntFunction HASH_UPDATER = StableFieldUpdater.ofInt( - URI.class, "hash", new ToIntFunction<>() { - @ForceInline @Override public int applyAsInt(URI uri) { return uri.hashCode0(); }}, -1); - - // Used reflectively by HASH_UPDATER - @Stable private transient int hash; // Zero ==> undefined private transient String decodedUserInfo; @@ -1587,24 +1576,25 @@ public boolean equals(Object ob) { * @return A hash-code value for this URI */ public int hashCode() { - return HASH_UPDATER.applyAsInt(this); - } - - private int hashCode0() { - int h = 0; - h = hashIgnoringCase(0, scheme); - h = hash(h, fragment); - if (isOpaque()) { - h = hash(h, schemeSpecificPart); - } else { - h = hash(h, path); - h = hash(h, query); - if (host != null) { - h = hash(h, userInfo); - h = hashIgnoringCase(h, host); - h += 1949 * port; + int h = hash; + if (h == 0) { + h = hashIgnoringCase(0, scheme); + h = hash(h, fragment); + if (isOpaque()) { + h = hash(h, schemeSpecificPart); } else { - h = hash(h, authority); + h = hash(h, path); + h = hash(h, query); + if (host != null) { + h = hash(h, userInfo); + h = hashIgnoringCase(h, host); + h += 1949 * port; + } else { + h = hash(h, authority); + } + } + if (h != 0) { + hash = h; } } return h; From 4a42b271af522a5993a961bbd385a7684058cf60 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 7 May 2025 11:20:31 +0200 Subject: [PATCH 13/24] Reformat --- src/java.base/share/classes/java/lang/reflect/Method.java | 4 ++-- src/java.base/share/classes/java/net/URI.java | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/java.base/share/classes/java/lang/reflect/Method.java b/src/java.base/share/classes/java/lang/reflect/Method.java index c44493d5f76ef..07616206075e7 100644 --- a/src/java.base/share/classes/java/lang/reflect/Method.java +++ b/src/java.base/share/classes/java/lang/reflect/Method.java @@ -93,7 +93,7 @@ public final class Method extends Executable { private Method root; private transient volatile MethodRepository genericInfo; private @Stable MethodAccessor methodAccessor; - + // End shared states private int hash; // not shared right now, eligible if expensive // Generics infrastructure @@ -364,7 +364,7 @@ public int hashCode() { if (hc == 0) { hc = hash = getDeclaringClass().getName().hashCode() ^ getName() - .hashCode(); + .hashCode(); } return hc; } diff --git a/src/java.base/share/classes/java/net/URI.java b/src/java.base/share/classes/java/net/URI.java index 2f609801f2378..cd116f3877bcb 100644 --- a/src/java.base/share/classes/java/net/URI.java +++ b/src/java.base/share/classes/java/net/URI.java @@ -40,7 +40,6 @@ import java.nio.charset.CharacterCodingException; import java.nio.file.Path; import java.text.Normalizer; - import jdk.internal.access.JavaNetUriAccess; import jdk.internal.access.SharedSecrets; import sun.nio.cs.UTF_8; From 6342fbd8361c9e2840f7ca30f08a48a8fe0a6403 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 7 May 2025 15:50:18 +0200 Subject: [PATCH 14/24] Add low level variants --- .../lang/stable/StableFieldUpdater.java | 210 +++++++++++++++--- .../StableFieldUpdaterExampleTest.java | 52 ++++- .../StableValue/StableFieldUpdaterTest.java | 140 +++++++++--- ....java => StableFieldUpdaterBenchmark.java} | 81 ++++++- 4 files changed, 413 insertions(+), 70 deletions(-) rename test/micro/org/openjdk/bench/java/lang/stable/{StableUpdatersBenchmark.java => StableFieldUpdaterBenchmark.java} (59%) diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java index 02448f1e41053..3f4e0e4f6059a 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java @@ -33,8 +33,6 @@ import sun.reflect.misc.ReflectUtil; import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; import java.lang.invoke.VarHandle; import java.lang.reflect.Field; import java.lang.reflect.Modifier; @@ -49,8 +47,8 @@ * {@link Object#hashCode()}. *

* Here is an example of how to convert - * - * {@snippet lang=java: + *

+ * {@snippet lang = java: * public final class Foo { * * private final Bar bar; @@ -73,10 +71,9 @@ * return Objects.hash(bar, baz); * } * } - * } - * to use {@code @Stable} lazy hashing: - * - * {@snippet lang=java: + *} to use {@code @Stable} lazy hashing: + *

+ * {@snippet lang = java: * public final class LazyFoo { * * private final Bar bar; @@ -106,14 +103,17 @@ * return HASH_UPDATER.applyAsInt(this); * } * } - * } + *} + *

+ * If the underlying hash lamba returns zero, it is replaced with {@code -1}. It is legal + * to provide {@code 0} as a replacement in which case there will be no replacement and + * the hash code will be {@code 0}. In such cases, {@link @Stable} fields cannot be + * constant-folded. *

- * If the underlying hash lamba returns zero, it is replaced with {@code -1}. - * * In cases where the entire range of hash codes are strictly specified (as it is for - * {@code String}), a {@code long} field can be used instead, and then we can use - * {@code 1 << 32} as a token for zero (as the lower 32 bits are zero) and then just - * cast to an {@code int} as shown in this example: + * {@code String}), a {@code long} field can be used instead, and a value of + * {@code 1 << 32} can be used as a token for zero (as the lower 32 bits are zero) and + * then just cast to an {@code int} as shown in this example: * * {@snippet lang=java: public final class LazySpecifiedFoo { @@ -123,7 +123,7 @@ public final class LazySpecifiedFoo { private static final ToLongFunction HASH_UPDATER = StableFieldUpdater.ofLong(LazySpecifiedFoo.class, "hash", - l -> Objects.hash(l.bar, l.baz), 1L << 32); + LazySpecifiedFoo::hashCodeFor, 1L << 32); @Stable private long hash; @@ -144,9 +144,59 @@ public boolean equals(Object o) { public int hashCode() { return (int) HASH_UPDATER.applyAsLong(this); } + + static long hashCodeFor(LazySpecifiedFoo foo) { + return Objects.hash(foo.bar, foo.baz); + } } * } + * The example above also features a static method {@code hashCodeFor()} that acts as + * the underlying hash function. This method can reside in another class. *

+ * Here is another example where a more low-level approach with VarHandle and MethodHandle + * parameters is used: + * + * {@snippet lang=java: + public final class MhFoo { + + private final Bar bar; + private final Baz baz; + + private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); + + private static final ToIntFunction HASH_UPDATER = + StableFieldUpdater.ofInt( + MhUtil.findVarHandle(LOOKUP, "hash", int.class), + MhUtil.findStatic(LOOKUP, "hashCodeFor", MethodType.methodType(int.class, MhFoo.class)), + -1); + + @Stable + private int hash; + + public MhFoo(Bar bar, Baz baz) { + this.bar = bar; + this.baz = baz; + } + + @Override + public boolean equals(Object o) { + return o instanceof Foo that + && Objects.equals(this.bar, that.bar) + && Objects.equals(this.baz, that.baz); + } + + @Override + public int hashCode() { + return HASH_UPDATER.applyAsInt(this); + } + + // Used reflectively + static int hashCodeFor(MhFoo foo) { + return Objects.hash(foo.bar, foo.baz); + } + } + * } + * * The provided {@code underlying} function must not recurse or the result of the * operation is unspecified. */ @@ -159,19 +209,11 @@ private StableFieldUpdater() {} public static ToIntFunction ofInt(VarHandle accessor, MethodHandle underlying, int zeroReplacement) { - Objects.requireNonNull(accessor); + check(accessor, int.class); Objects.requireNonNull(underlying); + var adaptedUnderlying = checkAndAdapt(underlying, int.class); - if (accessor.varType() != int.class) { - throw new IllegalArgumentException("Illegal accessor: " + accessor); - } - if (underlying.type().returnType() != int.class || underlying.type().parameterCount() != 1) { - throw new IllegalArgumentException("Illegal underlying function: " + underlying); - } - if (!underlying.type().parameterType(0).equals(Object.class)) { - underlying = underlying.asType(underlying.type().changeParameterType(0, Object.class)); - } - return new StableIntFieldUpdaterVarHandle<>(accessor, underlying, zeroReplacement); + return new StableIntFieldUpdaterVarHandle<>(accessor, adaptedUnderlying, zeroReplacement); } @CallerSensitive @@ -187,6 +229,16 @@ public static ToIntFunction ofInt(Class holderType, return new StableIntFieldUpdater<>(holderType, offset, underlying, zeroReplacement); } + public static ToLongFunction ofLong(VarHandle accessor, + MethodHandle underlying, + long zeroReplacement) { + check(accessor, long.class); + Objects.requireNonNull(underlying); + var adaptedUnderlying = checkAndAdapt(underlying, long.class); + + return makeLong(accessor, adaptedUnderlying, zeroReplacement); + } + @CallerSensitive public static ToLongFunction ofLong(Class holderType, String fieldName, @@ -244,6 +296,18 @@ private static ToLongFunction makeLong(Class holderType, } } + private static ToLongFunction makeLong(VarHandle accessor, + MethodHandle underlying, + long zeroReplacement) { + if (Architecture.is64bit()) { + // We are also relying on the fact that the VM will not place 64-bit + // instance fields that can cross cache lines. + return new StableLongFieldUpdaterVarHandle<>(accessor, underlying, zeroReplacement); + } else { + return new TearingStableLongFieldUpdaterVarHandle<>(accessor, underlying, zeroReplacement); + } + } + private record StableIntFieldUpdater(Class holderType, long offset, ToIntFunction underlying, @@ -252,14 +316,13 @@ private record StableIntFieldUpdater(Class holderType, @ForceInline @Override public int applyAsInt(T t) { - checkType(holderType, t); + checkInstanceOf(holderType, t); // Plain semantics suffice here as we are not dealing with a reference (for // a reference, the internal state initialization can be reordered with // other store ops). JLS (24) 17.4 states that 64-bit fields tear under // plain memory semantics. But, `int` is 32-bit. int v = UNSAFE.getInt(t, offset); if (v == 0) { - // StableUtil.preventReentry(t); synchronized (t) { v = UNSAFE.getIntAcquire(t, offset); if (v == 0) { @@ -275,7 +338,6 @@ public int applyAsInt(T t) { } } - private record StableIntFieldUpdaterVarHandle(VarHandle accessor, MethodHandle underlying, int zeroReplacement) implements ToIntFunction { @@ -316,14 +378,13 @@ private record StableLongFieldUpdater(Class holderType, @ForceInline @Override public long applyAsLong(T t) { - checkType(holderType, t); + checkInstanceOf(holderType, t); // Plain semantics suffice here as we are not dealing with a reference (for // a reference, the internal state initialization can be reordered with // other store ops). JLS (24) 17.4 states that 64-bit fields tear under // plain memory semantics. But, the VM is 64-bit. long v = UNSAFE.getLong(t, offset); if (v == 0) { - // StableUtil.preventReentry(t); synchronized (t) { v = UNSAFE.getLongAcquire(t, offset); if (v == 0) { @@ -347,14 +408,13 @@ private record TearingStableLongFieldUpdater(Class holderType, @ForceInline @Override public long applyAsLong(T t) { - checkType(holderType, t); + checkInstanceOf(holderType, t); // Plain semantics suffice here as we are not dealing with a reference (for // a reference, the internal state initialization can be reordered with // other store ops). JLS (24) 17.4 states that 64-bit fields tear under // plain memory semantics and this VM is not 64-bit. long v = UNSAFE.getLongOpaque(t, offset); if (v == 0) { - // StableUtil.preventReentry(t); synchronized (t) { v = UNSAFE.getLongAcquire(t, offset); if (v == 0) { @@ -370,15 +430,97 @@ public long applyAsLong(T t) { } } + private record StableLongFieldUpdaterVarHandle(VarHandle accessor, + MethodHandle underlying, + long zeroReplacement) implements ToLongFunction { + + @ForceInline + @Override + public long applyAsLong(T t) { + // Plain semantics suffice here as we are not dealing with a reference (for + // a reference, the internal state initialization can be reordered with + // other store ops). JLS (24) 17.4 states that 64-bit fields tear under + // plain memory semantics. But, the VM is 64-bit. + long v = (long) accessor.get(t); + if (v == 0) { + synchronized (t) { + v = (long) accessor.getAcquire(t); + if (v == 0) { + try { + v = (long) underlying.invokeExact(t); + } catch (Throwable e) { + throw new RuntimeException(e); + } + if (v == 0) { + v = zeroReplacement; + } + accessor.setRelease(t, v); + } + } + } + return v; + } + } + + private record TearingStableLongFieldUpdaterVarHandle(VarHandle accessor, + MethodHandle underlying, + long zeroReplacement) implements ToLongFunction { + + @ForceInline + @Override + public long applyAsLong(T t) { + // Plain semantics suffice here as we are not dealing with a reference (for + // a reference, the internal state initialization can be reordered with + // other store ops). JLS (24) 17.4 states that 64-bit fields tear under + // plain memory semantics and this VM is not 64-bit. + long v = (long) accessor.getOpaque(t); + if (v == 0) { + synchronized (t) { + v = (long) accessor.getAcquire(t); + if (v == 0) { + try { + v = (long) underlying.invokeExact(t); + } catch (Throwable e) { + throw new RuntimeException(e); + } + if (v == 0) { + v = zeroReplacement; + } + accessor.setRelease(t, v); + } + } + } + return v; + } + } + // Static support functions @ForceInline - private static void checkType(Class holderType, Object t) { + private static void checkInstanceOf(Class holderType, Object t) { if (!holderType.isInstance(t)) { throw new IllegalArgumentException("The provided t is not an instance of " + holderType); } } + private static void check(VarHandle accessor, Class varType) { + // Implicit null check + if (accessor.varType() != varType) { + throw new IllegalArgumentException("Illegal accessor: " + accessor); + } + } + + private static MethodHandle checkAndAdapt(MethodHandle underlying, Class returnType) { + final var underlyingType = underlying.type(); + if (underlyingType.returnType() != returnType || underlyingType.parameterCount() != 1) { + throw new IllegalArgumentException("Illegal underlying function: " + underlying); + } + if (!underlyingType.parameterType(0).equals(Object.class)) { + underlying = underlying.asType(underlyingType.changeParameterType(0, Object.class)); + } + return underlying; + } + private static long offset(Class holderType, String fieldName, Class fieldType, diff --git a/test/jdk/java/lang/StableValue/StableFieldUpdaterExampleTest.java b/test/jdk/java/lang/StableValue/StableFieldUpdaterExampleTest.java index 99f6b0ace12a5..d7c3df8d1a956 100644 --- a/test/jdk/java/lang/StableValue/StableFieldUpdaterExampleTest.java +++ b/test/jdk/java/lang/StableValue/StableFieldUpdaterExampleTest.java @@ -24,13 +24,17 @@ /* @test * @summary Basic tests for StableFieldUpdaterExampleTest examples in javadoc * @modules java.base/jdk.internal.lang.stable + * @modules java.base/jdk.internal.invoke * @run junit StableFieldUpdaterExampleTest */ +import jdk.internal.invoke.MhUtil; import jdk.internal.lang.stable.StableFieldUpdater; import org.junit.jupiter.api.Test; import java.lang.Override; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; import java.util.Objects; import java.util.function.ToIntFunction; import java.util.function.ToLongFunction; @@ -146,7 +150,7 @@ public final class LazySpecifiedFoo { private static final ToLongFunction HASH_UPDATER = StableFieldUpdater.ofLong(LazySpecifiedFoo.class, "hash", - l -> (l.bar == null && l.baz == null) ? 0 : Objects.hash(l.bar, l.baz), 1L << 32); + LazySpecifiedFoo::hashCodeFor, 1L << 32); @Stable private long hash; @@ -167,6 +171,52 @@ public boolean equals(Object o) { public int hashCode() { return (int) HASH_UPDATER.applyAsLong(this); } + + static long hashCodeFor(LazySpecifiedFoo foo) { + return (foo.bar == null && foo.baz == null) ? 0 : Objects.hash(foo.bar, foo.baz); + } + + } + + static + + public final class MhFoo { + + private final Bar bar; + private final Baz baz; + + private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); + + private static final ToIntFunction HASH_UPDATER = + StableFieldUpdater.ofInt( + MhUtil.findVarHandle(LOOKUP, "hash", int.class), + MhUtil.findStatic(LOOKUP, "hashCodeFor", MethodType.methodType(int.class, MhFoo.class)), + -1); + + @Stable + private int hash; + + public MhFoo(Bar bar, Baz baz) { + this.bar = bar; + this.baz = baz; + } + + @Override + public boolean equals(Object o) { + return o instanceof Foo that + && Objects.equals(this.bar, that.bar) + && Objects.equals(this.baz, that.baz); + } + + @Override + public int hashCode() { + return HASH_UPDATER.applyAsInt(this); + } + + // Used reflectively + static int hashCodeFor(MhFoo foo) { + return Objects.hash(foo.bar, foo.baz); + } } @Test diff --git a/test/jdk/java/lang/StableValue/StableFieldUpdaterTest.java b/test/jdk/java/lang/StableValue/StableFieldUpdaterTest.java index 2a9dac3bd335f..3d48cf880078f 100644 --- a/test/jdk/java/lang/StableValue/StableFieldUpdaterTest.java +++ b/test/jdk/java/lang/StableValue/StableFieldUpdaterTest.java @@ -31,12 +31,16 @@ import jdk.internal.invoke.MhUtil; import jdk.internal.lang.stable.StableFieldUpdater; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; -import java.lang.invoke.VarHandle; +import java.util.function.Function; import java.util.function.ToIntFunction; +import java.util.function.ToLongFunction; +import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.*; @@ -44,7 +48,6 @@ final class StableFieldUpdaterTest { private static final int ZERO_REPLACEMENT = 42; private static final String STRING = "Abc"; - private static final ToIntFunction STRING_HASH_CODE = String::hashCode; @Test void invariants() { @@ -55,21 +58,14 @@ void invariants() { assertEquals("Only fields of type 'int' are supported. The provided field is 'long StableFieldUpdaterTest$Foo.dummy'", x.getMessage()); } - @Test - void foo() { - var foo = new Foo(STRING); - assertEquals(0, foo.hash); - assertEquals(STRING.hashCode(), foo.hashCode()); - assertEquals(STRING.hashCode(), foo.hash); - - } - - @Test - void mhFoo() { - var foo = new MhFoo(STRING); - assertEquals(0, foo.hash); - assertEquals(STRING.hashCode(), foo.hashCode()); - assertEquals(STRING.hashCode(), foo.hash); + @ParameterizedTest + @MethodSource("fooConstructors") + void basic(Function namedConstructor) { + final HasHashField foo = namedConstructor.apply(STRING); + assertEquals(0L, foo.hash()); + int actual = foo.hashCode(); + assertEquals(STRING.hashCode(), actual); + assertEquals(actual, foo.hash()); } @Test @@ -81,15 +77,6 @@ void recordFoo() { assertEquals("Only non final fields are supported. The provided field is 'private final int StableFieldUpdaterTest$RecordFoo.hash'", x.getMessage()); } - @Test - void barInherit() { - var bar = new Bar(STRING); - assertEquals(0, bar.hash); - assertEquals(STRING.hashCode(), bar.hashCode()); - assertEquals(STRING.hashCode(), bar.hash); - } - - @SuppressWarnings({"rawtypes", "unchecked"}) @Test void uncheckedCall() { @@ -100,7 +87,7 @@ void uncheckedCall() { assertEquals("The provided t is not an instance of class StableFieldUpdaterTest$Foo", x.getMessage()); } - static final class Foo { + static final class Foo implements HasHashField { private static final ToIntFunction UPDATER = StableFieldUpdater.ofInt(Foo.class, "hash", f -> f.string.hashCode(), ZERO_REPLACEMENT); @@ -117,15 +104,43 @@ public int hashCode() { return UPDATER.applyAsInt(this); } + @Override + public long hash() { + return hash; + } + } + + static final class LongFoo implements HasHashField { + + private static final ToLongFunction UPDATER = + StableFieldUpdater.ofLong(LongFoo.class, "hash", f -> f.string.hashCode(), ZERO_REPLACEMENT); + private final String string; + + long hash; + long dummy; + + public LongFoo(String string) { + this.string = string; + } + @Override + public int hashCode() { + return (int)UPDATER.applyAsLong(this); + } + + @Override + public long hash() { + return hash; + } + } record RecordFoo(String string, int hash) {} - static final class Bar extends AbstractBar { - private static final ToIntFunction UPDATER = - StableFieldUpdater.ofInt(Bar.class, "hash", f -> f.string.hashCode(), ZERO_REPLACEMENT); + static final class InheritingFoo extends AbstractFoo implements HasHashField { + private static final ToIntFunction UPDATER = + StableFieldUpdater.ofInt(InheritingFoo.class, "hash", f -> f.string.hashCode(), ZERO_REPLACEMENT); - public Bar(String string) { + public InheritingFoo(String string) { super(string); } @@ -135,16 +150,21 @@ public int hashCode() { } } - static abstract class AbstractBar { + static abstract class AbstractFoo implements HasHashField { final String string; int hash; - public AbstractBar(String string) { + public AbstractFoo(String string) { this.string = string; } + + @Override + public long hash() { + return hash; + } } - static final class MhFoo { + static final class MhFoo implements HasHashField { private static final MethodHandle HASH_MH = MhUtil.findVirtual(MethodHandles.lookup(), "hash0", MethodType.methodType(int.class)); @@ -168,6 +188,58 @@ public int hash0() { return string.hashCode(); } + @Override + public long hash() { + return hash; + } + + } + + static final class LongMhFoo implements HasHashField { + + private static final MethodHandle HASH_MH = MhUtil.findVirtual(MethodHandles.lookup(), "hash0", MethodType.methodType(long.class)); + + private static final ToLongFunction UPDATER = + StableFieldUpdater.ofLong(MhUtil.findVarHandle(MethodHandles.lookup(), "hash", long.class), HASH_MH, ZERO_REPLACEMENT); + private final String string; + + long hash; + long dummy; + + public LongMhFoo(String string) { + this.string = string; + } + + @Override + public int hashCode() { + return (int)UPDATER.applyAsLong(this); + } + + public long hash0() { + return string.hashCode(); + } + + @Override + public long hash() { + return hash; + } + + } + + interface HasHashField { + long hash(); + } + + // Apparently, `hashCode()` is invoked if we create a stream of just `HasHashField` + // instances so we provide the associated constructors instead. + static Stream> fooConstructors() { + return Stream.of( + Foo::new, + LongFoo::new, + MhFoo::new, + LongMhFoo::new, + InheritingFoo::new + ); } } diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableUpdatersBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableFieldUpdaterBenchmark.java similarity index 59% rename from test/micro/org/openjdk/bench/java/lang/stable/StableUpdatersBenchmark.java rename to test/micro/org/openjdk/bench/java/lang/stable/StableFieldUpdaterBenchmark.java index dc6fe5824510d..6a38d788a8e19 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/StableUpdatersBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableFieldUpdaterBenchmark.java @@ -35,6 +35,10 @@ import org.openjdk.jmh.annotations.Threads; import org.openjdk.jmh.annotations.Warmup; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.invoke.VarHandle; import java.net.URI; import java.util.concurrent.TimeUnit; import java.util.function.ToIntFunction; @@ -49,16 +53,18 @@ @Measurement(iterations = 5, time = 2) @Fork(value = 2, jvmArgs = {"--enable-preview", "--add-exports", "java.base/jdk.internal.lang.stable=ALL-UNNAMED"}) @Threads(Threads.MAX) // Benchmark under contention -public class StableUpdatersBenchmark { +public class StableFieldUpdaterBenchmark { private static final String STRING = "https://some.site.com"; private static final Base BASE = new Base(STRING); private static final Updater UPDATER = new Updater(STRING); + private static final MhUpdater MH_UPDATER = new MhUpdater(STRING); private static final URI U_R_I = URI.create(STRING); private final Base base = new Base(STRING); private final Updater updater = new Updater(STRING); + private final MhUpdater mhUpdater = new MhUpdater(STRING); private static final URI uri = URI.create(STRING); @Benchmark @@ -71,6 +77,16 @@ public int base() { return base.hashCode(); } + @Benchmark + public int mhUpdaterStatic() { + return MH_UPDATER.hashCode(); + } + + @Benchmark + public int mhUpdater() { + return mhUpdater.hashCode(); + } + @Benchmark public int updaterStatic() { return UPDATER.hashCode(); @@ -122,6 +138,28 @@ public int hashCode() { } } + static final class MhUpdater extends Abstract { + + private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); + private static final ToIntFunction HASH_CODE_UPDATER = + StableFieldUpdater.ofInt( + MhUtil.findVarHandle(LOOKUP, "hashCode", int.class), + MhUtil.findVirtual(LOOKUP, "hashCode0", MethodType.methodType(int.class)), + -1); + + MhUpdater(String string) { super(string); } + + @Override + public int hashCode() { + return HASH_CODE_UPDATER.applyAsInt(this); + } + + private int hashCode0() { + return string.hashCode(); + } + + } + static class Abstract { final String string; @@ -132,4 +170,45 @@ static class Abstract { } } + + // From j.i.i.MhUtil + + private static final class MhUtil { + + public static VarHandle findVarHandle(MethodHandles.Lookup lookup, + String name, + Class type) { + return findVarHandle(lookup, lookup.lookupClass(), name, type); + } + + public static VarHandle findVarHandle(MethodHandles.Lookup lookup, + Class recv, + String name, + Class type) { + try { + return lookup.findVarHandle(recv, name, type); + } catch (ReflectiveOperationException e) { + throw new InternalError(e); + } + } + + + public static MethodHandle findVirtual(MethodHandles.Lookup lookup, + String name, + MethodType type) { + return findVirtual(lookup, lookup.lookupClass(), name, type); + } + + public static MethodHandle findVirtual(MethodHandles.Lookup lookup, + Class refc, + String name, + MethodType type) { + try { + return lookup.findVirtual(refc, name, type); + } catch (ReflectiveOperationException e) { + throw new InternalError(e); + } + } + } + } From a01ba9ab3d3c73250c474473c7032b02ccb877c3 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 7 May 2025 16:53:56 +0200 Subject: [PATCH 15/24] Add convenience methods and documentations --- .../lang/stable/StableFieldUpdater.java | 74 +++++++++++++++++++ .../StableValue/StableFieldUpdaterTest.java | 34 ++++++++- 2 files changed, 107 insertions(+), 1 deletion(-) diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java index 3f4e0e4f6059a..da70b94573eba 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java @@ -25,6 +25,7 @@ package jdk.internal.lang.stable; +import jdk.internal.invoke.MhUtil; import jdk.internal.misc.Unsafe; import jdk.internal.reflect.CallerSensitive; import jdk.internal.reflect.Reflection; @@ -33,6 +34,8 @@ import sun.reflect.misc.ReflectUtil; import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; import java.lang.invoke.VarHandle; import java.lang.reflect.Field; import java.lang.reflect.Modifier; @@ -196,6 +199,15 @@ static int hashCodeFor(MhFoo foo) { } } * } + * There is a convenience method for this idiomatic case that looks like this: + * {@snippet lang=java: + + private static final ToIntFunction HASH_UPDATER = + StableFieldUpdater.ofInt(MethodHandles.lookup(), "hash", "hashCodeFor")); + * } + * This will use the provided {@link MethodHandles#lookup()} to look up the field + * {@code hash} and also use the same lookup to look up a static method that takes + * a {@code MhFoo} and returns an {@code int}. * * The provided {@code underlying} function must not recurse or the result of the * operation is unspecified. @@ -206,6 +218,37 @@ private StableFieldUpdater() {} private static final Unsafe UNSAFE = Unsafe.getUnsafe(); + /** + * {@return a function that lazily sets the field named {@code fieldName} by invoking + * the static method in the {@link MethodHandles.Lookup#lookupClass()} + * named {@code staticUnderlyingMethodName} if the field was not previously + * set. Otherwise, the function returns the set field value} + *

+ * If the {@code staticUnderlyingMethodName} returns zero, the value is replaced with + * {@code -1} which is then stored in the field with the provided {@code fieldName}. + * + * @param lookup used to reflectively lookup entities + * @param fieldName the name of the lazily set field (of type {@code int}) + * @param staticUnderlyingMethodName the name of the method to invoke when computing + * the hash value (invoked at most once) + * @param target type + * @throws NullPointerException if any of the provided parameters are {@code null} + * @throws IllegalArgumentException if no {@code int} field can be found using the + * provided {@code lookup} and {@code fieldName} + * @throws IllegalArgumentException if no {@code int} function can be found using the + * provided {@code lookup} and + * {@code staticUnderlyingMethodName} where the + * single method parameter is of type {@code T}. + */ + public static ToIntFunction ofInt(MethodHandles.Lookup lookup, + String fieldName, + String staticUnderlyingMethodName) { + return ofInt( + MhUtil.findVarHandle(lookup, fieldName, int.class), + MhUtil.findStatic(lookup, staticUnderlyingMethodName, MethodType.methodType(int.class, lookup.lookupClass())), + -1); + } + public static ToIntFunction ofInt(VarHandle accessor, MethodHandle underlying, int zeroReplacement) { @@ -229,6 +272,37 @@ public static ToIntFunction ofInt(Class holderType, return new StableIntFieldUpdater<>(holderType, offset, underlying, zeroReplacement); } + /** + * {@return a function that lazily sets the field named {@code fieldName} by invoking + * the static method in the {@link MethodHandles.Lookup#lookupClass()} + * named {@code staticUnderlyingMethodName} if the field was not previously + * set. Otherwise, the function returns the set field value} + *

+ * If the {@code staticUnderlyingMethodName} returns zero, the value is replaced with + * {@code 1L<<32} which is then stored in the field with the provided {@code fieldName}. + * + * @param lookup used to reflectively lookup entities + * @param fieldName the name of the lazily set field (of type {@code long}) + * @param staticUnderlyingMethodName the name of the method to invoke when computing + * the hash value (invoked at most once) + * @param target type + * @throws NullPointerException if any of the provided parameters are {@code null} + * @throws IllegalArgumentException if no {@code int} field can be found using the + * provided {@code lookup} and {@code fieldName} + * @throws IllegalArgumentException if no {@code long} function can be found using the + * provided {@code lookup} and + * {@code staticUnderlyingMethodName} where the + * single method parameter is of type {@code T}. + */ + public static ToLongFunction ofLong(MethodHandles.Lookup lookup, + String fieldName, + String staticUnderlyingMethodName) { + return ofLong( + MhUtil.findVarHandle(lookup, fieldName, long.class), + MhUtil.findStatic(lookup, staticUnderlyingMethodName, MethodType.methodType(long.class, lookup.lookupClass())), + 1L << 31); + } + public static ToLongFunction ofLong(VarHandle accessor, MethodHandle underlying, long zeroReplacement) { diff --git a/test/jdk/java/lang/StableValue/StableFieldUpdaterTest.java b/test/jdk/java/lang/StableValue/StableFieldUpdaterTest.java index 3d48cf880078f..d6455101079fc 100644 --- a/test/jdk/java/lang/StableValue/StableFieldUpdaterTest.java +++ b/test/jdk/java/lang/StableValue/StableFieldUpdaterTest.java @@ -195,6 +195,37 @@ public long hash() { } + static final class SimpleMhFoo implements HasHashField { + + private static final ToIntFunction UPDATER = + StableFieldUpdater.ofInt(MethodHandles.lookup(), "hash", "computeHash"); + + private final String string; + + int hash; + long dummy; + + public SimpleMhFoo(String string) { + this.string = string; + } + + @Override + public int hashCode() { + return UPDATER.applyAsInt(this); + } + + @Override + public long hash() { + return hash; + } + + // Used reflectively + private static int computeHash(SimpleMhFoo target) { + return target.string.hashCode(); + } + + } + static final class LongMhFoo implements HasHashField { private static final MethodHandle HASH_MH = MhUtil.findVirtual(MethodHandles.lookup(), "hash0", MethodType.methodType(long.class)); @@ -238,7 +269,8 @@ static Stream> fooConstructors() { LongFoo::new, MhFoo::new, LongMhFoo::new, - InheritingFoo::new + InheritingFoo::new, + SimpleMhFoo::new ); } From de8e2387b77b18b6291c4ca4d6d96951dff7fe07 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 7 May 2025 16:59:02 +0200 Subject: [PATCH 16/24] Reformat --- .../jdk/java/lang/StableValue/StableFieldUpdaterTest.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test/jdk/java/lang/StableValue/StableFieldUpdaterTest.java b/test/jdk/java/lang/StableValue/StableFieldUpdaterTest.java index d6455101079fc..57a277f628c54 100644 --- a/test/jdk/java/lang/StableValue/StableFieldUpdaterTest.java +++ b/test/jdk/java/lang/StableValue/StableFieldUpdaterTest.java @@ -99,6 +99,7 @@ static final class Foo implements HasHashField { public Foo(String string) { this.string = string; } + @Override public int hashCode() { return UPDATER.applyAsInt(this); @@ -122,9 +123,10 @@ static final class LongFoo implements HasHashField { public LongFoo(String string) { this.string = string; } + @Override public int hashCode() { - return (int)UPDATER.applyAsLong(this); + return (int) UPDATER.applyAsLong(this); } @Override @@ -199,7 +201,7 @@ static final class SimpleMhFoo implements HasHashField { private static final ToIntFunction UPDATER = StableFieldUpdater.ofInt(MethodHandles.lookup(), "hash", "computeHash"); - + private final String string; int hash; @@ -243,7 +245,7 @@ public LongMhFoo(String string) { @Override public int hashCode() { - return (int)UPDATER.applyAsLong(this); + return (int) UPDATER.applyAsLong(this); } public long hash0() { From e2a2d7bde4bfe73b8f31ffbb6960d4dab635b491 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Thu, 8 May 2025 09:12:23 +0200 Subject: [PATCH 17/24] Add lazy CallSite methods --- .../lang/stable/StableFieldUpdater.java | 30 ++++++++++++++-- .../StableValue/StableFieldUpdaterTest.java | 34 +++++++++++++++++++ 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java index da70b94573eba..8d1d2328450da 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java @@ -33,6 +33,8 @@ import jdk.internal.vm.annotation.ForceInline; import sun.reflect.misc.ReflectUtil; +import java.lang.invoke.CallSite; +import java.lang.invoke.ConstantCallSite; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; @@ -217,6 +219,7 @@ public final class StableFieldUpdater { private StableFieldUpdater() {} private static final Unsafe UNSAFE = Unsafe.getUnsafe(); + private static final MethodHandles.Lookup LOCAL_LOOKUP = MethodHandles.lookup(); /** * {@return a function that lazily sets the field named {@code fieldName} by invoking @@ -253,7 +256,6 @@ public static ToIntFunction ofInt(VarHandle accessor, MethodHandle underlying, int zeroReplacement) { check(accessor, int.class); - Objects.requireNonNull(underlying); var adaptedUnderlying = checkAndAdapt(underlying, int.class); return new StableIntFieldUpdaterVarHandle<>(accessor, adaptedUnderlying, zeroReplacement); @@ -307,7 +309,6 @@ public static ToLongFunction ofLong(VarHandle accessor, MethodHandle underlying, long zeroReplacement) { check(accessor, long.class); - Objects.requireNonNull(underlying); var adaptedUnderlying = checkAndAdapt(underlying, long.class); return makeLong(accessor, adaptedUnderlying, zeroReplacement); @@ -326,6 +327,30 @@ public static ToLongFunction ofLong(Class holderType, return makeLong(holderType, offset, underlying, zeroReplacement); } + public static CallSite lazyOfInt(MethodHandles.Lookup lookup, + String unused, + VarHandle accessor, + MethodHandle underlying, + int zeroReplacement) { + check(accessor, int.class); + var adaptedUnderlying = checkAndAdapt(underlying, int.class); + var handle = MhUtil.findStatic(LOCAL_LOOKUP, + "ofInt", MethodType.methodType(ToIntFunction.class, VarHandle.class, MethodHandle.class, int.class)); + return new ConstantCallSite(MethodHandles.insertArguments(handle, 0, accessor, adaptedUnderlying, zeroReplacement)); + } + + public static CallSite lazyOfLong(MethodHandles.Lookup lookup, + String unused, + VarHandle accessor, + MethodHandle underlying, + long zeroReplacement) { + check(accessor, long.class); + var adaptedUnderlying = checkAndAdapt(underlying, long.class); + var handle = MhUtil.findStatic(LOCAL_LOOKUP, + "makeLong", MethodType.methodType(ToLongFunction.class, VarHandle.class, MethodHandle.class, long.class)); + return new ConstantCallSite(MethodHandles.insertArguments(handle, 0, accessor, adaptedUnderlying, zeroReplacement)); + } + // Only to be used by classes that are used "early" in the init sequence. public static final class Raw { @@ -585,6 +610,7 @@ private static void check(VarHandle accessor, Class varType) { } private static MethodHandle checkAndAdapt(MethodHandle underlying, Class returnType) { + // Implicit null check final var underlyingType = underlying.type(); if (underlyingType.returnType() != returnType || underlyingType.parameterCount() != 1) { throw new IllegalArgumentException("Illegal underlying function: " + underlying); diff --git a/test/jdk/java/lang/StableValue/StableFieldUpdaterTest.java b/test/jdk/java/lang/StableValue/StableFieldUpdaterTest.java index 57a277f628c54..46d447e891373 100644 --- a/test/jdk/java/lang/StableValue/StableFieldUpdaterTest.java +++ b/test/jdk/java/lang/StableValue/StableFieldUpdaterTest.java @@ -34,6 +34,8 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; +import java.lang.invoke.CallSite; +import java.lang.invoke.ConstantCallSite; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; @@ -87,6 +89,38 @@ void uncheckedCall() { assertEquals("The provided t is not an instance of class StableFieldUpdaterTest$Foo", x.getMessage()); } + @Test + void lazyOfInt() throws Throwable { + var lookup = MethodHandles.lookup(); + CallSite callSite = StableFieldUpdater.lazyOfInt(lookup, "", + MhUtil.findVarHandle(lookup, SimpleMhFoo.class, "hash", int.class), + MhUtil.findStatic(lookup,SimpleMhFoo.class, "computeHash", MethodType.methodType(int.class, SimpleMhFoo.class)) + , -1); + + @SuppressWarnings("unchecked") + ToIntFunction hasher = (ToIntFunction) callSite.getTarget().invoke(); + + var foo = new SimpleMhFoo(STRING); + int hash = hasher.applyAsInt(foo); + assertEquals(STRING.hashCode(), hash); + } + + @Test + void lazyOfLong() throws Throwable { + var lookup = MethodHandles.lookup(); + CallSite callSite = StableFieldUpdater.lazyOfLong(lookup, "", + MhUtil.findVarHandle(lookup, LongMhFoo.class, "hash", long.class), + MhUtil.findVirtual(lookup, LongMhFoo.class, "hash0", MethodType.methodType(long.class)) + , -1); + + @SuppressWarnings("unchecked") + ToLongFunction hasher = (ToLongFunction) callSite.getTarget().invoke(); + + var foo = new LongMhFoo(STRING); + long hash = hasher.applyAsLong(foo); + assertEquals(STRING.hashCode(), hash); + } + static final class Foo implements HasHashField { private static final ToIntFunction UPDATER = From e928591aa84a358d4d72c9927bcfad2d0c52606c Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Thu, 8 May 2025 09:23:42 +0200 Subject: [PATCH 18/24] Revert changes in stable classes --- .../jdk/internal/lang/stable/StableUtil.java | 13 ------------- .../jdk/internal/lang/stable/StableValueImpl.java | 12 ++++++++++-- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableUtil.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableUtil.java index 9336deea33ddf..f6f33f9b1e86b 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableUtil.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableUtil.java @@ -25,15 +25,10 @@ package jdk.internal.lang.stable; -import jdk.internal.misc.Unsafe; - -import java.lang.reflect.Field; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.StringJoiner; -import java.util.function.Function; -import java.util.function.ToIntFunction; public final class StableUtil { @@ -101,14 +96,6 @@ public static Map> map(Set keys) { return Map.ofEntries(entries); } - // This method is not annotated with @ForceInline as it is always called - // in a slow path. - static void preventReentry(Object obj) { - if (Thread.holdsLock(obj)) { - throw new IllegalStateException("Recursive initialization of a stable value is illegal"); - } - } - public static void assertSizeNonNegative(int size) { if (size < 0) { throw new IllegalArgumentException("size can not be negative: " + size); diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableValueImpl.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableValueImpl.java index 1d1a8578dbbcb..88c80eb6b395f 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableValueImpl.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableValueImpl.java @@ -81,7 +81,7 @@ public boolean trySet(T contents) { return false; } // Prevent reentry via an orElseSet(supplier) - StableUtil.preventReentry(this); + preventReentry(); // Mutual exclusion is required here as `orElseSet` might also // attempt to modify the `wrappedValue` synchronized (this) { @@ -132,7 +132,7 @@ public T orElseSet(Supplier supplier) { @DontInline private T orElseSetSlowPath(Supplier supplier) { - StableUtil.preventReentry(this); + preventReentry(); synchronized (this) { final Object t = contents; // Plain semantics suffice here if (t == null) { @@ -168,6 +168,14 @@ static String renderWrapped(Object t) { // Private methods + // This method is not annotated with @ForceInline as it is always called + // in a slow path. + private void preventReentry() { + if (Thread.holdsLock(this)) { + throw new IllegalStateException("Recursive initialization of a stable value is illegal"); + } + } + /** * Wraps the provided {@code newValue} and tries to set the contents. *

From 1628b2641b8f765daa1ab8ac488561aa85cc010a Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Thu, 8 May 2025 15:13:15 +0200 Subject: [PATCH 19/24] Add composition of functions and MHs --- .../lang/stable/StableFieldUpdater.java | 291 +++++++++++------- .../StableFieldUpdaterExampleTest.java | 7 +- .../StableValue/StableFieldUpdaterTest.java | 79 ++++- .../stable/StableFieldUpdaterBenchmark.java | 5 +- 4 files changed, 247 insertions(+), 135 deletions(-) diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java index 8d1d2328450da..92c3029b2db31 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java @@ -86,7 +86,7 @@ * * private static final ToIntFunction HASH_UPDATER = * StableFieldUpdater.ofInt(LazyFoo.class, "hash", - * l -> Objects.hash(l.bar, l.baz), -1); + * l -> Objects.hash(l.bar, l.baz)); * * @Stable * private int hash; @@ -120,41 +120,41 @@ * {@code 1 << 32} can be used as a token for zero (as the lower 32 bits are zero) and * then just cast to an {@code int} as shown in this example: * - * {@snippet lang=java: - public final class LazySpecifiedFoo { - - private final Bar bar; - private final Baz baz; - - private static final ToLongFunction HASH_UPDATER = - StableFieldUpdater.ofLong(LazySpecifiedFoo.class, "hash", - LazySpecifiedFoo::hashCodeFor, 1L << 32); - - @Stable - private long hash; - - public LazySpecifiedFoo(Bar bar, Baz baz) { - this.bar = bar; - this.baz = baz; - } - - @Override - public boolean equals(Object o) { - return (o instanceof Foo that) - && Objects.equals(this.bar, that.bar) - && Objects.equals(this.baz, that.baz); - } - - @Override - public int hashCode() { - return (int) HASH_UPDATER.applyAsLong(this); - } - - static long hashCodeFor(LazySpecifiedFoo foo) { - return Objects.hash(foo.bar, foo.baz); - } - } - * } + * {@snippet lang = java: + * public final class LazySpecifiedFoo { + * + * private final Bar bar; + * private final Baz baz; + * + * private static final ToLongFunction HASH_UPDATER = + * StableFieldUpdater.ofLong(LazySpecifiedFoo.class, "hash", + * StableFieldUpdater.replaceLongZero(LazySpecifiedFoo::hashCodeFor, 1L << 32)); + * + * @Stable + * private long hash; + * + * public LazySpecifiedFoo(Bar bar, Baz baz) { + * this.bar = bar; + * this.baz = baz; + * } + * + * @Override + * public boolean equals(Object o) { + * return (o instanceof Foo that) + * && Objects.equals(this.bar, that.bar) + * && Objects.equals(this.baz, that.baz); + * } + * + * @Override + * public int hashCode() { + * return (int) HASH_UPDATER.applyAsLong(this); + * } + * + * static long hashCodeFor(LazySpecifiedFoo foo) { + * return Objects.hash(foo.bar, foo.baz); + * } + * } + *} * The example above also features a static method {@code hashCodeFor()} that acts as * the underlying hash function. This method can reside in another class. *

@@ -172,8 +172,7 @@ public final class MhFoo { private static final ToIntFunction HASH_UPDATER = StableFieldUpdater.ofInt( MhUtil.findVarHandle(LOOKUP, "hash", int.class), - MhUtil.findStatic(LOOKUP, "hashCodeFor", MethodType.methodType(int.class, MhFoo.class)), - -1); + MhUtil.findStatic(LOOKUP, "hashCodeFor", MethodType.methodType(int.class, MhFoo.class))); @Stable private int hash; @@ -201,7 +200,7 @@ static int hashCodeFor(MhFoo foo) { } } * } - * There is a convenience method for this idiomatic case that looks like this: + * There is a convenience method for the idiomatic case above that looks like this: * {@snippet lang=java: private static final ToIntFunction HASH_UPDATER = @@ -213,6 +212,9 @@ static int hashCodeFor(MhFoo foo) { * * The provided {@code underlying} function must not recurse or the result of the * operation is unspecified. + *

+ * If a reference value of {@code null} is used as a parameter in any of the methods + * in this class, a {@link NullPointerException} is thrown. */ public final class StableFieldUpdater { @@ -248,30 +250,27 @@ public static ToIntFunction ofInt(MethodHandles.Lookup lookup, String staticUnderlyingMethodName) { return ofInt( MhUtil.findVarHandle(lookup, fieldName, int.class), - MhUtil.findStatic(lookup, staticUnderlyingMethodName, MethodType.methodType(int.class, lookup.lookupClass())), - -1); + MhUtil.findStatic(lookup, staticUnderlyingMethodName, MethodType.methodType(int.class, lookup.lookupClass()))); } public static ToIntFunction ofInt(VarHandle accessor, - MethodHandle underlying, - int zeroReplacement) { + MethodHandle underlying) { check(accessor, int.class); var adaptedUnderlying = checkAndAdapt(underlying, int.class); - return new StableIntFieldUpdaterVarHandle<>(accessor, adaptedUnderlying, zeroReplacement); + return new StableIntFieldUpdaterVarHandle<>(accessor, adaptedUnderlying); } @CallerSensitive public static ToIntFunction ofInt(Class holderType, String fieldName, - ToIntFunction underlying, - int zeroReplacement) { + ToIntFunction underlying) { Objects.requireNonNull(holderType); Objects.requireNonNull(fieldName); Objects.requireNonNull(underlying); final long offset = offset(holderType, fieldName, int.class, Reflection.getCallerClass()); - return new StableIntFieldUpdater<>(holderType, offset, underlying, zeroReplacement); + return new StableIntFieldUpdater<>(holderType, offset, underlying); } /** @@ -301,54 +300,147 @@ public static ToLongFunction ofLong(MethodHandles.Lookup lookup, String staticUnderlyingMethodName) { return ofLong( MhUtil.findVarHandle(lookup, fieldName, long.class), - MhUtil.findStatic(lookup, staticUnderlyingMethodName, MethodType.methodType(long.class, lookup.lookupClass())), - 1L << 31); + MhUtil.findStatic(lookup, staticUnderlyingMethodName, MethodType.methodType(long.class, lookup.lookupClass()))); } public static ToLongFunction ofLong(VarHandle accessor, - MethodHandle underlying, - long zeroReplacement) { + MethodHandle underlying) { check(accessor, long.class); var adaptedUnderlying = checkAndAdapt(underlying, long.class); - return makeLong(accessor, adaptedUnderlying, zeroReplacement); + return makeLong(accessor, adaptedUnderlying); } @CallerSensitive public static ToLongFunction ofLong(Class holderType, String fieldName, - ToLongFunction underlying, - long zeroReplacement) { + ToLongFunction underlying) { Objects.requireNonNull(holderType); Objects.requireNonNull(fieldName); Objects.requireNonNull(underlying); final long offset = offset(holderType, fieldName, long.class, Reflection.getCallerClass()); - return makeLong(holderType, offset, underlying, zeroReplacement); + return makeLong(holderType, offset, underlying); } + /** + * {@return a function that will replace any zero value returned by the provided + * {@code underlying} function with the provided {@code zeroReplacement}} + * @param underlying function to filter return values from + * @param zeroReplacement to replace any zero values returned by the {@code underlying} + * function. + * @param The function's type parameter + */ + public static ToIntFunction replaceIntZero(ToIntFunction underlying, int zeroReplacement) { + + record IntZeroReplacer(ToIntFunction underlying, int zeroReplacement) implements ToIntFunction { + @ForceInline + @Override + public int applyAsInt(T value) { + return replaceZero(underlying.applyAsInt(value), zeroReplacement); + } + } + + Objects.requireNonNull(underlying); + return new IntZeroReplacer<>(underlying, zeroReplacement); + } + + /** + * {@return a method handle that will replace any zero value returned by the provided + * {@code underlying} handle with the provided {@code zeroReplacement}} + * @param underlying function to filter return values from + * @param zeroReplacement to replace any zero values returned by the {@code underlying} + * method handle. + */ + public static MethodHandle replaceIntZero(MethodHandle underlying, int zeroReplacement) { + + final class Holder { + private static final MethodHandle RETURN_FILTER = + MhUtil.findStatic(LOCAL_LOOKUP, "replaceZero", MethodType.methodType(int.class, int.class, int.class)); + } + check(underlying, int.class); + return MethodHandles.filterReturnValue(underlying, + MethodHandles.insertArguments(Holder.RETURN_FILTER, 1, zeroReplacement)); + } + + /** + * {@return a function that will replace any zero value returned by the provided + * {@code underlying} function with the provided {@code zeroReplacement}} + * @param underlying function to filter return values from + * @param zeroReplacement to replace any zero values returned by the {@code underlying} + * function. + * @param The function's type parameter + */ + public static ToLongFunction replaceLongZero(ToLongFunction underlying, long zeroReplacement) { + + record LongZeroReplacer(ToLongFunction underlying, long zeroReplacement) implements ToLongFunction { + @ForceInline + @Override + public long applyAsLong(T value) { + return replaceZero(underlying.applyAsLong(value), zeroReplacement); + } + } + + Objects.requireNonNull(underlying); + return new LongZeroReplacer<>(underlying, zeroReplacement); + } + + /** + * {@return a method handle that will replace any zero value returned by the provided + * {@code underlying} handle with the provided {@code zeroReplacement}} + * @param underlying function to filter return values from + * @param zeroReplacement to replace any zero values returned by the {@code underlying} + * method handle. + */ + public static MethodHandle replaceLongZero(MethodHandle underlying, long zeroReplacement) { + + final class Holder { + private static final MethodHandle RETURN_FILTER = + MhUtil.findStatic(LOCAL_LOOKUP, "replaceZero", MethodType.methodType(long.class, long.class, long.class)); + } + + check(underlying, long.class); + return MethodHandles.filterReturnValue(underlying, + MethodHandles.insertArguments(Holder.RETURN_FILTER, 1, zeroReplacement)); + } + + // Also used reflectively + @ForceInline + private static int replaceZero(int value, int zeroReplacement) { + return value == 0 + ? zeroReplacement + : value; + } + + // Also used reflectively + @ForceInline + private static long replaceZero(long value, long zeroReplacement) { + return value == 0 + ? zeroReplacement + : value; + } + + public static CallSite lazyOfInt(MethodHandles.Lookup lookup, String unused, VarHandle accessor, - MethodHandle underlying, - int zeroReplacement) { + MethodHandle underlying) { check(accessor, int.class); var adaptedUnderlying = checkAndAdapt(underlying, int.class); var handle = MhUtil.findStatic(LOCAL_LOOKUP, - "ofInt", MethodType.methodType(ToIntFunction.class, VarHandle.class, MethodHandle.class, int.class)); - return new ConstantCallSite(MethodHandles.insertArguments(handle, 0, accessor, adaptedUnderlying, zeroReplacement)); + "ofInt", MethodType.methodType(ToIntFunction.class, VarHandle.class, MethodHandle.class)); + return new ConstantCallSite(MethodHandles.insertArguments(handle, 0, accessor, adaptedUnderlying)); } public static CallSite lazyOfLong(MethodHandles.Lookup lookup, String unused, VarHandle accessor, - MethodHandle underlying, - long zeroReplacement) { + MethodHandle underlying) { check(accessor, long.class); var adaptedUnderlying = checkAndAdapt(underlying, long.class); var handle = MhUtil.findStatic(LOCAL_LOOKUP, - "makeLong", MethodType.methodType(ToLongFunction.class, VarHandle.class, MethodHandle.class, long.class)); - return new ConstantCallSite(MethodHandles.insertArguments(handle, 0, accessor, adaptedUnderlying, zeroReplacement)); + "makeLong", MethodType.methodType(ToLongFunction.class, VarHandle.class, MethodHandle.class)); + return new ConstantCallSite(MethodHandles.insertArguments(handle, 0, accessor, adaptedUnderlying)); } // Only to be used by classes that are used "early" in the init sequence. @@ -358,59 +450,54 @@ private Raw() {} public static ToIntFunction ofInt(Class holderType, long offset, - ToIntFunction underlying, - int zeroReplacement) { + ToIntFunction underlying) { Objects.requireNonNull(holderType); if (offset < 0) { throw new IllegalArgumentException(); } Objects.requireNonNull(underlying); - return new StableIntFieldUpdater<>(holderType, offset, underlying, zeroReplacement); + return new StableIntFieldUpdater<>(holderType, offset, underlying); } public static ToLongFunction ofLong(Class holderType, long offset, - ToLongFunction underlying, - long zeroReplacement) { + ToLongFunction underlying) { Objects.requireNonNull(holderType); if (offset < 0) { throw new IllegalArgumentException(); } Objects.requireNonNull(underlying); - return makeLong(holderType, offset, underlying, zeroReplacement); + return makeLong(holderType, offset, underlying); } } private static ToLongFunction makeLong(Class holderType, long offset, - ToLongFunction underlying, - long zeroReplacement) { + ToLongFunction underlying) { if (Architecture.is64bit()) { // We are also relying on the fact that the VM will not place 64-bit // instance fields that can cross cache lines. - return new StableLongFieldUpdater<>(holderType, offset, underlying, zeroReplacement); + return new StableLongFieldUpdater<>(holderType, offset, underlying); } else { - return new TearingStableLongFieldUpdater<>(holderType, offset, underlying, zeroReplacement); + return new TearingStableLongFieldUpdater<>(holderType, offset, underlying); } } private static ToLongFunction makeLong(VarHandle accessor, - MethodHandle underlying, - long zeroReplacement) { + MethodHandle underlying) { if (Architecture.is64bit()) { // We are also relying on the fact that the VM will not place 64-bit // instance fields that can cross cache lines. - return new StableLongFieldUpdaterVarHandle<>(accessor, underlying, zeroReplacement); + return new StableLongFieldUpdaterVarHandle<>(accessor, underlying); } else { - return new TearingStableLongFieldUpdaterVarHandle<>(accessor, underlying, zeroReplacement); + return new TearingStableLongFieldUpdaterVarHandle<>(accessor, underlying); } } private record StableIntFieldUpdater(Class holderType, long offset, - ToIntFunction underlying, - int zeroReplacement) implements ToIntFunction { + ToIntFunction underlying) implements ToIntFunction { @ForceInline @Override @@ -426,9 +513,6 @@ public int applyAsInt(T t) { v = UNSAFE.getIntAcquire(t, offset); if (v == 0) { v = underlying.applyAsInt(t); - if (v == 0) { - v = zeroReplacement; - } UNSAFE.putIntRelease(t, offset, v); } } @@ -438,8 +522,7 @@ public int applyAsInt(T t) { } private record StableIntFieldUpdaterVarHandle(VarHandle accessor, - MethodHandle underlying, - int zeroReplacement) implements ToIntFunction { + MethodHandle underlying) implements ToIntFunction { @ForceInline @Override @@ -458,9 +541,6 @@ public int applyAsInt(T t) { } catch (Throwable e) { throw new RuntimeException(e); } - if (v == 0) { - v = zeroReplacement; - } accessor.setRelease(t, v); } } @@ -471,8 +551,7 @@ public int applyAsInt(T t) { private record StableLongFieldUpdater(Class holderType, long offset, - ToLongFunction underlying, - long zeroReplacement) implements ToLongFunction { + ToLongFunction underlying) implements ToLongFunction { @ForceInline @Override @@ -488,9 +567,6 @@ public long applyAsLong(T t) { v = UNSAFE.getLongAcquire(t, offset); if (v == 0) { v = underlying.applyAsLong(t); - if (v == 0) { - v = zeroReplacement; - } UNSAFE.putLongRelease(t, offset, v); } } @@ -501,8 +577,7 @@ public long applyAsLong(T t) { private record TearingStableLongFieldUpdater(Class holderType, long offset, - ToLongFunction underlying, - long zeroReplacement) implements ToLongFunction { + ToLongFunction underlying) implements ToLongFunction { @ForceInline @Override @@ -518,9 +593,6 @@ public long applyAsLong(T t) { v = UNSAFE.getLongAcquire(t, offset); if (v == 0) { v = underlying.applyAsLong(t); - if (v == 0) { - v = zeroReplacement; - } UNSAFE.putLongRelease(t, offset, v); } } @@ -530,8 +602,7 @@ public long applyAsLong(T t) { } private record StableLongFieldUpdaterVarHandle(VarHandle accessor, - MethodHandle underlying, - long zeroReplacement) implements ToLongFunction { + MethodHandle underlying) implements ToLongFunction { @ForceInline @Override @@ -550,9 +621,6 @@ public long applyAsLong(T t) { } catch (Throwable e) { throw new RuntimeException(e); } - if (v == 0) { - v = zeroReplacement; - } accessor.setRelease(t, v); } } @@ -562,8 +630,7 @@ public long applyAsLong(T t) { } private record TearingStableLongFieldUpdaterVarHandle(VarHandle accessor, - MethodHandle underlying, - long zeroReplacement) implements ToLongFunction { + MethodHandle underlying) implements ToLongFunction { @ForceInline @Override @@ -582,9 +649,6 @@ public long applyAsLong(T t) { } catch (Throwable e) { throw new RuntimeException(e); } - if (v == 0) { - v = zeroReplacement; - } accessor.setRelease(t, v); } } @@ -610,17 +674,22 @@ private static void check(VarHandle accessor, Class varType) { } private static MethodHandle checkAndAdapt(MethodHandle underlying, Class returnType) { - // Implicit null check + check(underlying, returnType); final var underlyingType = underlying.type(); - if (underlyingType.returnType() != returnType || underlyingType.parameterCount() != 1) { - throw new IllegalArgumentException("Illegal underlying function: " + underlying); - } if (!underlyingType.parameterType(0).equals(Object.class)) { underlying = underlying.asType(underlyingType.changeParameterType(0, Object.class)); } return underlying; } + private static void check(MethodHandle underlying, Class returnType) { + // Implicit null check + final var underlyingType = underlying.type(); + if (underlyingType.returnType() != returnType || underlyingType.parameterCount() != 1) { + throw new IllegalArgumentException("Illegal underlying function: " + underlying); + } + } + private static long offset(Class holderType, String fieldName, Class fieldType, diff --git a/test/jdk/java/lang/StableValue/StableFieldUpdaterExampleTest.java b/test/jdk/java/lang/StableValue/StableFieldUpdaterExampleTest.java index d7c3df8d1a956..f65f698a3d2de 100644 --- a/test/jdk/java/lang/StableValue/StableFieldUpdaterExampleTest.java +++ b/test/jdk/java/lang/StableValue/StableFieldUpdaterExampleTest.java @@ -118,7 +118,7 @@ public final class LazyFoo { private static final ToIntFunction HASH_UPDATER = StableFieldUpdater.ofInt(LazyFoo.class, "hash", - l -> Objects.hash(l.bar, l.baz), -1); + l -> Objects.hash(l.bar, l.baz)); @Stable private int hash; @@ -150,7 +150,7 @@ public final class LazySpecifiedFoo { private static final ToLongFunction HASH_UPDATER = StableFieldUpdater.ofLong(LazySpecifiedFoo.class, "hash", - LazySpecifiedFoo::hashCodeFor, 1L << 32); + StableFieldUpdater.replaceLongZero(LazySpecifiedFoo::hashCodeFor, 1L << 32)); @Stable private long hash; @@ -190,8 +190,7 @@ public final class MhFoo { private static final ToIntFunction HASH_UPDATER = StableFieldUpdater.ofInt( MhUtil.findVarHandle(LOOKUP, "hash", int.class), - MhUtil.findStatic(LOOKUP, "hashCodeFor", MethodType.methodType(int.class, MhFoo.class)), - -1); + MhUtil.findStatic(LOOKUP, "hashCodeFor", MethodType.methodType(int.class, MhFoo.class))); @Stable private int hash; diff --git a/test/jdk/java/lang/StableValue/StableFieldUpdaterTest.java b/test/jdk/java/lang/StableValue/StableFieldUpdaterTest.java index 46d447e891373..02338692072b0 100644 --- a/test/jdk/java/lang/StableValue/StableFieldUpdaterTest.java +++ b/test/jdk/java/lang/StableValue/StableFieldUpdaterTest.java @@ -40,6 +40,7 @@ import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.util.function.Function; +import java.util.function.IntFunction; import java.util.function.ToIntFunction; import java.util.function.ToLongFunction; import java.util.stream.Stream; @@ -48,16 +49,26 @@ final class StableFieldUpdaterTest { - private static final int ZERO_REPLACEMENT = 42; private static final String STRING = "Abc"; @Test void invariants() { - assertThrows(NullPointerException.class, () -> StableFieldUpdater.ofInt(null, "a", _ -> 0, ZERO_REPLACEMENT)); - assertThrows(NullPointerException.class, () -> StableFieldUpdater.ofInt(String.class, null, _ -> 0, ZERO_REPLACEMENT)); - assertThrows(NullPointerException.class, () -> StableFieldUpdater.ofInt(Foo.class, "hash", null, ZERO_REPLACEMENT)); - var x = assertThrows(IllegalArgumentException.class, () -> StableFieldUpdater.ofInt(Foo.class, "dummy", _ -> 0, ZERO_REPLACEMENT)); - assertEquals("Only fields of type 'int' are supported. The provided field is 'long StableFieldUpdaterTest$Foo.dummy'", x.getMessage()); + assertThrows(NullPointerException.class, () -> StableFieldUpdater.ofInt(null, "a", _ -> 0)); + assertThrows(NullPointerException.class, () -> StableFieldUpdater.ofInt(String.class, null, _ -> 0)); + assertThrows(NullPointerException.class, () -> StableFieldUpdater.ofInt(Foo.class, "hash", null)); + var xi = assertThrows(IllegalArgumentException.class, () -> StableFieldUpdater.ofInt(Foo.class, "dummy", _ -> 0)); + assertEquals("Only fields of type 'int' are supported. The provided field is 'long StableFieldUpdaterTest$Foo.dummy'", xi.getMessage()); + + assertThrows(NullPointerException.class, () -> StableFieldUpdater.ofLong(null, "a", _ -> 0)); + assertThrows(NullPointerException.class, () -> StableFieldUpdater.ofLong(String.class, null, _ -> 0)); + assertThrows(NullPointerException.class, () -> StableFieldUpdater.ofLong(Foo.class, "hash", null)); + var xl = assertThrows(IllegalArgumentException.class, () -> StableFieldUpdater.ofLong(Foo.class, "hash", _ -> 0)); + assertEquals("Only fields of type 'long' are supported. The provided field is 'int StableFieldUpdaterTest$Foo.hash'", xl.getMessage()); + + assertThrows(NullPointerException.class, () -> StableFieldUpdater.replaceIntZero((ToIntFunction)null, 1)); + assertThrows(NullPointerException.class, () -> StableFieldUpdater.replaceIntZero((MethodHandle) null, 1)); + assertThrows(NullPointerException.class, () -> StableFieldUpdater.replaceLongZero((ToLongFunction)null, 1L)); + assertThrows(NullPointerException.class, () -> StableFieldUpdater.replaceLongZero((MethodHandle) null, 1L)); } @ParameterizedTest @@ -75,7 +86,7 @@ void recordFoo() { var recordFoo = new RecordFoo(STRING, 0); // The field is `final` var x = assertThrows(IllegalArgumentException.class, - () -> StableFieldUpdater.ofInt(RecordFoo.class, "hash", _ -> 0, ZERO_REPLACEMENT)); + () -> StableFieldUpdater.ofInt(RecordFoo.class, "hash", _ -> 0)); assertEquals("Only non final fields are supported. The provided field is 'private final int StableFieldUpdaterTest$RecordFoo.hash'", x.getMessage()); } @@ -83,7 +94,7 @@ void recordFoo() { @Test void uncheckedCall() { // Use a raw type - ToIntFunction updater = StableFieldUpdater.ofInt(Foo.class, "hash", f -> f.string.hashCode(), ZERO_REPLACEMENT); + ToIntFunction updater = StableFieldUpdater.ofInt(Foo.class, "hash", f -> f.string.hashCode()); var object = new Object(); var x = assertThrows(IllegalArgumentException.class, () -> updater.applyAsInt(object)); assertEquals("The provided t is not an instance of class StableFieldUpdaterTest$Foo", x.getMessage()); @@ -94,8 +105,7 @@ void lazyOfInt() throws Throwable { var lookup = MethodHandles.lookup(); CallSite callSite = StableFieldUpdater.lazyOfInt(lookup, "", MhUtil.findVarHandle(lookup, SimpleMhFoo.class, "hash", int.class), - MhUtil.findStatic(lookup,SimpleMhFoo.class, "computeHash", MethodType.methodType(int.class, SimpleMhFoo.class)) - , -1); + MhUtil.findStatic(lookup,SimpleMhFoo.class, "computeHash", MethodType.methodType(int.class, SimpleMhFoo.class))); @SuppressWarnings("unchecked") ToIntFunction hasher = (ToIntFunction) callSite.getTarget().invoke(); @@ -110,8 +120,7 @@ void lazyOfLong() throws Throwable { var lookup = MethodHandles.lookup(); CallSite callSite = StableFieldUpdater.lazyOfLong(lookup, "", MhUtil.findVarHandle(lookup, LongMhFoo.class, "hash", long.class), - MhUtil.findVirtual(lookup, LongMhFoo.class, "hash0", MethodType.methodType(long.class)) - , -1); + MhUtil.findVirtual(lookup, LongMhFoo.class, "hash0", MethodType.methodType(long.class))); @SuppressWarnings("unchecked") ToLongFunction hasher = (ToLongFunction) callSite.getTarget().invoke(); @@ -121,10 +130,46 @@ void lazyOfLong() throws Throwable { assertEquals(STRING.hashCode(), hash); } + @Test + void replaceIntZeroFunction() { + int zeroReplacement = -1; + ToIntFunction underlying = i -> i; + var mod = StableFieldUpdater.replaceIntZero(underlying, zeroReplacement); + assertEquals(1, mod.applyAsInt(1)); + assertEquals(zeroReplacement, mod.applyAsInt(0)); + } + + @Test + void replaceIntZeroHandle() throws Throwable { + int zeroReplacement = -1; + MethodHandle underlying = MethodHandles.identity(int.class); + var mod = StableFieldUpdater.replaceIntZero(underlying, zeroReplacement); + assertEquals(1, (int) mod.invoke(1)); + assertEquals(zeroReplacement, (int) mod.invoke(0)); + } + + @Test + void replaceLongZeroFunction() { + long zeroReplacement = -1; + ToLongFunction underlying = i -> i; + var mod = StableFieldUpdater.replaceLongZero(underlying, zeroReplacement); + assertEquals(1L, mod.applyAsLong(1L)); + assertEquals(zeroReplacement, mod.applyAsLong(0L)); + } + + @Test + void replaceLongZeroHandle() throws Throwable { + long zeroReplacement = -1; + MethodHandle underlying = MethodHandles.identity(long.class); + var mod = StableFieldUpdater.replaceLongZero(underlying, zeroReplacement); + assertEquals(1L, (long) mod.invoke(1L)); + assertEquals(zeroReplacement, (long) mod.invoke(0L)); + } + static final class Foo implements HasHashField { private static final ToIntFunction UPDATER = - StableFieldUpdater.ofInt(Foo.class, "hash", f -> f.string.hashCode(), ZERO_REPLACEMENT); + StableFieldUpdater.ofInt(Foo.class, "hash", f -> f.string.hashCode()); private final String string; int hash; @@ -148,7 +193,7 @@ public long hash() { static final class LongFoo implements HasHashField { private static final ToLongFunction UPDATER = - StableFieldUpdater.ofLong(LongFoo.class, "hash", f -> f.string.hashCode(), ZERO_REPLACEMENT); + StableFieldUpdater.ofLong(LongFoo.class, "hash", f -> f.string.hashCode()); private final String string; long hash; @@ -174,7 +219,7 @@ record RecordFoo(String string, int hash) {} static final class InheritingFoo extends AbstractFoo implements HasHashField { private static final ToIntFunction UPDATER = - StableFieldUpdater.ofInt(InheritingFoo.class, "hash", f -> f.string.hashCode(), ZERO_REPLACEMENT); + StableFieldUpdater.ofInt(InheritingFoo.class, "hash", f -> f.string.hashCode()); public InheritingFoo(String string) { super(string); @@ -205,7 +250,7 @@ static final class MhFoo implements HasHashField { private static final MethodHandle HASH_MH = MhUtil.findVirtual(MethodHandles.lookup(), "hash0", MethodType.methodType(int.class)); private static final ToIntFunction UPDATER = - StableFieldUpdater.ofInt(MhUtil.findVarHandle(MethodHandles.lookup(), "hash", int.class), HASH_MH, ZERO_REPLACEMENT); + StableFieldUpdater.ofInt(MhUtil.findVarHandle(MethodHandles.lookup(), "hash", int.class), HASH_MH); private final String string; int hash; @@ -267,7 +312,7 @@ static final class LongMhFoo implements HasHashField { private static final MethodHandle HASH_MH = MhUtil.findVirtual(MethodHandles.lookup(), "hash0", MethodType.methodType(long.class)); private static final ToLongFunction UPDATER = - StableFieldUpdater.ofLong(MhUtil.findVarHandle(MethodHandles.lookup(), "hash", long.class), HASH_MH, ZERO_REPLACEMENT); + StableFieldUpdater.ofLong(MhUtil.findVarHandle(MethodHandles.lookup(), "hash", long.class), HASH_MH); private final String string; long hash; diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableFieldUpdaterBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableFieldUpdaterBenchmark.java index 6a38d788a8e19..ef36e5e58b3d2 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/StableFieldUpdaterBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableFieldUpdaterBenchmark.java @@ -128,7 +128,7 @@ static final class Updater extends Abstract { public int applyAsInt(Updater updater) { return updater.string.hashCode(); } - }, -1); + }); Updater(String string) { super(string); } @@ -144,8 +144,7 @@ static final class MhUpdater extends Abstract { private static final ToIntFunction HASH_CODE_UPDATER = StableFieldUpdater.ofInt( MhUtil.findVarHandle(LOOKUP, "hashCode", int.class), - MhUtil.findVirtual(LOOKUP, "hashCode0", MethodType.methodType(int.class)), - -1); + MhUtil.findVirtual(LOOKUP, "hashCode0", MethodType.methodType(int.class))); MhUpdater(String string) { super(string); } From ce1b832b59bc046ce5d04722a570ef32a99b34a6 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Fri, 9 May 2025 10:31:05 +0200 Subject: [PATCH 20/24] Remove unused factories and add comment --- .../lang/stable/StableFieldUpdater.java | 29 ------------------- .../StableFieldUpdaterExampleTest.java | 4 +++ 2 files changed, 4 insertions(+), 29 deletions(-) diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java index 92c3029b2db31..1dd0f5f221491 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java @@ -443,35 +443,6 @@ public static CallSite lazyOfLong(MethodHandles.Lookup lookup, return new ConstantCallSite(MethodHandles.insertArguments(handle, 0, accessor, adaptedUnderlying)); } - // Only to be used by classes that are used "early" in the init sequence. - public static final class Raw { - - private Raw() {} - - public static ToIntFunction ofInt(Class holderType, - long offset, - ToIntFunction underlying) { - Objects.requireNonNull(holderType); - if (offset < 0) { - throw new IllegalArgumentException(); - } - Objects.requireNonNull(underlying); - return new StableIntFieldUpdater<>(holderType, offset, underlying); - } - - public static ToLongFunction ofLong(Class holderType, - long offset, - ToLongFunction underlying) { - Objects.requireNonNull(holderType); - if (offset < 0) { - throw new IllegalArgumentException(); - } - Objects.requireNonNull(underlying); - return makeLong(holderType, offset, underlying); - } - - } - private static ToLongFunction makeLong(Class holderType, long offset, ToLongFunction underlying) { diff --git a/test/jdk/java/lang/StableValue/StableFieldUpdaterExampleTest.java b/test/jdk/java/lang/StableValue/StableFieldUpdaterExampleTest.java index f65f698a3d2de..9a969c0ed4151 100644 --- a/test/jdk/java/lang/StableValue/StableFieldUpdaterExampleTest.java +++ b/test/jdk/java/lang/StableValue/StableFieldUpdaterExampleTest.java @@ -41,6 +41,10 @@ import static org.junit.jupiter.api.Assertions.assertEquals; +/** + * This test is to make sure the example in the documentation of + * {@linkplain StableFieldUpdater} remains correct and compilable. + */ final class StableFieldUpdaterExampleTest { @interface Stable {} // No access to the real @Stable From cbcf13ba09a70d15f6817afb62196c86a06d2506 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Fri, 9 May 2025 10:32:25 +0200 Subject: [PATCH 21/24] Revert unintended change --- .../org/openjdk/bench/java/lang/foreign/SegmentBulkHash.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/micro/org/openjdk/bench/java/lang/foreign/SegmentBulkHash.java b/test/micro/org/openjdk/bench/java/lang/foreign/SegmentBulkHash.java index 7e702f3e096ec..4740897b6040f 100644 --- a/test/micro/org/openjdk/bench/java/lang/foreign/SegmentBulkHash.java +++ b/test/micro/org/openjdk/bench/java/lang/foreign/SegmentBulkHash.java @@ -97,4 +97,4 @@ public int nativeSegmentJava() { return result; } -} +} \ No newline at end of file From 9a58db6a198154899a79266f29bf104c4c1e5de3 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Fri, 9 May 2025 10:33:26 +0200 Subject: [PATCH 22/24] Revert unintended change --- .../org/openjdk/bench/java/lang/foreign/SegmentBulkHash.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/micro/org/openjdk/bench/java/lang/foreign/SegmentBulkHash.java b/test/micro/org/openjdk/bench/java/lang/foreign/SegmentBulkHash.java index 4740897b6040f..927a7d3fb1f72 100644 --- a/test/micro/org/openjdk/bench/java/lang/foreign/SegmentBulkHash.java +++ b/test/micro/org/openjdk/bench/java/lang/foreign/SegmentBulkHash.java @@ -97,4 +97,5 @@ public int nativeSegmentJava() { return result; } -} \ No newline at end of file +} + From 119cbcf4ce9b70399246717f378dd56b10157aee Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 14 May 2025 17:27:29 +0200 Subject: [PATCH 23/24] Wip --- .../lang/stable/StableFieldUpdater.java | 498 ++++-------------- .../stable/StableFieldUpdaterGenerator.java | 341 ++++++++++++ .../StableFieldUpdaterExampleTest.java | 104 +--- .../StableValue/StableFieldUpdaterTest.java | 380 +++++++------ .../stable/StableFieldUpdaterBenchmark.java | 93 +--- 5 files changed, 676 insertions(+), 740 deletions(-) create mode 100644 src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdaterGenerator.java diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java index 1dd0f5f221491..fd585750b51ac 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java @@ -26,12 +26,8 @@ package jdk.internal.lang.stable; import jdk.internal.invoke.MhUtil; -import jdk.internal.misc.Unsafe; -import jdk.internal.reflect.CallerSensitive; -import jdk.internal.reflect.Reflection; import jdk.internal.util.Architecture; import jdk.internal.vm.annotation.ForceInline; -import sun.reflect.misc.ReflectUtil; import java.lang.invoke.CallSite; import java.lang.invoke.ConstantCallSite; @@ -39,11 +35,10 @@ import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.lang.invoke.VarHandle; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; +import java.util.List; import java.util.Objects; -import java.util.function.ToIntFunction; -import java.util.function.ToLongFunction; + +import static jdk.internal.lang.stable.StableFieldUpdaterGenerator.*; /** * Stable field updaters. @@ -220,129 +215,80 @@ public final class StableFieldUpdater { private StableFieldUpdater() {} - private static final Unsafe UNSAFE = Unsafe.getUnsafe(); private static final MethodHandles.Lookup LOCAL_LOOKUP = MethodHandles.lookup(); /** - * {@return a function that lazily sets the field named {@code fieldName} by invoking - * the static method in the {@link MethodHandles.Lookup#lookupClass()} - * named {@code staticUnderlyingMethodName} if the field was not previously - * set. Otherwise, the function returns the set field value} + * {@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} *

- * If the {@code staticUnderlyingMethodName} returns zero, the value is replaced with - * {@code -1} which is then stored in the field with the provided {@code fieldName}. * - * @param lookup used to reflectively lookup entities - * @param fieldName the name of the lazily set field (of type {@code int}) - * @param staticUnderlyingMethodName the name of the method to invoke when computing - * the hash value (invoked at most once) - * @param target type + * @param accessor used to access a field + * @param underlying function which is invoked if a field has its + * default value. * @throws NullPointerException if any of the provided parameters are {@code null} - * @throws IllegalArgumentException if no {@code int} field can be found using the - * provided {@code lookup} and {@code fieldName} - * @throws IllegalArgumentException if no {@code int} function can be found using the - * provided {@code lookup} and - * {@code staticUnderlyingMethodName} where the - * single method parameter is of type {@code T}. + * @throws IllegalArgumentException if the accessor's {@linkplain VarHandle#varType()} + * is not equal to the return type of the + * underlying's {@linkplain MethodHandle#type()} + * @throws IllegalArgumentException if the provided {@code underlying} function does + * take exactly one parameter of a reference type. */ - public static ToIntFunction ofInt(MethodHandles.Lookup lookup, - String fieldName, - String staticUnderlyingMethodName) { - return ofInt( - MhUtil.findVarHandle(lookup, fieldName, int.class), - MhUtil.findStatic(lookup, staticUnderlyingMethodName, MethodType.methodType(int.class, lookup.lookupClass()))); - } - - public static ToIntFunction ofInt(VarHandle accessor, - MethodHandle underlying) { - check(accessor, int.class); - var adaptedUnderlying = checkAndAdapt(underlying, int.class); - - return new StableIntFieldUpdaterVarHandle<>(accessor, adaptedUnderlying); - } - - @CallerSensitive - public static ToIntFunction ofInt(Class holderType, - String fieldName, - ToIntFunction underlying) { - Objects.requireNonNull(holderType); - Objects.requireNonNull(fieldName); - Objects.requireNonNull(underlying); + public static MethodHandle atMostOnce(VarHandle accessor, + MethodHandle underlying) { + // Implicit null check + final var accessorVarType = accessor.varType(); + final var accessorCoordinateTypes = accessor.coordinateTypes(); + final var underlyingType = underlying.type(); - final long offset = offset(holderType, fieldName, int.class, Reflection.getCallerClass()); - return new StableIntFieldUpdater<>(holderType, offset, underlying); - } + if (accessorVarType != underlyingType.returnType()) { + throw new IllegalArgumentException("Return type mismatch: accessor: " + accessor + ", underlying: " + underlying); + } - /** - * {@return a function that lazily sets the field named {@code fieldName} by invoking - * the static method in the {@link MethodHandles.Lookup#lookupClass()} - * named {@code staticUnderlyingMethodName} if the field was not previously - * set. Otherwise, the function returns the set field value} - *

- * If the {@code staticUnderlyingMethodName} returns zero, the value is replaced with - * {@code 1L<<32} which is then stored in the field with the provided {@code fieldName}. - * - * @param lookup used to reflectively lookup entities - * @param fieldName the name of the lazily set field (of type {@code long}) - * @param staticUnderlyingMethodName the name of the method to invoke when computing - * the hash value (invoked at most once) - * @param target type - * @throws NullPointerException if any of the provided parameters are {@code null} - * @throws IllegalArgumentException if no {@code int} field can be found using the - * provided {@code lookup} and {@code fieldName} - * @throws IllegalArgumentException if no {@code long} function can be found using the - * provided {@code lookup} and - * {@code staticUnderlyingMethodName} where the - * single method parameter is of type {@code T}. - */ - public static ToLongFunction ofLong(MethodHandles.Lookup lookup, - String fieldName, - String staticUnderlyingMethodName) { - return ofLong( - MhUtil.findVarHandle(lookup, fieldName, long.class), - MhUtil.findStatic(lookup, staticUnderlyingMethodName, MethodType.methodType(long.class, lookup.lookupClass()))); - } + if (!accessorCoordinateTypes.equals(underlyingType.parameterList())) { + throw new IllegalArgumentException("Parameter type mismatch: accessor: " + accessor + ", underlying: " + underlying); + } - public static ToLongFunction ofLong(VarHandle accessor, - MethodHandle underlying) { - check(accessor, long.class); - var adaptedUnderlying = checkAndAdapt(underlying, long.class); + if (!accessor.isAccessModeSupported(VarHandle.AccessMode.SET)) { + throw new IllegalArgumentException("The accessor is read only: " + accessor); + } - return makeLong(accessor, adaptedUnderlying); - } + // 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)); - @CallerSensitive - public static ToLongFunction ofLong(Class holderType, - String fieldName, - ToLongFunction underlying) { - Objects.requireNonNull(holderType); - Objects.requireNonNull(fieldName); - Objects.requireNonNull(underlying); + final MethodHandle initialAccessor = accessor.toMethodHandle(initialAccessMode(accessorVarType)); - final long offset = offset(holderType, fieldName, long.class, Reflection.getCallerClass()); - return makeLong(holderType, offset, underlying); + return StableFieldUpdaterGenerator.handle(accessor, initialAccessor, underlying); } - /** - * {@return a function that will replace any zero value returned by the provided - * {@code underlying} function with the provided {@code zeroReplacement}} - * @param underlying function to filter return values from - * @param zeroReplacement to replace any zero values returned by the {@code underlying} - * function. - * @param The function's type parameter - */ - public static ToIntFunction replaceIntZero(ToIntFunction underlying, int zeroReplacement) { - - record IntZeroReplacer(ToIntFunction underlying, int zeroReplacement) implements ToIntFunction { - @ForceInline - @Override - public int applyAsInt(T value) { - return replaceZero(underlying.applyAsInt(value), zeroReplacement); + private static VarHandle.AccessMode initialAccessMode(Class varType) { + if (varType.isPrimitive()) { + if (!Architecture.is64bit() && (varType.equals(long.class) || varType.equals(double.class))) { + // JLS (24) 17.4 states that 64-bit fields tear under plain memory semantics. + // Opaque semantics provides "Bitwise Atomicity" thus avoiding tearing. + return VarHandle.AccessMode.GET_OPAQUE; + } else { + // Reordering does not affect primitive values + // Todo: This does not establish a happens-before relation. + // Plain semantics suffice here as we are not dealing with a reference (for + // a reference, the internal state initialization can be reordered with + // other store ops). + return VarHandle.AccessMode.GET; } + } else { + // Acquire semantics is needed for reference variables to prevent the internal + // state initialization can be reordered with other store ops. + return VarHandle.AccessMode.GET_ACQUIRE; } + } - Objects.requireNonNull(underlying); - return new IntZeroReplacer<>(underlying, zeroReplacement); + static Class leafClass(Class type) { + return type.isArray() + ? leafClass(type.componentType()) + : type; } /** @@ -364,295 +310,74 @@ final class Holder { } /** - * {@return a function that will replace any zero value returned by the provided - * {@code underlying} function with the provided {@code zeroReplacement}} + * {@return a method handle that will replace any zero value returned by the provided + * {@code underlying} handle with the provided {@code zeroReplacement}} * @param underlying function to filter return values from * @param zeroReplacement to replace any zero values returned by the {@code underlying} - * function. - * @param The function's type parameter + * method handle. */ - public static ToLongFunction replaceLongZero(ToLongFunction underlying, long zeroReplacement) { + public static MethodHandle replaceLongZero(MethodHandle underlying, long zeroReplacement) { - record LongZeroReplacer(ToLongFunction underlying, long zeroReplacement) implements ToLongFunction { - @ForceInline - @Override - public long applyAsLong(T value) { - return replaceZero(underlying.applyAsLong(value), zeroReplacement); - } + final class Holder { + private static final MethodHandle RETURN_FILTER = + MhUtil.findStatic(LOCAL_LOOKUP, "replaceZero", MethodType.methodType(long.class, long.class, long.class)); } - Objects.requireNonNull(underlying); - return new LongZeroReplacer<>(underlying, zeroReplacement); + check(underlying, long.class); + return MethodHandles.filterReturnValue(underlying, + MethodHandles.insertArguments(Holder.RETURN_FILTER, 1, zeroReplacement)); } /** - * {@return a method handle that will replace any zero value returned by the provided - * {@code underlying} handle with the provided {@code zeroReplacement}} + * {@return a method handle that will replace any null value returned by the provided + * {@code underlying} handle with the provided {@code nullReplacement}} * @param underlying function to filter return values from - * @param zeroReplacement to replace any zero values returned by the {@code underlying} + * @param nullReplacement to replace any zero values returned by the {@code underlying} * method handle. */ - public static MethodHandle replaceLongZero(MethodHandle underlying, long zeroReplacement) { + public static MethodHandle replaceReferenceNull(MethodHandle underlying, Object nullReplacement) { final class Holder { private static final MethodHandle RETURN_FILTER = - MhUtil.findStatic(LOCAL_LOOKUP, "replaceZero", MethodType.methodType(long.class, long.class, long.class)); + MhUtil.findStatic(LOCAL_LOOKUP, "replaceNull", MethodType.methodType(Object.class, Object.class, Object.class)); } - - check(underlying, long.class); + check(underlying, Object.class); return MethodHandles.filterReturnValue(underlying, - MethodHandles.insertArguments(Holder.RETURN_FILTER, 1, zeroReplacement)); + MethodHandles.insertArguments(Holder.RETURN_FILTER, 1, nullReplacement)); } - // Also used reflectively + // Used reflectively @ForceInline private static int replaceZero(int value, int zeroReplacement) { - return value == 0 - ? zeroReplacement - : value; + return value == 0 ? zeroReplacement : value; } - // Also used reflectively + // Used reflectively @ForceInline private static long replaceZero(long value, long zeroReplacement) { - return value == 0 - ? zeroReplacement - : value; + return value == 0 ? zeroReplacement : value; } - - public static CallSite lazyOfInt(MethodHandles.Lookup lookup, - String unused, - VarHandle accessor, - MethodHandle underlying) { - check(accessor, int.class); - var adaptedUnderlying = checkAndAdapt(underlying, int.class); - var handle = MhUtil.findStatic(LOCAL_LOOKUP, - "ofInt", MethodType.methodType(ToIntFunction.class, VarHandle.class, MethodHandle.class)); - return new ConstantCallSite(MethodHandles.insertArguments(handle, 0, accessor, adaptedUnderlying)); + // Used reflectively + // Cannot reuse `Objects::requireNonNullElse` as it prohibits `null` replacements + @ForceInline + private static Object replaceNull(Object value, Object nullReplacement) { + return value == null ? nullReplacement : value; } - public static CallSite lazyOfLong(MethodHandles.Lookup lookup, - String unused, - VarHandle accessor, - MethodHandle underlying) { - check(accessor, long.class); - var adaptedUnderlying = checkAndAdapt(underlying, long.class); + public static CallSite lazyAtMostOnce(MethodHandles.Lookup lookup, + String unused, + VarHandle accessor, + MethodHandle underlying) { + Objects.requireNonNull(accessor); + Objects.requireNonNull(underlying); var handle = MhUtil.findStatic(LOCAL_LOOKUP, - "makeLong", MethodType.methodType(ToLongFunction.class, VarHandle.class, MethodHandle.class)); - return new ConstantCallSite(MethodHandles.insertArguments(handle, 0, accessor, adaptedUnderlying)); - } - - private static ToLongFunction makeLong(Class holderType, - long offset, - ToLongFunction underlying) { - if (Architecture.is64bit()) { - // We are also relying on the fact that the VM will not place 64-bit - // instance fields that can cross cache lines. - return new StableLongFieldUpdater<>(holderType, offset, underlying); - } else { - return new TearingStableLongFieldUpdater<>(holderType, offset, underlying); - } - } - - private static ToLongFunction makeLong(VarHandle accessor, - MethodHandle underlying) { - if (Architecture.is64bit()) { - // We are also relying on the fact that the VM will not place 64-bit - // instance fields that can cross cache lines. - return new StableLongFieldUpdaterVarHandle<>(accessor, underlying); - } else { - return new TearingStableLongFieldUpdaterVarHandle<>(accessor, underlying); - } - } - - private record StableIntFieldUpdater(Class holderType, - long offset, - ToIntFunction underlying) implements ToIntFunction { - - @ForceInline - @Override - public int applyAsInt(T t) { - checkInstanceOf(holderType, t); - // Plain semantics suffice here as we are not dealing with a reference (for - // a reference, the internal state initialization can be reordered with - // other store ops). JLS (24) 17.4 states that 64-bit fields tear under - // plain memory semantics. But, `int` is 32-bit. - int v = UNSAFE.getInt(t, offset); - if (v == 0) { - synchronized (t) { - v = UNSAFE.getIntAcquire(t, offset); - if (v == 0) { - v = underlying.applyAsInt(t); - UNSAFE.putIntRelease(t, offset, v); - } - } - } - return v; - } - } - - private record StableIntFieldUpdaterVarHandle(VarHandle accessor, - MethodHandle underlying) implements ToIntFunction { - - @ForceInline - @Override - public int applyAsInt(T t) { - // Plain semantics suffice here as we are not dealing with a reference (for - // a reference, the internal state initialization can be reordered with - // other store ops). JLS (24) 17.4 states that 64-bit fields tear under - // plain memory semantics. But, `int` is 32-bit. - int v = (int) accessor.get(t); - if (v == 0) { - synchronized (t) { - v = (int) accessor.getAcquire(t); - if (v == 0) { - try { - v = (int) underlying.invokeExact(t); - } catch (Throwable e) { - throw new RuntimeException(e); - } - accessor.setRelease(t, v); - } - } - } - return v; - } - } - - private record StableLongFieldUpdater(Class holderType, - long offset, - ToLongFunction underlying) implements ToLongFunction { - - @ForceInline - @Override - public long applyAsLong(T t) { - checkInstanceOf(holderType, t); - // Plain semantics suffice here as we are not dealing with a reference (for - // a reference, the internal state initialization can be reordered with - // other store ops). JLS (24) 17.4 states that 64-bit fields tear under - // plain memory semantics. But, the VM is 64-bit. - long v = UNSAFE.getLong(t, offset); - if (v == 0) { - synchronized (t) { - v = UNSAFE.getLongAcquire(t, offset); - if (v == 0) { - v = underlying.applyAsLong(t); - UNSAFE.putLongRelease(t, offset, v); - } - } - } - return v; - } - } - - private record TearingStableLongFieldUpdater(Class holderType, - long offset, - ToLongFunction underlying) implements ToLongFunction { - - @ForceInline - @Override - public long applyAsLong(T t) { - checkInstanceOf(holderType, t); - // Plain semantics suffice here as we are not dealing with a reference (for - // a reference, the internal state initialization can be reordered with - // other store ops). JLS (24) 17.4 states that 64-bit fields tear under - // plain memory semantics and this VM is not 64-bit. - long v = UNSAFE.getLongOpaque(t, offset); - if (v == 0) { - synchronized (t) { - v = UNSAFE.getLongAcquire(t, offset); - if (v == 0) { - v = underlying.applyAsLong(t); - UNSAFE.putLongRelease(t, offset, v); - } - } - } - return v; - } - } - - private record StableLongFieldUpdaterVarHandle(VarHandle accessor, - MethodHandle underlying) implements ToLongFunction { - - @ForceInline - @Override - public long applyAsLong(T t) { - // Plain semantics suffice here as we are not dealing with a reference (for - // a reference, the internal state initialization can be reordered with - // other store ops). JLS (24) 17.4 states that 64-bit fields tear under - // plain memory semantics. But, the VM is 64-bit. - long v = (long) accessor.get(t); - if (v == 0) { - synchronized (t) { - v = (long) accessor.getAcquire(t); - if (v == 0) { - try { - v = (long) underlying.invokeExact(t); - } catch (Throwable e) { - throw new RuntimeException(e); - } - accessor.setRelease(t, v); - } - } - } - return v; - } - } - - private record TearingStableLongFieldUpdaterVarHandle(VarHandle accessor, - MethodHandle underlying) implements ToLongFunction { - - @ForceInline - @Override - public long applyAsLong(T t) { - // Plain semantics suffice here as we are not dealing with a reference (for - // a reference, the internal state initialization can be reordered with - // other store ops). JLS (24) 17.4 states that 64-bit fields tear under - // plain memory semantics and this VM is not 64-bit. - long v = (long) accessor.getOpaque(t); - if (v == 0) { - synchronized (t) { - v = (long) accessor.getAcquire(t); - if (v == 0) { - try { - v = (long) underlying.invokeExact(t); - } catch (Throwable e) { - throw new RuntimeException(e); - } - accessor.setRelease(t, v); - } - } - } - return v; - } + "atMostOnce", MethodType.methodType(MethodHandle.class, VarHandle.class, MethodHandle.class)); + return new ConstantCallSite(MethodHandles.insertArguments(handle, 0, accessor, underlying)); } // Static support functions - @ForceInline - private static void checkInstanceOf(Class holderType, Object t) { - if (!holderType.isInstance(t)) { - throw new IllegalArgumentException("The provided t is not an instance of " + holderType); - } - } - - private static void check(VarHandle accessor, Class varType) { - // Implicit null check - if (accessor.varType() != varType) { - throw new IllegalArgumentException("Illegal accessor: " + accessor); - } - } - - private static MethodHandle checkAndAdapt(MethodHandle underlying, Class returnType) { - check(underlying, returnType); - final var underlyingType = underlying.type(); - if (!underlyingType.parameterType(0).equals(Object.class)) { - underlying = underlying.asType(underlyingType.changeParameterType(0, Object.class)); - } - return underlying; - } - private static void check(MethodHandle underlying, Class returnType) { // Implicit null check final var underlyingType = underlying.type(); @@ -661,41 +386,4 @@ private static void check(MethodHandle underlying, Class returnType) { } } - private static long offset(Class holderType, - String fieldName, - Class fieldType, - Class caller) { - final Field field; - try { - field = findField(holderType, fieldName); - int modifiers = field.getModifiers(); - if (Modifier.isFinal(modifiers)) { - throw illegalField("non final fields", field); - } - ReflectUtil.ensureMemberAccess(caller, holderType, null, modifiers); - if (field.getType() != fieldType) { - throw illegalField("fields of type '" + fieldType + "'", field); - } - } catch (NoSuchFieldException | IllegalAccessException e) { - throw new IllegalArgumentException(e); - } - return UNSAFE.objectFieldOffset(field); - } - - private static IllegalArgumentException illegalField(String msg, Field field) { - return new IllegalArgumentException("Only " + msg + " are supported. The provided field is '" + field + "'"); - } - - private static Field findField(Class holderType, String fieldName) throws NoSuchFieldException { - if (holderType.equals(Object.class)) { - throw new NoSuchFieldException("'" + fieldName + "' in '" + holderType + "'"); - } - for (Field f : holderType.getDeclaredFields()) { - if (f.getName().equals(fieldName)) { - return f; - } - } - return findField(holderType.getSuperclass(), fieldName); - } - } diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdaterGenerator.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdaterGenerator.java new file mode 100644 index 0000000000000..022a89da6d967 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdaterGenerator.java @@ -0,0 +1,341 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.lang.stable; + +import jdk.internal.invoke.MhUtil; +import jdk.internal.vm.annotation.ForceInline; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.invoke.VarHandle; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Stable field updater code generator. + */ +final class StableFieldUpdaterGenerator { + + private StableFieldUpdaterGenerator() {} + + private static final MethodHandles.Lookup LOCAL_LOOKUP = MethodHandles.lookup(); + + record Shape(Class varType, List> coordinateTypes){ + + static Shape of(VarHandle varHandle) { + final List> shapeCoordinateTypes = new ArrayList<>(varHandle.coordinateTypes().size()); + for (Class coordinate: varHandle.coordinateTypes()) { + shapeCoordinateTypes.add(toObjectIfReference(coordinate)); + } + return new Shape(toObjectIfReference(varHandle.varType()), shapeCoordinateTypes); + } + + static Class toObjectIfReference(Class type) { + // Todo: fix arrays of references + return type.isPrimitive() || type.isArray() + ? type + : Object.class; + } + + } + + // Used to hold hand-rolled and bespoke resolvers and shapeConstructors + record Maker(MethodHandle applyResolver, MethodHandle shapeConstructor) { + MethodHandle make(VarHandle accessor, MethodHandle initialAccessor, MethodHandle underlying) { + try { + return applyResolver + .bindTo(shapeConstructor + .invoke(accessor, initialAccessor, underlying)); + } catch (Throwable t) { + throw new InternalError(t); + } + } + } + + static MethodHandle handle(VarHandle accessor, + MethodHandle initialAccessor, + MethodHandle underlying) { + + final var underlyingType = underlying.type(); + + // Allow `invokeExact()` of the `apply(Object)` method + final var adaptedUnderlying = underlyingType.parameterType(0).equals(Object.class) + || underlyingType.parameterType(0).isArray() + ? underlying + : underlying.asType(underlyingType.changeParameterType(0, Object.class)); + + final var shape = Shape.of(accessor); + final var maker = MAKERS.computeIfAbsent(shape, + s -> { + // System.out.println("shape = " + s); + return bespokeMaker(accessor, initialAccessor, underlying); + }); + final var handle = maker.make(accessor, initialAccessor, adaptedUnderlying); + + return accessor.coordinateTypes().size() == 1 + ? handle.asType(MethodType.methodType(handle.type().returnType(), underlying.type().parameterType(0))) + : handle; + + } + + private static Maker bespokeMaker(VarHandle accessor, + MethodHandle initialAccessor, + MethodHandle underlying) { + + // Todo: Implement this using bytecode generation + throw new UnsupportedOperationException("No support for accessor: " + accessor + + ", initialAccessor: " + initialAccessor + ", underlying: " + underlying); + } + + + // Hand-rolled classes + + static final MethodHandle INT_ONCE_APPLY = + MhUtil.findVirtual(LOCAL_LOOKUP, IntOnce.class, "apply", + MethodType.methodType(int.class, Object.class)); + + record IntOnce(VarHandle accessor, + MethodHandle initialAccessor, + MethodHandle underlying) { + + // Used reflectively + @ForceInline + public int apply(Object t) { + try { + final int v = (int) initialAccessor.invoke(t); + return v == 0 ? applySlowPath(t) : v; + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + + public int applySlowPath(Object t) { + preventReentry(this); + int v; + synchronized (this) { + v = (int) accessor.getAcquire(t); + if (v == 0) { + try { + v = (int) underlying.invokeExact(t); + } catch (Throwable e) { + throw new RuntimeException(e); + } + accessor.setRelease(t, v); + } + } + return v; + } + } + + static final MethodHandle LONG_ONCE_APPLY = + MhUtil.findVirtual(LOCAL_LOOKUP, LongOnce.class, "apply", + MethodType.methodType(long.class, Object.class)); + + record LongOnce(VarHandle accessor, + MethodHandle initialAccessor, + MethodHandle underlying) { + + // Used reflectively + @ForceInline + public long apply(Object t) { + try { + long v = (long) initialAccessor.invoke(t); + return v == 0 ? applySlowPath(t) : v; + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + + public long applySlowPath(Object t) { + preventReentry(this); + long v; + synchronized (this) { + v = (long) accessor.getAcquire(t); + if (v == 0) { + try { + v = (long) underlying.invokeExact(t); + } catch (Throwable e) { + throw new RuntimeException(e); + } + accessor.setRelease(t, v); + } + } + return v; + } + + } + + static final MethodHandle REFERENCE_ONCE_APPLY = + MhUtil.findVirtual(LOCAL_LOOKUP, ReferenceOnce.class, "apply", + MethodType.methodType(Object.class, Object.class)); + + record ReferenceOnce(VarHandle accessor, + MethodHandle initialAccessor, + MethodHandle underlying) { + + // Used reflectively + @ForceInline + public Object apply(Object t) { + try { + final Object v = (Object) initialAccessor.invoke(t); + return v == null ? applySlowPath(t) : v; + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + + public Object applySlowPath(Object t) { + preventReentry(this); + Object v; + synchronized (this) { + v = (Object) accessor.getAcquire(t); + if (v == null) { + try { + v = (int) underlying.invokeExact(t); + } catch (Throwable e) { + throw new RuntimeException(e); + } + accessor.setRelease(t, v); + } + } + return v; + } + } + + // Arrays + + static final MethodHandle INT_ARRAY_ONCE_APPLY = + MhUtil.findVirtual(LOCAL_LOOKUP, IntArrayOnce.class, "apply", + MethodType.methodType(int.class, int[].class, int.class)); + + record IntArrayOnce(VarHandle accessor, + MethodHandle initialAccessor, + MethodHandle underlying) { + + // Used reflectively + @ForceInline + public int apply(int[] array, int i) { + try { + final int v = (int) initialAccessor.invoke(array, i); + return v == 0 ? applySlowPath(array, i) : v; + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + + public int applySlowPath(int[] array, int i) { + preventReentry(this); + int v; + synchronized (this) { + v = (int) accessor.getAcquire(array, i); + if (v == 0) { + try { + v = (int) underlying.invokeExact(array, i); + } catch (Throwable e) { + throw new RuntimeException(e); + } + accessor.setRelease(array, i, v); + } + } + return v; + } + } + + static final MethodHandle LONG_ARRAY_ONCE_APPLY = + MhUtil.findVirtual(LOCAL_LOOKUP, LongArrayOnce.class, "apply", + MethodType.methodType(long.class, long[].class, int.class)); + + record LongArrayOnce(VarHandle accessor, + MethodHandle initialAccessor, + MethodHandle underlying) { + + // Used reflectively + @ForceInline + public long apply(long[] array, int i) { + try { + final long v = (long) initialAccessor.invoke(array, i); + return v == 0 ? applySlowPath(array, i) : v; + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + + public long applySlowPath(long[] array, int i) { + preventReentry(this); + long v; + synchronized (this) { + v = (long) accessor.getAcquire(array, i); + if (v == 0) { + try { + v = (long) underlying.invokeExact(array, i); + } catch (Throwable e) { + throw new RuntimeException(e); + } + accessor.setRelease(array, i, v); + } + } + return v; + } + } + + + // This method is not annotated with @ForceInline as it is always called + // in a slow path. + static void preventReentry(Object obj) { + if (Thread.holdsLock(obj)) { + throw new IllegalStateException("Recursive initialization of a stable value is illegal"); + } + } + + // Caching of Shapes + + private static final Map MAKERS = new ConcurrentHashMap<>(); + + static { + // Add hand-rolled classes + + // Scalars + MAKERS.put(new Shape(int.class, List.of(Object.class)), new Maker(INT_ONCE_APPLY, findMakerConstructor(IntOnce.class))); + MAKERS.put(new Shape(long.class, List.of(Object.class)), new Maker(LONG_ONCE_APPLY, findMakerConstructor(LongOnce.class))); + MAKERS.put(new Shape(Object.class, List.of(Object.class)), new Maker(REFERENCE_ONCE_APPLY, findMakerConstructor(ReferenceOnce.class))); + // Arrays + MAKERS.put(new Shape(int.class, List.of(int[].class, int.class)), new Maker(INT_ARRAY_ONCE_APPLY, findMakerConstructor(IntArrayOnce.class))); + MAKERS.put(new Shape(long.class, List.of(long[].class, int.class)), new Maker(LONG_ARRAY_ONCE_APPLY, findMakerConstructor(LongArrayOnce.class))); + } + + private static MethodHandle findMakerConstructor(Class type) { + try { + return LOCAL_LOOKUP.findConstructor(type, + MethodType.methodType(void.class, VarHandle.class, MethodHandle.class, MethodHandle.class)); + } catch (ReflectiveOperationException e) { + throw new InternalError(e); + } + } + +} diff --git a/test/jdk/java/lang/StableValue/StableFieldUpdaterExampleTest.java b/test/jdk/java/lang/StableValue/StableFieldUpdaterExampleTest.java index 9a969c0ed4151..d8938bf9d1cc5 100644 --- a/test/jdk/java/lang/StableValue/StableFieldUpdaterExampleTest.java +++ b/test/jdk/java/lang/StableValue/StableFieldUpdaterExampleTest.java @@ -33,8 +33,10 @@ import org.junit.jupiter.api.Test; import java.lang.Override; +import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; +import java.lang.invoke.VarHandle; import java.util.Objects; import java.util.function.ToIntFunction; import java.util.function.ToLongFunction; @@ -120,86 +122,23 @@ public final class LazyFoo { private final Bar bar; private final Baz baz; - private static final ToIntFunction HASH_UPDATER = - StableFieldUpdater.ofInt(LazyFoo.class, "hash", - l -> Objects.hash(l.bar, l.baz)); + private static final MethodHandle HASH_UPDATER; - @Stable - private int hash; - - public LazyFoo(Bar bar, Baz baz) { - this.bar = bar; - this.baz = baz; + static { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + try { + VarHandle accessor = lookup.findVarHandle(LazyFoo.class, "hash", int.class); + MethodHandle underlying = lookup.findStatic(LazyFoo.class, "hashCodeFor", MethodType.methodType(int.class, LazyFoo.class)); + HASH_UPDATER = StableFieldUpdater.atMostOnce(accessor, underlying); + } catch (ReflectiveOperationException e) { + throw new InternalError(e); + } } - @Override - public boolean equals(Object o) { - return o instanceof Foo that - && Objects.equals(this.bar, that.bar) - && Objects.equals(this.baz, that.baz); - } - - @Override - public int hashCode() { - return HASH_UPDATER.applyAsInt(this); - } - } - - static - - public final class LazySpecifiedFoo { - - private final Bar bar; - private final Baz baz; - - private static final ToLongFunction HASH_UPDATER = - StableFieldUpdater.ofLong(LazySpecifiedFoo.class, "hash", - StableFieldUpdater.replaceLongZero(LazySpecifiedFoo::hashCodeFor, 1L << 32)); - - @Stable - private long hash; - - public LazySpecifiedFoo(Bar bar, Baz baz) { - this.bar = bar; - this.baz = baz; - } - - @Override - public boolean equals(Object o) { - return (o instanceof Foo that) - && Objects.equals(this.bar, that.bar) - && Objects.equals(this.baz, that.baz); - } - - @Override - public int hashCode() { - return (int) HASH_UPDATER.applyAsLong(this); - } - - static long hashCodeFor(LazySpecifiedFoo foo) { - return (foo.bar == null && foo.baz == null) ? 0 : Objects.hash(foo.bar, foo.baz); - } - - } - - static - - public final class MhFoo { - - private final Bar bar; - private final Baz baz; - - private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); - - private static final ToIntFunction HASH_UPDATER = - StableFieldUpdater.ofInt( - MhUtil.findVarHandle(LOOKUP, "hash", int.class), - MhUtil.findStatic(LOOKUP, "hashCodeFor", MethodType.methodType(int.class, MhFoo.class))); - @Stable private int hash; - public MhFoo(Bar bar, Baz baz) { + public LazyFoo(Bar bar, Baz baz) { this.bar = bar; this.baz = baz; } @@ -213,11 +152,14 @@ public boolean equals(Object o) { @Override public int hashCode() { - return HASH_UPDATER.applyAsInt(this); + try { + return (int) HASH_UPDATER.invokeExact(this); + } catch (Throwable e) { + throw new RuntimeException(e); + } } - // Used reflectively - static int hashCodeFor(MhFoo foo) { + private static int hashCodeFor(LazyFoo foo) { return Objects.hash(foo.bar, foo.baz); } } @@ -232,12 +174,4 @@ void lazy() { assertEquals(Objects.hash(1, 2), foo.hashCode()); } - @Test - void lazySpec() { - var foo = new LazySpecifiedFoo(null, null); - assertEquals(0, foo.hashCode()); - assertEquals(0, foo.hashCode()); - assertEquals(1L << 32, foo.hash); - } - } diff --git a/test/jdk/java/lang/StableValue/StableFieldUpdaterTest.java b/test/jdk/java/lang/StableValue/StableFieldUpdaterTest.java index 02338692072b0..67cb299caaa5f 100644 --- a/test/jdk/java/lang/StableValue/StableFieldUpdaterTest.java +++ b/test/jdk/java/lang/StableValue/StableFieldUpdaterTest.java @@ -30,51 +30,139 @@ import jdk.internal.invoke.MhUtil; import jdk.internal.lang.stable.StableFieldUpdater; +import org.junit.jupiter.api.Named; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; +import java.lang.foreign.Arena; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.MemorySegment; import java.lang.invoke.CallSite; import java.lang.invoke.ConstantCallSite; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; +import java.lang.invoke.VarHandle; +import java.lang.invoke.WrongMethodTypeException; +import java.util.Objects; import java.util.function.Function; import java.util.function.IntFunction; import java.util.function.ToIntFunction; import java.util.function.ToLongFunction; import java.util.stream.Stream; +import static java.lang.foreign.ValueLayout.JAVA_INT; import static org.junit.jupiter.api.Assertions.*; final class StableFieldUpdaterTest { private static final String STRING = "Abc"; + private static final int SIZE = 8; + + + private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); + + private static final VarHandle ACCESSOR; + private static final MethodHandle UNDERLYING; + private static final MethodHandle DOUBLE_UNDERLYING; + + static { + try { + ACCESSOR = LOOKUP.findVarHandle(Foo.class, "hash", int.class); + UNDERLYING = LOOKUP.findStatic(Foo.class, "hashCodeFor", MethodType.methodType(int.class, Foo.class)); + DOUBLE_UNDERLYING = LOOKUP.findStatic(StableFieldUpdaterTest.class, "doubleFrom", MethodType.methodType(double.class, Foo.class)); + } catch (ReflectiveOperationException e) { + throw new InternalError(e); + } + } @Test void invariants() { - assertThrows(NullPointerException.class, () -> StableFieldUpdater.ofInt(null, "a", _ -> 0)); - assertThrows(NullPointerException.class, () -> StableFieldUpdater.ofInt(String.class, null, _ -> 0)); - assertThrows(NullPointerException.class, () -> StableFieldUpdater.ofInt(Foo.class, "hash", null)); - var xi = assertThrows(IllegalArgumentException.class, () -> StableFieldUpdater.ofInt(Foo.class, "dummy", _ -> 0)); - assertEquals("Only fields of type 'int' are supported. The provided field is 'long StableFieldUpdaterTest$Foo.dummy'", xi.getMessage()); - - assertThrows(NullPointerException.class, () -> StableFieldUpdater.ofLong(null, "a", _ -> 0)); - assertThrows(NullPointerException.class, () -> StableFieldUpdater.ofLong(String.class, null, _ -> 0)); - assertThrows(NullPointerException.class, () -> StableFieldUpdater.ofLong(Foo.class, "hash", null)); - var xl = assertThrows(IllegalArgumentException.class, () -> StableFieldUpdater.ofLong(Foo.class, "hash", _ -> 0)); - assertEquals("Only fields of type 'long' are supported. The provided field is 'int StableFieldUpdaterTest$Foo.hash'", xl.getMessage()); - - assertThrows(NullPointerException.class, () -> StableFieldUpdater.replaceIntZero((ToIntFunction)null, 1)); + assertThrows(NullPointerException.class, () -> StableFieldUpdater.atMostOnce(null, UNDERLYING)); + assertThrows(NullPointerException.class, () -> StableFieldUpdater.atMostOnce(ACCESSOR, null)); + var xi = assertThrows(IllegalArgumentException.class, () -> StableFieldUpdater.atMostOnce(ACCESSOR, DOUBLE_UNDERLYING)); + assertEquals("Return type mismatch: accessor: VarHandle[varType=int, coord=[class StableFieldUpdaterTest$Foo]], underlying: MethodHandle(Foo)double", xi.getMessage()); + assertThrows(NullPointerException.class, () -> StableFieldUpdater.replaceIntZero((MethodHandle) null, 1)); - assertThrows(NullPointerException.class, () -> StableFieldUpdater.replaceLongZero((ToLongFunction)null, 1L)); assertThrows(NullPointerException.class, () -> StableFieldUpdater.replaceLongZero((MethodHandle) null, 1L)); } + @Test + void multiCoordinateIntArray() throws Throwable { + var accessor = MethodHandles.arrayElementVarHandle(int[].class); + var underlying = LOOKUP.findStatic(StableFieldUpdaterTest.class, "multiCoordinateMethod", + MethodType.methodType(int.class, int[].class, int.class)); + var atMostOnce = StableFieldUpdater.atMostOnce(accessor, underlying); + int index = 1; + int[] array = new int[SIZE]; + int val = (int) atMostOnce.invokeExact(array, index); + assertEquals(index, val); + } + + @Test + void multiCoordinateLongArray() throws Throwable { + var accessor = MethodHandles.arrayElementVarHandle(long[].class); + var underlying = LOOKUP.findStatic(StableFieldUpdaterTest.class, "multiCoordinateMethod", + MethodType.methodType(long.class, long[].class, int.class)); + var atMostOnce = StableFieldUpdater.atMostOnce(accessor, underlying); + int index = 1; + long[] array = new long[SIZE]; + long val = (long) atMostOnce.invokeExact(array, index); + assertEquals(index, val); + } + + @Test + void multiCoordinateSegment() throws Throwable { + var layout = MemoryLayout.sequenceLayout(SIZE, JAVA_INT); + var accessor = layout.varHandle(MemoryLayout.PathElement.sequenceElement()); + accessor = MethodHandles.insertCoordinates(accessor, 1, 0L); // zero offset + var underlying = LOOKUP.findStatic(StableFieldUpdaterTest.class, "multiCoordinateMethod", + MethodType.methodType(int.class, MemorySegment.class, long.class)); + var atMostOnce = StableFieldUpdater.atMostOnce(accessor, underlying); + long index = 1L; + try (var arena = Arena.ofConfined()) { + var segment = arena.allocate(layout); + int val = (int) atMostOnce.invokeExact(segment, index); + assertEquals(index, val); + } + } + + // Used reflectively: int[], int + static int multiCoordinateMethod(int[] array, int index) { + return index; + } + + // Used reflectively: + static long multiCoordinateMethod(long[] array, int index) { + return index; + } + + // Used reflectively + static int multiCoordinateMethod(MemorySegment segment, long index) { + return (int) index; + } + + @Test + void returnedHandleTypes() { + MethodHandle handle = StableFieldUpdater.atMostOnce(ACCESSOR, UNDERLYING); + assertEquals(int.class, handle.type().returnType()); + assertEquals(Foo.class, handle.type().parameterType(0)); + assertEquals(1, handle.type().parameterCount()); + } + + @Test + void foo() throws Throwable { + MethodHandle handle = StableFieldUpdater.atMostOnce(ACCESSOR, UNDERLYING); + Foo foo = new Foo(STRING); + int hash = (int) handle.invokeExact(foo); + assertEquals(STRING.hashCode(), hash); + } + @ParameterizedTest @MethodSource("fooConstructors") - void basic(Function namedConstructor) { - final HasHashField foo = namedConstructor.apply(STRING); + void basic(Function ctor) { + final HasHashField foo = ctor.apply(STRING); assertEquals(0L, foo.hash()); int actual = foo.hashCode(); assertEquals(STRING.hashCode(), actual); @@ -82,63 +170,50 @@ void basic(Function namedConstructor) { } @Test - void recordFoo() { - var recordFoo = new RecordFoo(STRING, 0); - // The field is `final` - var x = assertThrows(IllegalArgumentException.class, - () -> StableFieldUpdater.ofInt(RecordFoo.class, "hash", _ -> 0)); - assertEquals("Only non final fields are supported. The provided field is 'private final int StableFieldUpdaterTest$RecordFoo.hash'", x.getMessage()); - } + void recordFoo() throws ReflectiveOperationException { + record RecordFoo(String string, int hash) { - @SuppressWarnings({"rawtypes", "unchecked"}) - @Test - void uncheckedCall() { - // Use a raw type - ToIntFunction updater = StableFieldUpdater.ofInt(Foo.class, "hash", f -> f.string.hashCode()); - var object = new Object(); - var x = assertThrows(IllegalArgumentException.class, () -> updater.applyAsInt(object)); - assertEquals("The provided t is not an instance of class StableFieldUpdaterTest$Foo", x.getMessage()); + private int hashCodeFor() { + return string.hashCode(); + } + + } + var accessor = LOOKUP.findVarHandle(RecordFoo.class, "hash", int.class); + var underlying = LOOKUP.findVirtual(RecordFoo.class, "hashCodeFor", MethodType.methodType(int.class)); + + accessor.isAccessModeSupported(VarHandle.AccessMode.SET); + + // The field is `final` + var x = assertThrows(IllegalArgumentException.class, () -> StableFieldUpdater.atMostOnce(accessor, underlying)); + assertEquals("The accessor is read only: VarHandle[varType=int, coord=[class StableFieldUpdaterTest$1RecordFoo]]", x.getMessage()); } @Test - void lazyOfInt() throws Throwable { - var lookup = MethodHandles.lookup(); - CallSite callSite = StableFieldUpdater.lazyOfInt(lookup, "", - MhUtil.findVarHandle(lookup, SimpleMhFoo.class, "hash", int.class), - MhUtil.findStatic(lookup,SimpleMhFoo.class, "computeHash", MethodType.methodType(int.class, SimpleMhFoo.class))); + void wrongReceiver() { + var handle = StableFieldUpdater.atMostOnce(ACCESSOR, UNDERLYING); + var wrongType = new InheritingFoo(STRING); - @SuppressWarnings("unchecked") - ToIntFunction hasher = (ToIntFunction) callSite.getTarget().invoke(); + assertThrows(WrongMethodTypeException.class, () -> { + int hash = (int) handle.invokeExact(wrongType); + }); - var foo = new SimpleMhFoo(STRING); - int hash = hasher.applyAsInt(foo); - assertEquals(STRING.hashCode(), hash); + assertThrows(ClassCastException.class, () -> { + int hash = (int) handle.invoke(wrongType); + }); } @Test - void lazyOfLong() throws Throwable { + void lazyAtMostOnce() throws Throwable { var lookup = MethodHandles.lookup(); - CallSite callSite = StableFieldUpdater.lazyOfLong(lookup, "", - MhUtil.findVarHandle(lookup, LongMhFoo.class, "hash", long.class), - MhUtil.findVirtual(lookup, LongMhFoo.class, "hash0", MethodType.methodType(long.class))); + CallSite callSite = StableFieldUpdater.lazyAtMostOnce(lookup, "", ACCESSOR, UNDERLYING); - @SuppressWarnings("unchecked") - ToLongFunction hasher = (ToLongFunction) callSite.getTarget().invoke(); + MethodHandle hasher = (MethodHandle) callSite.getTarget().invoke(); - var foo = new LongMhFoo(STRING); - long hash = hasher.applyAsLong(foo); + var foo = new Foo(STRING); + int hash = (int) hasher.invoke(foo); assertEquals(STRING.hashCode(), hash); } - @Test - void replaceIntZeroFunction() { - int zeroReplacement = -1; - ToIntFunction underlying = i -> i; - var mod = StableFieldUpdater.replaceIntZero(underlying, zeroReplacement); - assertEquals(1, mod.applyAsInt(1)); - assertEquals(zeroReplacement, mod.applyAsInt(0)); - } - @Test void replaceIntZeroHandle() throws Throwable { int zeroReplacement = -1; @@ -148,15 +223,6 @@ void replaceIntZeroHandle() throws Throwable { assertEquals(zeroReplacement, (int) mod.invoke(0)); } - @Test - void replaceLongZeroFunction() { - long zeroReplacement = -1; - ToLongFunction underlying = i -> i; - var mod = StableFieldUpdater.replaceLongZero(underlying, zeroReplacement); - assertEquals(1L, mod.applyAsLong(1L)); - assertEquals(zeroReplacement, mod.applyAsLong(0L)); - } - @Test void replaceLongZeroHandle() throws Throwable { long zeroReplacement = -1; @@ -168,8 +234,19 @@ void replaceLongZeroHandle() throws Throwable { static final class Foo implements HasHashField { - private static final ToIntFunction UPDATER = - StableFieldUpdater.ofInt(Foo.class, "hash", f -> f.string.hashCode()); + private static final MethodHandle HASH_UPDATER; + + static { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + try { + VarHandle accessor = lookup.findVarHandle(Foo.class, "hash", int.class); + MethodHandle underlying = lookup.findStatic(Foo.class, "hashCodeFor", MethodType.methodType(int.class, Foo.class)); + HASH_UPDATER = StableFieldUpdater.atMostOnce(accessor, underlying); + } catch (ReflectiveOperationException e) { + throw new InternalError(e); + } + } + private final String string; int hash; @@ -181,19 +258,38 @@ public Foo(String string) { @Override public int hashCode() { - return UPDATER.applyAsInt(this); + try { + return (int) HASH_UPDATER.invokeExact(this); + } catch (Throwable e) { + throw new RuntimeException(e); + } } @Override public long hash() { return hash; } + + private static int hashCodeFor(Foo foo) { + return foo.string.hashCode(); + } } static final class LongFoo implements HasHashField { - private static final ToLongFunction UPDATER = - StableFieldUpdater.ofLong(LongFoo.class, "hash", f -> f.string.hashCode()); + private static final MethodHandle HASH_UPDATER; + + static { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + try { + VarHandle accessor = lookup.findVarHandle(LongFoo.class, "hash", long.class); + MethodHandle underlying = lookup.findStatic(LongFoo.class, "hashCodeFor", MethodType.methodType(long.class, LongFoo.class)); + HASH_UPDATER = StableFieldUpdater.atMostOnce(accessor, underlying); + } catch (ReflectiveOperationException e) { + throw new InternalError(e); + } + } + private final String string; long hash; @@ -205,7 +301,11 @@ public LongFoo(String string) { @Override public int hashCode() { - return (int) UPDATER.applyAsLong(this); + try { + return (int) (long) HASH_UPDATER.invokeExact(this); + } catch (Throwable e) { + throw new RuntimeException(e); + } } @Override @@ -213,145 +313,77 @@ public long hash() { return hash; } - } - - record RecordFoo(String string, int hash) {} - - static final class InheritingFoo extends AbstractFoo implements HasHashField { - private static final ToIntFunction UPDATER = - StableFieldUpdater.ofInt(InheritingFoo.class, "hash", f -> f.string.hashCode()); - - public InheritingFoo(String string) { - super(string); - } - - @Override - public int hashCode() { - return UPDATER.applyAsInt(this); - } - } - - static abstract class AbstractFoo implements HasHashField { - final String string; - int hash; - - public AbstractFoo(String string) { - this.string = string; + private static long hashCodeFor(LongFoo foo) { + return foo.string.hashCode(); } - @Override - public long hash() { - return hash; - } } - static final class MhFoo implements HasHashField { - - private static final MethodHandle HASH_MH = MhUtil.findVirtual(MethodHandles.lookup(), "hash0", MethodType.methodType(int.class)); - - private static final ToIntFunction UPDATER = - StableFieldUpdater.ofInt(MhUtil.findVarHandle(MethodHandles.lookup(), "hash", int.class), HASH_MH); - private final String string; - - int hash; - long dummy; - public MhFoo(String string) { - this.string = string; - } - @Override - public int hashCode() { - return UPDATER.applyAsInt(this); - } - - public int hash0() { - return string.hashCode(); - } + static final class InheritingFoo extends AbstractFoo implements HasHashField { - @Override - public long hash() { - return hash; + private static final MethodHandle HASH_UPDATER; + + static { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + try { + VarHandle accessor = lookup.findVarHandle(InheritingFoo.class, "hash", int.class); + MethodHandle underlying = lookup.findStatic(InheritingFoo.class, "hashCodeFor", MethodType.methodType(int.class, InheritingFoo.class)); + HASH_UPDATER = StableFieldUpdater.atMostOnce(accessor, underlying); + } catch (ReflectiveOperationException e) { + throw new InternalError(e); + } } - } - - static final class SimpleMhFoo implements HasHashField { - - private static final ToIntFunction UPDATER = - StableFieldUpdater.ofInt(MethodHandles.lookup(), "hash", "computeHash"); - - private final String string; - - int hash; - long dummy; - - public SimpleMhFoo(String string) { - this.string = string; + public InheritingFoo(String string) { + super(string); } @Override public int hashCode() { - return UPDATER.applyAsInt(this); + try { + return (int) HASH_UPDATER.invokeExact(this); + } catch (Throwable e) { + throw new RuntimeException(e); + } } - @Override - public long hash() { - return hash; - } - - // Used reflectively - private static int computeHash(SimpleMhFoo target) { - return target.string.hashCode(); + private static int hashCodeFor(InheritingFoo foo) { + return foo.string.hashCode(); } - } - static final class LongMhFoo implements HasHashField { - - private static final MethodHandle HASH_MH = MhUtil.findVirtual(MethodHandles.lookup(), "hash0", MethodType.methodType(long.class)); - - private static final ToLongFunction UPDATER = - StableFieldUpdater.ofLong(MhUtil.findVarHandle(MethodHandles.lookup(), "hash", long.class), HASH_MH); - private final String string; - - long hash; - long dummy; + static abstract class AbstractFoo implements HasHashField { + final String string; + int hash; - public LongMhFoo(String string) { + public AbstractFoo(String string) { this.string = string; } - @Override - public int hashCode() { - return (int) UPDATER.applyAsLong(this); - } - - public long hash0() { - return string.hashCode(); - } - @Override public long hash() { return hash; } - } interface HasHashField { long hash(); } + // Illegal underlying function for int and long + private static double doubleFrom(Foo foo) { + return 1; + } + // Apparently, `hashCode()` is invoked if we create a stream of just `HasHashField` // instances so we provide the associated constructors instead. static Stream> fooConstructors() { return Stream.of( Foo::new, LongFoo::new, - MhFoo::new, - LongMhFoo::new, - InheritingFoo::new, - SimpleMhFoo::new + InheritingFoo::new ); } diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableFieldUpdaterBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableFieldUpdaterBenchmark.java index ef36e5e58b3d2..b05684a89d5c0 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/StableFieldUpdaterBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableFieldUpdaterBenchmark.java @@ -40,6 +40,7 @@ import java.lang.invoke.MethodType; import java.lang.invoke.VarHandle; import java.net.URI; +import java.sql.Ref; import java.util.concurrent.TimeUnit; import java.util.function.ToIntFunction; @@ -59,12 +60,10 @@ public class StableFieldUpdaterBenchmark { private static final Base BASE = new Base(STRING); private static final Updater UPDATER = new Updater(STRING); - private static final MhUpdater MH_UPDATER = new MhUpdater(STRING); private static final URI U_R_I = URI.create(STRING); private final Base base = new Base(STRING); private final Updater updater = new Updater(STRING); - private final MhUpdater mhUpdater = new MhUpdater(STRING); private static final URI uri = URI.create(STRING); @Benchmark @@ -77,16 +76,6 @@ public int base() { return base.hashCode(); } - @Benchmark - public int mhUpdaterStatic() { - return MH_UPDATER.hashCode(); - } - - @Benchmark - public int mhUpdater() { - return mhUpdater.hashCode(); - } - @Benchmark public int updaterStatic() { return UPDATER.hashCode(); @@ -122,35 +111,28 @@ public int hashCode() { static final class Updater extends Abstract { - private static final ToIntFunction HASH_CODE_UPDATER = - StableFieldUpdater.ofInt(Updater.class, "hashCode", new ToIntFunction<>() { - @Override - public int applyAsInt(Updater updater) { - return updater.string.hashCode(); - } - }); + private static final MethodHandle HASH_UPDATER; - Updater(String string) { super(string); } - - @Override - public int hashCode() { - return HASH_CODE_UPDATER.applyAsInt(this); + static { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + try { + var varHandle = lookup.findVarHandle(Updater.class, "hashCode", int.class); + var accessor = lookup.findVirtual(Updater.class, "hashCode0", MethodType.methodType(int.class)); + HASH_UPDATER = StableFieldUpdater.atMostOnce(varHandle, accessor); + } catch (ReflectiveOperationException e) { + throw new InternalError(e); + } } - } - - static final class MhUpdater extends Abstract { - private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); - private static final ToIntFunction HASH_CODE_UPDATER = - StableFieldUpdater.ofInt( - MhUtil.findVarHandle(LOOKUP, "hashCode", int.class), - MhUtil.findVirtual(LOOKUP, "hashCode0", MethodType.methodType(int.class))); - - MhUpdater(String string) { super(string); } + Updater(String string) { super(string); } @Override public int hashCode() { - return HASH_CODE_UPDATER.applyAsInt(this); + try { + return (int) HASH_UPDATER.invokeExact(this); + } catch (Throwable e) { + throw new RuntimeException(e); + } } private int hashCode0() { @@ -169,45 +151,4 @@ static class Abstract { } } - - // From j.i.i.MhUtil - - private static final class MhUtil { - - public static VarHandle findVarHandle(MethodHandles.Lookup lookup, - String name, - Class type) { - return findVarHandle(lookup, lookup.lookupClass(), name, type); - } - - public static VarHandle findVarHandle(MethodHandles.Lookup lookup, - Class recv, - String name, - Class type) { - try { - return lookup.findVarHandle(recv, name, type); - } catch (ReflectiveOperationException e) { - throw new InternalError(e); - } - } - - - public static MethodHandle findVirtual(MethodHandles.Lookup lookup, - String name, - MethodType type) { - return findVirtual(lookup, lookup.lookupClass(), name, type); - } - - public static MethodHandle findVirtual(MethodHandles.Lookup lookup, - Class refc, - String name, - MethodType type) { - try { - return lookup.findVirtual(refc, name, type); - } catch (ReflectiveOperationException e) { - throw new InternalError(e); - } - } - } - } From 2df08074b28551eacb2c914c09c28354def3b364 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 14 May 2025 17:40:39 +0200 Subject: [PATCH 24/24] Fix docs --- .../lang/stable/StableFieldUpdater.java | 124 +++++++----------- .../stable/StableFieldUpdaterGenerator.java | 6 + .../StableFieldUpdaterExampleTest.java | 55 ++++++++ 3 files changed, 111 insertions(+), 74 deletions(-) diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java index fd585750b51ac..d6bd5a06b5169 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java @@ -79,9 +79,18 @@ * private final Bar bar; * private final Baz baz; * - * private static final ToIntFunction HASH_UPDATER = - * StableFieldUpdater.ofInt(LazyFoo.class, "hash", - * l -> Objects.hash(l.bar, l.baz)); + * private static final MethodHandle HASH_UPDATER; + * + * static { + * MethodHandles.Lookup lookup = MethodHandles.lookup(); + * try { + * VarHandle accessor = lookup.findVarHandle(LazyFoo.class, "hash", int.class); + * MethodHandle underlying = lookup.findStatic(LazyFoo.class, "hashCodeFor", MethodType.methodType(int.class, LazyFoo.class)); + * HASH_UPDATER = StableFieldUpdater.atMostOnce(accessor, underlying); + * } catch (ReflectiveOperationException e) { + * throw new InternalError(e); + * } + * } * * @Stable * private int hash; @@ -100,15 +109,21 @@ * * @Override * public int hashCode() { - * return HASH_UPDATER.applyAsInt(this); + * try { + * return (int) HASH_UPDATER.invokeExact(this); + * } catch (Throwable e) { + * throw new RuntimeException(e); + * } + * } + * + * private static int hashCodeFor(LazyFoo foo) { + * return Objects.hash(foo.bar, foo.baz); * } * } *} *

- * If the underlying hash lamba returns zero, it is replaced with {@code -1}. It is legal - * to provide {@code 0} as a replacement in which case there will be no replacement and - * the hash code will be {@code 0}. In such cases, {@link @Stable} fields cannot be - * constant-folded. + * If the underlying hash lamba returns zero, the hash code will be {@code 0}. In such + * cases, {@link @Stable} fields cannot be constant-folded. *

* In cases where the entire range of hash codes are strictly specified (as it is for * {@code String}), a {@code long} field can be used instead, and a value of @@ -116,14 +131,28 @@ * then just cast to an {@code int} as shown in this example: * * {@snippet lang = java: - * public final class LazySpecifiedFoo { + * public final class LazySpecifiedFoo { * * private final Bar bar; * private final Baz baz; * - * private static final ToLongFunction HASH_UPDATER = - * StableFieldUpdater.ofLong(LazySpecifiedFoo.class, "hash", - * StableFieldUpdater.replaceLongZero(LazySpecifiedFoo::hashCodeFor, 1L << 32)); + * private static final MethodHandle HASH_UPDATER; + * + * static { + * MethodHandles.Lookup lookup = MethodHandles.lookup(); + * try { + * VarHandle accessor = lookup.findVarHandle(LazySpecifiedFoo.class, "hash", long.class); + * MethodHandle underlying = lookup.findStatic(LazySpecifiedFoo.class, "hashCodeFor", MethodType.methodType(long.class, LazySpecifiedFoo.class)); + * + * // Replaces zero with 2^32. Bits 32-63 will then be masked away by the + * // `hashCode()` method using an (int) cast. + * underlying = StableFieldUpdater.replaceLongZero(underlying, 1L << 32); + * + * HASH_UPDATER = StableFieldUpdater.atMostOnce(accessor, underlying); + * } catch (ReflectiveOperationException e) { + * throw new InternalError(e); + * } + * } * * @Stable * private long hash; @@ -135,17 +164,21 @@ * * @Override * public boolean equals(Object o) { - * return (o instanceof Foo that) + * return o instanceof Foo that * && Objects.equals(this.bar, that.bar) * && Objects.equals(this.baz, that.baz); * } * * @Override * public int hashCode() { - * return (int) HASH_UPDATER.applyAsLong(this); + * try { + * return (int) (long) HASH_UPDATER.invokeExact(this); + * } catch (Throwable e) { + * throw new RuntimeException(e); + * } * } * - * static long hashCodeFor(LazySpecifiedFoo foo) { + * private static long hashCodeFor(LazySpecifiedFoo foo) { * return Objects.hash(foo.bar, foo.baz); * } * } @@ -153,60 +186,9 @@ * The example above also features a static method {@code hashCodeFor()} that acts as * the underlying hash function. This method can reside in another class. *

- * Here is another example where a more low-level approach with VarHandle and MethodHandle - * parameters is used: * - * {@snippet lang=java: - public final class MhFoo { - - private final Bar bar; - private final Baz baz; - - private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); - - private static final ToIntFunction HASH_UPDATER = - StableFieldUpdater.ofInt( - MhUtil.findVarHandle(LOOKUP, "hash", int.class), - MhUtil.findStatic(LOOKUP, "hashCodeFor", MethodType.methodType(int.class, MhFoo.class))); - - @Stable - private int hash; - - public MhFoo(Bar bar, Baz baz) { - this.bar = bar; - this.baz = baz; - } - - @Override - public boolean equals(Object o) { - return o instanceof Foo that - && Objects.equals(this.bar, that.bar) - && Objects.equals(this.baz, that.baz); - } - - @Override - public int hashCode() { - return HASH_UPDATER.applyAsInt(this); - } - - // Used reflectively - static int hashCodeFor(MhFoo foo) { - return Objects.hash(foo.bar, foo.baz); - } - } - * } - * There is a convenience method for the idiomatic case above that looks like this: - * {@snippet lang=java: - - private static final ToIntFunction HASH_UPDATER = - StableFieldUpdater.ofInt(MethodHandles.lookup(), "hash", "hashCodeFor")); - * } - * This will use the provided {@link MethodHandles#lookup()} to look up the field - * {@code hash} and also use the same lookup to look up a static method that takes - * a {@code MhFoo} and returns an {@code int}. - * - * The provided {@code underlying} function must not recurse or the result of the - * operation is unspecified. + * The provided {@code underlying} function must not recurse or an IllegalStateException + * will be thrown. *

* If a reference value of {@code null} is used as a parameter in any of the methods * in this class, a {@link NullPointerException} is thrown. @@ -285,12 +267,6 @@ private static VarHandle.AccessMode initialAccessMode(Class varType) { } } - static Class leafClass(Class type) { - return type.isArray() - ? leafClass(type.componentType()) - : type; - } - /** * {@return a method handle that will replace any zero value returned by the provided * {@code underlying} handle with the provided {@code zeroReplacement}} diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdaterGenerator.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdaterGenerator.java index 022a89da6d967..5371a1f874c1a 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdaterGenerator.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdaterGenerator.java @@ -313,6 +313,12 @@ static void preventReentry(Object obj) { } } + static Class componentTypeDeep(Class type) { + return type.isArray() + ? componentTypeDeep(type.componentType()) + : type; + } + // Caching of Shapes private static final Map MAKERS = new ConcurrentHashMap<>(); diff --git a/test/jdk/java/lang/StableValue/StableFieldUpdaterExampleTest.java b/test/jdk/java/lang/StableValue/StableFieldUpdaterExampleTest.java index d8938bf9d1cc5..b850321eaff68 100644 --- a/test/jdk/java/lang/StableValue/StableFieldUpdaterExampleTest.java +++ b/test/jdk/java/lang/StableValue/StableFieldUpdaterExampleTest.java @@ -164,6 +164,61 @@ private static int hashCodeFor(LazyFoo foo) { } } + static + + public final class LazySpecifiedFoo { + + private final Bar bar; + private final Baz baz; + + private static final MethodHandle HASH_UPDATER; + + static { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + try { + VarHandle accessor = lookup.findVarHandle(LazySpecifiedFoo.class, "hash", long.class); + MethodHandle underlying = lookup.findStatic(LazySpecifiedFoo.class, "hashCodeFor", MethodType.methodType(long.class, LazySpecifiedFoo.class)); + + // Replaces zero with 2^32. Bits 32-63 will then be masked away by the + // `hashCode()` method using an (int) cast. + underlying = StableFieldUpdater.replaceLongZero(underlying, 1L << 32); + + HASH_UPDATER = StableFieldUpdater.atMostOnce(accessor, underlying); + } catch (ReflectiveOperationException e) { + throw new InternalError(e); + } + } + + @Stable + private long hash; + + public LazySpecifiedFoo(Bar bar, Baz baz) { + this.bar = bar; + this.baz = baz; + } + + @Override + public boolean equals(Object o) { + return o instanceof Foo that + && Objects.equals(this.bar, that.bar) + && Objects.equals(this.baz, that.baz); + } + + @Override + public int hashCode() { + try { + return (int) (long) HASH_UPDATER.invokeExact(this); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + + private static long hashCodeFor(LazySpecifiedFoo foo) { + return Objects.hash(foo.bar, foo.baz); + } + } + + @Test void lazy() { var bar = new Bar(1);