From 272b31e99a8bdd7c55733a12c3e246ab2e46b24d Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 3 Jun 2024 17:58:44 +0200 Subject: [PATCH 001/327] Initial commit --- .../build/tools/classlist/HelloClasslist.java | 13 + make/test/BuildMicrobenchmark.gmk | 2 + src/hotspot/share/ci/ciField.cpp | 14 +- .../share/classes/java/lang/Class.java | 21 +- .../java/lang/reflect/AccessibleObject.java | 8 +- .../classes/java/lang/reflect/Field.java | 12 +- .../java/util/ImmutableCollections.java | 157 +++++++++ .../access/JavaUtilCollectionAccess.java | 7 + .../jdk/internal/lang/StableValue.java | 297 ++++++++++++++++++ .../jdk/internal/lang/stable/StableUtil.java | 50 +++ .../internal/lang/stable/StableValueImpl.java | 157 +++++++++ .../share/classes/sun/misc/Unsafe.java | 38 +-- .../lang/stable/MemoizedFunctionTest.java | 126 ++++++++ .../lang/stable/MemoizedIntFunctionTest.java | 121 +++++++ .../lang/stable/MemoizedSupplierTest.java | 63 ++++ .../internal/lang/stable/StableTestUtil.java | 101 ++++++ .../internal/lang/stable/StableValueTest.java | 209 ++++++++++++ .../lang/stable/TrustedFieldTypeTest.java | 102 ++++++ 18 files changed, 1468 insertions(+), 30 deletions(-) create mode 100644 src/java.base/share/classes/jdk/internal/lang/StableValue.java create mode 100644 src/java.base/share/classes/jdk/internal/lang/stable/StableUtil.java create mode 100644 src/java.base/share/classes/jdk/internal/lang/stable/StableValueImpl.java create mode 100644 test/jdk/jdk/internal/lang/stable/MemoizedFunctionTest.java create mode 100644 test/jdk/jdk/internal/lang/stable/MemoizedIntFunctionTest.java create mode 100644 test/jdk/jdk/internal/lang/stable/MemoizedSupplierTest.java create mode 100644 test/jdk/jdk/internal/lang/stable/StableTestUtil.java create mode 100644 test/jdk/jdk/internal/lang/stable/StableValueTest.java create mode 100644 test/jdk/jdk/internal/lang/stable/TrustedFieldTypeTest.java diff --git a/make/jdk/src/classes/build/tools/classlist/HelloClasslist.java b/make/jdk/src/classes/build/tools/classlist/HelloClasslist.java index 1b930ca752777..f178a1b5d2a03 100644 --- a/make/jdk/src/classes/build/tools/classlist/HelloClasslist.java +++ b/make/jdk/src/classes/build/tools/classlist/HelloClasslist.java @@ -192,4 +192,17 @@ private static Object invoke(MethodHandle mh, Object ... args) throws Throwable throw t; } } + + // Generate enhanced switch statements + // Todo: Enable when in a public API +/* MemoizedSupplier memoizedSupplier = + StableValue.memoizedSupplier(new Supplier() { + @Override public Integer get() { return 42; } + }); + memoizedSupplier.get(); + MemoizedIntSupplier intFunction = + StableArray.memoizedIntFunction(new IntFunction<>() { + @Override public Object apply(int value) { return 42; } + }); + intFunction.apply(0);*/ } diff --git a/make/test/BuildMicrobenchmark.gmk b/make/test/BuildMicrobenchmark.gmk index 7b65e89610e04..5fedb5b74533a 100644 --- a/make/test/BuildMicrobenchmark.gmk +++ b/make/test/BuildMicrobenchmark.gmk @@ -109,6 +109,8 @@ $(eval $(call SetupJavaCompilation, BUILD_JDK_MICROBENCHMARK, \ --add-exports java.base/jdk.internal.vm=ALL-UNNAMED \ --add-exports java.base/sun.invoke.util=ALL-UNNAMED \ --add-exports java.base/sun.security.util=ALL-UNNAMED \ + --add-exports java.base/jdk.internal.lang=ALL-UNNAMED \ + --add-exports java.base/jdk.internal.access=ALL-UNNAMED \ --add-exports java.base/sun.security.util.math=ALL-UNNAMED \ --add-exports java.base/sun.security.util.math.intpoly=ALL-UNNAMED \ --enable-preview \ diff --git a/src/hotspot/share/ci/ciField.cpp b/src/hotspot/share/ci/ciField.cpp index 0eddd87200ae0..71dd1b424d287 100644 --- a/src/hotspot/share/ci/ciField.cpp +++ b/src/hotspot/share/ci/ciField.cpp @@ -253,6 +253,16 @@ static bool trust_final_non_static_fields(ciInstanceKlass* holder) { return TrustFinalNonStaticFields; } +// Todo: Change to 'java/lang/StableValue' etc. once StableValue becomes a public API +const char* stable_value_klass_name = "jdk/internal/lang/StableValue"; + +static bool trust_final_non_static_fields_of_type(Symbol* signature) { + if (signature->equals(stable_value_klass_name) == 0) { + return true; + } + return false; +} + void ciField::initialize_from(fieldDescriptor* fd) { // Get the flags, offset, and canonical holder of the field. _flags = ciFlags(fd->access_flags(), fd->field_flags().is_stable(), fd->field_status().is_initialized_final_update()); @@ -285,7 +295,9 @@ void ciField::initialize_from(fieldDescriptor* fd) { // An instance field can be constant if it's a final static field or if // it's a final non-static field of a trusted class (classes in // java.lang.invoke and sun.invoke packages and subpackages). - _is_constant = is_stable_field || trust_final_non_static_fields(_holder); + _is_constant = is_stable_field || + trust_final_non_static_fields(_holder) || + trust_final_non_static_fields_of_type(fd->signature()); } } else { // For CallSite objects treat the target field as a compile time constant. diff --git a/src/java.base/share/classes/java/lang/Class.java b/src/java.base/share/classes/java/lang/Class.java index 4573a6dc69004..efed107dccdfb 100644 --- a/src/java.base/share/classes/java/lang/Class.java +++ b/src/java.base/share/classes/java/lang/Class.java @@ -4766,10 +4766,23 @@ public Class[] getPermittedSubclasses() { return null; } if (subClasses.length > 0) { - if (Arrays.stream(subClasses).anyMatch(c -> !isDirectSubType(c))) { - subClasses = Arrays.stream(subClasses) - .filter(this::isDirectSubType) - .toArray(s -> new Class[s]); + // Neither Streams nor lambdas are used here in order to allow + // this method to be invoked early in the boot sequence. + boolean anyIsNotDirectSubclass = false; + for (Class c : subClasses) { + if (!isDirectSubType(c)) { + anyIsNotDirectSubclass = true; + break; + } + } + if (anyIsNotDirectSubclass) { + List> list = new ArrayList<>(); + for (Class subClass : subClasses) { + if (isDirectSubType(subClass)) { + list.add(subClass); + } + } + subClasses = list.toArray(new Class[0]); } } if (subClasses.length > 0) { diff --git a/src/java.base/share/classes/java/lang/reflect/AccessibleObject.java b/src/java.base/share/classes/java/lang/reflect/AccessibleObject.java index d0b50047031c1..b16bf05c2998b 100644 --- a/src/java.base/share/classes/java/lang/reflect/AccessibleObject.java +++ b/src/java.base/share/classes/java/lang/reflect/AccessibleObject.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2024, 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 @@ -385,11 +385,15 @@ private void throwInaccessibleObjectException(Class caller, Class declarin msg += " " + pn + "\"" ; if (caller != null) msg += " to " + caller.getModule(); + throw newInaccessibleObjectException(msg); + } + + static InaccessibleObjectException newInaccessibleObjectException(String msg) { InaccessibleObjectException e = new InaccessibleObjectException(msg); if (printStackTraceWhenAccessFails()) { e.printStackTrace(System.err); } - throw e; + return e; } private boolean isSubclassOf(Class queryClass, Class ofClass) { diff --git a/src/java.base/share/classes/java/lang/reflect/Field.java b/src/java.base/share/classes/java/lang/reflect/Field.java index 3e9d02d55095c..c1f9c7d10ce25 100644 --- a/src/java.base/share/classes/java/lang/reflect/Field.java +++ b/src/java.base/share/classes/java/lang/reflect/Field.java @@ -26,6 +26,7 @@ package java.lang.reflect; import jdk.internal.access.SharedSecrets; +import jdk.internal.lang.StableValue; import jdk.internal.reflect.CallerSensitive; import jdk.internal.reflect.FieldAccessor; import jdk.internal.reflect.Reflection; @@ -174,8 +175,15 @@ Field copy() { @CallerSensitive public void setAccessible(boolean flag) { AccessibleObject.checkPermission(); - if (flag) checkCanSetAccessible(Reflection.getCallerClass()); - setAccessible0(flag); + // Always check if the field is a final StableValue + if (StableValue.class.isAssignableFrom(type) && Modifier.isFinal(modifiers)) { + throw newInaccessibleObjectException( + "Unable to make field " + this + " accessible: " + + "Fields declared to implement " + StableValue.class.getName() + " are trusted"); + } + if (flag) { + checkCanSetAccessible(Reflection.getCallerClass()); + } } @Override diff --git a/src/java.base/share/classes/java/util/ImmutableCollections.java b/src/java.base/share/classes/java/util/ImmutableCollections.java index 726c7bb923b25..65308b4936004 100644 --- a/src/java.base/share/classes/java/util/ImmutableCollections.java +++ b/src/java.base/share/classes/java/util/ImmutableCollections.java @@ -34,10 +34,14 @@ import java.lang.reflect.Array; import java.util.function.BiFunction; import java.util.function.Function; +import java.util.function.IntFunction; import java.util.function.Predicate; import java.util.function.UnaryOperator; + +import jdk.internal.ValueBased; import jdk.internal.access.JavaUtilCollectionAccess; import jdk.internal.access.SharedSecrets; +import jdk.internal.lang.StableValue; import jdk.internal.misc.CDS; import jdk.internal.vm.annotation.Stable; @@ -126,6 +130,14 @@ public List listFromTrustedArray(Object[] array) { public List listFromTrustedArrayNullsAllowed(Object[] array) { return ImmutableCollections.listFromTrustedArrayNullsAllowed(array); } + public List listFromStable(List> delegate, + IntFunction mapper) { + return new StableList<>(delegate, mapper); + } + public Map mapFromStable(Map> delegate, + Function mapper) { + return new StableMap<>(delegate, mapper); + } }); } } @@ -1360,6 +1372,151 @@ private Object writeReplace() { return new CollSer(CollSer.IMM_MAP, array); } } + + @ValueBased + static final class StableList extends AbstractImmutableList { + + private final List> delegate; + private final IntFunction mapper; + + public StableList(List> delegate, + IntFunction mapper) { + this.delegate = delegate; + this.mapper = mapper; + } + + @Override + public int size() { + return delegate.size(); + } + + @Override + public E get(int index) { + return delegate.get(index) + .computeIfUnset(index, mapper::apply); + } + + @Override + public int indexOf(Object o) { + for (int i = 0; i < size(); i++) { + if (get(i).equals(o)) { + return i; + } + } + return -1; + } + + @Override + public int lastIndexOf(Object o) { + for (int i = size() - 1; i >= 0; i--) { + if (get(i).equals(o)) { + return i; + } + } + return -1; + } + } + + static final class StableMap extends AbstractImmutableMap { + + @Stable + private final Map> delegate; + @Stable + private final Function mapper; + + public StableMap(Map> delegate, + Function mapper) { + this.delegate = delegate; + this.mapper = mapper; + } + + @Override + public boolean containsKey(Object o) { + Objects.requireNonNull(o); + return delegate.containsKey(o); + } + + @Override + public boolean containsValue(Object o) { + return delegate.entrySet().stream() + .anyMatch(e -> value(e).equals(o)); + } + + @Override + public int hashCode() { + return delegate.entrySet().stream() + .reduce(0, + (h, e) -> h + (e.getKey().hashCode()) ^ ((value(e)).hashCode()), + (a, b) -> a); + } + + @Override + @SuppressWarnings("unchecked") + public V get(Object o) { + StableValue stable = delegate.get(o); + return stable == null + ? null + : stable.computeIfUnset((K)o, mapper); + } + + @Override + public int size() { + return delegate.size(); + } + + @Override + public boolean isEmpty() { + return delegate.isEmpty(); + } + + final class StableMapIterator implements Iterator> { + + @Stable + private final Iterator>> delegateIterator; + + StableMapIterator() { + delegateIterator = StableMap.this.delegate.entrySet().iterator(); + } + + @Override + public boolean hasNext() { + return delegateIterator.hasNext(); + } + + @Override + public Map.Entry next() { + Map.Entry> dNext = delegateIterator.next(); + return new KeyValueHolder<>(dNext.getKey(), value(dNext)); + } + } + + @Override + public Set> entrySet() { + return new AbstractImmutableSet<>() { + @Override + public int size() { + return StableMap.this.size(); + } + + @Override + public Iterator> iterator() { + return new StableMap.StableMapIterator(); + } + + @Override + public int hashCode() { + return StableMap.this.hashCode(); + } + }; + } + + // Unwraps a value + private V value(Entry> e) { + return e.getValue().computeIfUnset(e.getKey(), mapper); + } + + } + } // ---------- Serialization Proxy ---------- diff --git a/src/java.base/share/classes/jdk/internal/access/JavaUtilCollectionAccess.java b/src/java.base/share/classes/jdk/internal/access/JavaUtilCollectionAccess.java index f88d57521ac5a..fa2592b16df4b 100644 --- a/src/java.base/share/classes/jdk/internal/access/JavaUtilCollectionAccess.java +++ b/src/java.base/share/classes/jdk/internal/access/JavaUtilCollectionAccess.java @@ -25,9 +25,16 @@ package jdk.internal.access; +import jdk.internal.lang.StableValue; + import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.function.IntFunction; public interface JavaUtilCollectionAccess { List listFromTrustedArray(Object[] array); List listFromTrustedArrayNullsAllowed(Object[] array); + List listFromStable(List> delegate, IntFunction mapper); + Map mapFromStable(Map> delegate, Function mapper); } diff --git a/src/java.base/share/classes/jdk/internal/lang/StableValue.java b/src/java.base/share/classes/jdk/internal/lang/StableValue.java new file mode 100644 index 0000000000000..d30bfa4263ab1 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/lang/StableValue.java @@ -0,0 +1,297 @@ +/* + * Copyright (c) 2024, 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; + +import jdk.internal.access.SharedSecrets; +import jdk.internal.lang.stable.StableValueImpl; + +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.Set; +import java.util.function.Function; +import java.util.function.IntFunction; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * A thin, atomic, thread-safe, set-at-most-once, stable value holder eligible for + * certain JVM optimizations if set to a value. + *

+ * A stable value is said to be monotonic because the state of a stable value can only go + * from unset to set and consequently, a value can only be set + * at most once. +

+ * To create a new fresh (unset) StableValue, use the {@linkplain StableValue#of()} + * factory. + *

+ * All methods that can set the stable value's value are guarded such that competing + * set operations (by other threads) will block if another set operation is + * already in progress. + *

+ * Except for a StableValue's value itself, all method parameters must be non-null + * or a {@link NullPointerException} will be thrown. + * + * @param type of the wrapped value + * + * @since 23 + */ +public sealed interface StableValue + permits StableValueImpl { + + // Principal methods + + /** + * {@return {@code true} if the stable value was set to the provided {@code value}, + * otherwise returns {@code false}} + * + * @param value to set (nullable) + */ + boolean trySet(T value); + + /** + * {@return the set value (nullable) if set, otherwise return the {@code other} value} + * @param other to return if the stable value is not set + */ + T orElse(T other); + + /** + * {@return the set value if set, otherwise throws + * {@code NoSuchElementException}} + * + * @throws NoSuchElementException if no value is set + */ + T orElseThrow(); + + /** + * {@return {@code true} if a value is set, {@code false} otherwise} + */ + boolean isSet(); + + /** + * If the stable value is unset, attempts to compute its value using the given + * supplier function and enters it into this stable value. + * + *

If the supplier function itself throws an (unchecked) exception, the exception + * is rethrown, and no value is set. The most common usage is to construct a new + * object serving as an initial value or memoized result, as in: + * + *

 {@code
+     * T t = stable.computeIfUnset(T::new);
+     * }
+ * + * @implSpec + * The default implementation is equivalent to the following steps for this + * {@code stable}: + * + *
 {@code
+     * if (stable.isSet()) {
+     *     return stable.getOrThrow();
+     * }
+     * T newValue = supplier.apply(key);
+     * stable.trySet(newValue);
+     * return newValue;
+     * }
+ * Except, the method is atomic and thread-safe. + * + * @param supplier the mapping supplier to compute a value + * @return the current (existing or computed) value associated with + * the stable value + */ + T computeIfUnset(Supplier supplier); + + /** + * If the stable value is unset, attempts to compute its value using the given + * {@code input} parameter and the provided {@code mapper} function and enters + * it into this stable value. + * + *

If the mapper function itself throws an (unchecked) exception, the exception + * is rethrown, and no value is set. The most common usage is to construct a new + * object serving as an initial value or memoized result in a {@code mop}, as in: + * + *

 {@code
+     * Map> map = ...
+     * K key = ...
+     * T t = map.get(key).computeIfUnset(key, k -> new T(k));
+     * }
+ * + * @implSpec + * The default implementation is equivalent to the following steps for this + * {@code stable} value: + * + *
 {@code
+     * if (stable.isSet()) {
+     *     return stable.getOrThrow();
+     * }
+     * T newValue = mapper.apply(input);
+     * stable.trySet(newValue);
+     * return newValue;
+     * }
+ * Except, the method is atomic and thread-safe. + * +\ * @param input to be used with the provided {@code mapper} + * @param mapper the mapping function to compute a value from the {@code input} + * @return the current (existing or computed) value associated with + * the stable value + */ + T computeIfUnset(I input, Function mapper); + + + // Convenience methods + + /** + * Sets the stable value to the provided {@code value}, or, if already set to a + * non-null value, throws {@linkplain IllegalStateException}} + * + * @param value to set (nullable) + * @throws IllegalArgumentException if a non-null value is already set + */ + default void setOrThrow(T value) { + if (!trySet(value)) { + throw new IllegalStateException("Cannot set value to " + value + + " because a value is alredy set: " + this); + } + } + + // Factories + + /** + * {@return a fresh stable value with an unset ({@code null}) value} + * + * @param the value type to set + */ + static StableValue of() { + return StableValueImpl.of(); + } + + /** + * {@return a lazily computed, atomic, thread-safe, set-at-most-once-per-index, + * List of stable elements eligible for certain JVM optimizations} + * + * @param size the size of the returned list + * @param mapper to invoke when an element is to be computed + * @param the {@code List}'s element type + */ + static List ofList(int size, + IntFunction mapper) { + if (size < 0) { + throw new IllegalArgumentException(); + } + if (size == 0) { + return List.of(); + } + Objects.requireNonNull(mapper); + List> backing = Stream.generate(StableValue::of) + .limit(size) + .toList(); + + return SharedSecrets.getJavaUtilCollectionAccess() + .listFromStable(backing, mapper); + }; + + /** + * {@return a lazily computed, atomic, thread-safe, set-at-most-once-per-key, + * Map of stable elements eligible for certain JVM optimizations} + * @param keys the keys in the {@code Map} + * @param mapper to invoke when a value is to be computed + * @param the {@code Map}'s key type + * @param the {@code Map}'s value type + */ + static Map ofMap(Set keys, + Function mapper) { + Objects.requireNonNull(keys); + Objects.requireNonNull(mapper); + Map> backing = keys.stream() + .collect(Collectors.toMap(Function.identity(), _ -> StableValue.of())); + return SharedSecrets.getJavaUtilCollectionAccess() + .mapFromStable(backing, mapper); + } + + /** + * {@return a new thread-safe, stable, lazily computed {@linkplain Supplier supplier} + * that records the value of the provided {@code original} supplier upon being first + * accessed via {@linkplain Supplier#get()}} + *

+ * The provided {@code original} supplier is guaranteed to be successfully invoked + * at most once even in a multi-threaded environment. Competing threads invoking the + * {@linkplain Supplier#get()} method when a value is already under computation + * will block until a value is computed or an exception is thrown by the + * computing thread. + *

+ * If the {@code original} Supplier invokes the returned Supplier recursively, + * a StackOverflowError will be thrown when the returned + * Supplier's {@linkplain Function#apply(Object)}} method is invoked. + *

+ * If the provided {@code original} supplier throws an exception, it is relayed + * to the initial caller. Subsequent read operations will incur a new invocation + * of the provided {@code original}. + * + * @param original supplier + * @param the type of results supplied by the returned supplier + */ + static Supplier memoizedSupplier(Supplier original) { + Objects.requireNonNull(original); + final StableValue stable = StableValue.of(); + return () -> stable.computeIfUnset(original); + } + + /** + * {@return a memoized IntFunction backed by a {@linkplain #ofList(int, IntFunction) + * stable list} where the provided {@code original} will be invoked at most once per + * index} + * @param size the allowed input values, [0, size) + * @param original to invoke when an element is to be computed + * @param the type of the result of the function + */ + static IntFunction memoizedIntFunction(int size, + IntFunction original) { + return ofList(size, original)::get; + } + + /** + * {@return a memoized Function backed by a {@linkplain #ofMap(Set, Function) + * stable map} where the provided {@code original} will be invoked at most once per + * index} + * @param inputs the allowed input values + * @param original to invoke when an element is to be computed + * @param the type of the input to the function + * @param the type of the result of the function + */ + static Function memoizedFunction(Set inputs, + Function original) { + final Map backing = ofMap(inputs, original); + return t -> { + if (!backing.containsKey(t)) { + throw new IllegalArgumentException("Input not allowed: "+t); + } + return backing.get(t); + }; + } + + +} \ No newline at end of file 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 new file mode 100644 index 0000000000000..29d3bb8a22919 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableUtil.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2024, 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; + +final class StableUtil { + + private StableUtil() {} + + static final Unsafe UNSAFE = Unsafe.getUnsafe(); + + private static final Object NULL_SENTINEL = new Object(); + + @SuppressWarnings("unchecked") + static T nullSentinel() { + return (T) NULL_SENTINEL; + } + + static String render(T t) { + if (t != null) { + return t == nullSentinel() ? "[null]" : "[" + t + "]"; + } + return ".unset"; + } + +} \ No newline at end of file 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 new file mode 100644 index 0000000000000..aa0253bda5c55 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableValueImpl.java @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2024, 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.lang.StableValue; +import jdk.internal.vm.annotation.DontInline; +import jdk.internal.vm.annotation.ForceInline; +import jdk.internal.vm.annotation.Stable; + +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.function.Function; +import java.util.function.Supplier; + +import static jdk.internal.lang.stable.StableUtil.*; + +public final class StableValueImpl implements StableValue { + + private static final long VALUE_OFFSET = + UNSAFE.objectFieldOffset(StableValueImpl.class, "value"); + + private final Object mutex = new Object(); + + // Unset: null + // Set(non-null): The set value + // Set(null): nullSentinel() + @Stable + private volatile T value; + + private StableValueImpl() {} + + @ForceInline + @Override + public boolean trySet(T value) { + synchronized (mutex) { + return UNSAFE.compareAndSetReference(this, VALUE_OFFSET, + null, (value == null) ? nullSentinel() : value); + } + } + + @ForceInline + @Override + public T orElseThrow() { + final T t = value; + if (t != null) { + return t == nullSentinel() ? null : t; + } + throw new NoSuchElementException("No value set"); + } + + @ForceInline + @Override + public T orElse(T other) { + final T t = value; + if (t != null) { + return t == nullSentinel() ? null : t; + } + return other; + } + + @Override + public boolean isSet() { + return value != null; + } + + @ForceInline + @Override + public T computeIfUnset(Supplier supplier) { + final T t = value; + if (t != null) { + return t == nullSentinel() ? null : t; + } + return compute(supplier); + } + + @DontInline + private T compute(Supplier supplier) { + synchronized (mutex) { + T t = value; + if (t != null) { + return t == nullSentinel() ? null : t; + } + t = supplier.get(); + trySet(t); + return orElseThrow(); + } + } + + @ForceInline + @Override + public T computeIfUnset(I input, Function mapper) { + final T t = value; + if (t != null) { + return t == nullSentinel() ? null : t; + } + return compute(input, mapper); + } + + @DontInline + private T compute(I input, Function mapper) { + synchronized (mutex) { + T t = value; + if (t != null) { + return t == nullSentinel() ? null : t; + } + t = mapper.apply(input); + trySet(t); + return orElseThrow(); + } + } + + + @Override + public int hashCode() { + return Objects.hashCode(orElse(null)); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof StableValueImpl other && + Objects.equals(value, other.value); + } + + @Override + public String toString() { + return "StableValue" + render(value); + } + + // Factory + public static StableValueImpl of() { + return new StableValueImpl<>(); + } + +} diff --git a/src/jdk.unsupported/share/classes/sun/misc/Unsafe.java b/src/jdk.unsupported/share/classes/sun/misc/Unsafe.java index 3f91fd70db485..68e76a14b16d9 100644 --- a/src/jdk.unsupported/share/classes/sun/misc/Unsafe.java +++ b/src/jdk.unsupported/share/classes/sun/misc/Unsafe.java @@ -648,13 +648,7 @@ public long objectFieldOffset(Field f) { if (f == null) { throw new NullPointerException(); } - Class declaringClass = f.getDeclaringClass(); - if (declaringClass.isHidden()) { - throw new UnsupportedOperationException("can't get field offset on a hidden class: " + f); - } - if (declaringClass.isRecord()) { - throw new UnsupportedOperationException("can't get field offset on a record class: " + f); - } + assertNotTrusted(f); return theInternalUnsafe.objectFieldOffset(f); } @@ -687,13 +681,7 @@ public long staticFieldOffset(Field f) { if (f == null) { throw new NullPointerException(); } - Class declaringClass = f.getDeclaringClass(); - if (declaringClass.isHidden()) { - throw new UnsupportedOperationException("can't get field offset on a hidden class: " + f); - } - if (declaringClass.isRecord()) { - throw new UnsupportedOperationException("can't get field offset on a record class: " + f); - } + assertNotTrusted(f); return theInternalUnsafe.staticFieldOffset(f); } @@ -718,13 +706,7 @@ public Object staticFieldBase(Field f) { if (f == null) { throw new NullPointerException(); } - Class declaringClass = f.getDeclaringClass(); - if (declaringClass.isHidden()) { - throw new UnsupportedOperationException("can't get base address on a hidden class: " + f); - } - if (declaringClass.isRecord()) { - throw new UnsupportedOperationException("can't get base address on a record class: " + f); - } + assertNotTrusted(f); return theInternalUnsafe.staticFieldBase(f); } @@ -743,6 +725,20 @@ public int arrayBaseOffset(Class arrayClass) { return theInternalUnsafe.arrayBaseOffset(arrayClass); } + private static void assertNotTrusted(Field f) { + Class declaringClass = f.getDeclaringClass(); + if (declaringClass.isHidden()) { + throw new UnsupportedOperationException("can't get base address on a hidden class: " + f); + } + if (declaringClass.isRecord()) { + throw new UnsupportedOperationException("can't get base address on a record class: " + f); + } + Class fieldType = f.getType(); + if (fieldType.getName().equals("jdk.internal.lang.stable.StableValue")) { + throw new UnsupportedOperationException("can't get field offset for a field of type " + fieldType.getName() + ": " + f); + } + } + /** The value of {@code arrayBaseOffset(boolean[].class)} */ public static final int ARRAY_BOOLEAN_BASE_OFFSET = jdk.internal.misc.Unsafe.ARRAY_BOOLEAN_BASE_OFFSET; diff --git a/test/jdk/jdk/internal/lang/stable/MemoizedFunctionTest.java b/test/jdk/jdk/internal/lang/stable/MemoizedFunctionTest.java new file mode 100644 index 0000000000000..5be311013f14a --- /dev/null +++ b/test/jdk/jdk/internal/lang/stable/MemoizedFunctionTest.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2024, 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 StableValue implementations + * @modules java.base/jdk.internal.lang + * @compile --enable-preview -source ${jdk.version} MemoizedFunctionTest.java + * @run junit/othervm --enable-preview MemoizedFunctionTest + */ + +import jdk.internal.lang.StableValue; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.LockSupport; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.*; + +final class MemoizedFunctionTest { + + private static final int SIZE = 3; + private static final int INDEX = 1; + private static final Set INPUTS = IntStream.range(0, SIZE).boxed().collect(Collectors.toSet()); + private static final Function FUNCTION = Function.identity(); + + @Test + void basic() { + StableTestUtil.CountingFunction original = new StableTestUtil.CountingFunction<>(FUNCTION); + Function function = StableValue.memoizedFunction(INPUTS, original); + assertEquals(INDEX, function.apply(INDEX)); + assertEquals(1, original.cnt()); + assertEquals(INDEX, function.apply(INDEX)); + assertEquals(1, original.cnt()); + assertThrows(IllegalArgumentException.class, () -> function.apply(SIZE)); + } + + @Test + void empty() { + StableTestUtil.CountingFunction original = new StableTestUtil.CountingFunction<>(FUNCTION); + Function function = StableValue.memoizedFunction(Set.of(), original); + assertThrows(IllegalArgumentException.class, () -> function.apply(INDEX)); + } + +/* @Test + void toStringTest() { + Function function = StableValue.memoizedFunction(INPUTS, FUNCTION); + String expectedEmpty = "MemoizedFunction[original=" + FUNCTION; + assertTrue(function.toString().startsWith(expectedEmpty), function.toString()); + function.apply(INDEX); + assertTrue(function.toString().contains("[" + INDEX + "]"), function.toString()); + }*/ + + @Test + void shakedownSingleThread() { + for (int size = 0; size < 128; size++) { + Function memo = StableValue.memoizedFunction(IntStream.range(0, size).boxed().collect(Collectors.toSet()), Function.identity()); + for (int i = 0; i < size; i++) { + assertEquals(i, memo.apply(i)); + } + } + } + + @Test + void shakedownMultiThread() { + int threadCount = 8; + var startGate = new CountDownLatch(1); + for (int size = 0; size < 128; size++) { + Function memo = StableValue.memoizedFunction(IntStream.range(0, size).boxed().collect(Collectors.toSet()), Function.identity()); + final int fSize = size; + List threads = Stream.generate(() -> new Thread(() -> { + while (startGate.getCount() != 0) { + Thread.onSpinWait(); + } + for (int i = 0; i < fSize; i++) { + int value = memo.apply(i); + assertEquals(i, value); + } + })) + .limit(threadCount) + .toList(); + threads.forEach(Thread::start); + // Give some time for the threads to start + LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1)); + // Release them as close as possible + startGate.countDown(); + threads.forEach(MemoizedFunctionTest::join); + } + } + + static void join(Thread t) { + try { + t.join(); + } catch (InterruptedException e) { + fail(e); + } + } + +} \ No newline at end of file diff --git a/test/jdk/jdk/internal/lang/stable/MemoizedIntFunctionTest.java b/test/jdk/jdk/internal/lang/stable/MemoizedIntFunctionTest.java new file mode 100644 index 0000000000000..491e458dbfd51 --- /dev/null +++ b/test/jdk/jdk/internal/lang/stable/MemoizedIntFunctionTest.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2024, 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 StableValue implementations + * @modules java.base/jdk.internal.lang + * @compile --enable-preview -source ${jdk.version} MemoizedIntFunctionTest.java + * @run junit/othervm --enable-preview MemoizedIntFunctionTest + */ + +import jdk.internal.lang.StableValue; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.LockSupport; +import java.util.function.IntFunction; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.*; + +final class MemoizedIntFunctionTest { + + private static final int SIZE = 3; + private static final int INDEX = 1; + private static final IntFunction FUNCTION = i -> i; + + @Test + void basic() { + StableTestUtil.CountingIntFunction original = new StableTestUtil.CountingIntFunction<>(FUNCTION); + IntFunction function = StableValue.memoizedIntFunction(SIZE, original); + assertEquals(INDEX, function.apply(INDEX)); + assertEquals(1, original.cnt()); + assertEquals(INDEX, function.apply(INDEX)); + assertEquals(1, original.cnt()); + assertThrows(IndexOutOfBoundsException.class, () -> function.apply(SIZE)); + } + + @Test + void empty() { + StableTestUtil.CountingIntFunction original = new StableTestUtil.CountingIntFunction<>(FUNCTION); + IntFunction function = StableValue.memoizedIntFunction(0, original); + assertThrows(IndexOutOfBoundsException.class, () -> function.apply(INDEX)); + } + +/* @Test + void toStringTest() { + IntFunction function = StableValue.memoizedIntFunction(SIZE, FUNCTION); + String expectedEmpty = "MemoizedIntFunction[original=" + FUNCTION + ", delegate=StableArray[.unset, .unset, .unset]"; + assertTrue(function.toString().startsWith(expectedEmpty), function.toString()); + function.apply(INDEX); + assertTrue(function.toString().contains("delegate=StableArray[.unset, [" + INDEX + "], .unset]"), function.toString()); + }*/ + + @Test + void shakedownSingleThread() { + for (int size = 0; size < 128; size++) { + IntFunction memo = StableValue.memoizedIntFunction(size, i -> i); + for (int i = 0; i < size; i++) { + assertEquals(i, memo.apply(i)); + } + } + } + + @Test + void shakedownMultiThread() { + int threadCount = 8; + var startGate = new CountDownLatch(1); + for (int size = 0; size < 128; size++) { + IntFunction memo = StableValue.memoizedIntFunction(size, i -> i); + final int fSize = size; + List threads = Stream.generate(() -> new Thread(() -> { + while (startGate.getCount() != 0) { + Thread.onSpinWait(); + } + for (int i = 0; i < fSize; i++) { + int value = memo.apply(i); + assertEquals(i, value); + } + })) + .limit(threadCount) + .toList(); + threads.forEach(Thread::start); + // Give some time for the threads to start + LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1)); + // Release them as close as possible + startGate.countDown(); + threads.forEach(MemoizedIntFunctionTest::join); + } + } + + static void join(Thread t) { + try { + t.join(); + } catch (InterruptedException e) { + fail(e); + } + } + +} \ No newline at end of file diff --git a/test/jdk/jdk/internal/lang/stable/MemoizedSupplierTest.java b/test/jdk/jdk/internal/lang/stable/MemoizedSupplierTest.java new file mode 100644 index 0000000000000..dcc552702823d --- /dev/null +++ b/test/jdk/jdk/internal/lang/stable/MemoizedSupplierTest.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2024, 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 StableValue implementations + * @modules java.base/jdk.internal.lang + * @compile --enable-preview -source ${jdk.version} MemoizedSupplierTest.java + * @run junit/othervm --enable-preview MemoizedSupplierTest + */ + +import jdk.internal.lang.StableValue; +import org.junit.jupiter.api.Test; + +import java.util.function.Supplier; + +import static org.junit.jupiter.api.Assertions.*; + +final class MemoizedSupplierTest { + + private static final int VALUE = 42; + private static final Supplier SUPPLIER = () -> VALUE; + + @Test + void basic() { + StableTestUtil.CountingSupplier original = new StableTestUtil.CountingSupplier<>(SUPPLIER); + Supplier supplier = StableValue.memoizedSupplier(original); + assertEquals(VALUE, supplier.get()); + assertEquals(1, original.cnt()); + assertEquals(VALUE, supplier.get()); + assertEquals(1, original.cnt()); + } + +/* @Test + void toStringTest() { + Supplier supplier = StableValue.memoizedSupplier(SUPPLIER); + String expectedEmpty = "MemoizedSupplier[original=" + SUPPLIER + ", delegate=StableValue.unset]"; + assertEquals(expectedEmpty, supplier.toString()); + supplier.get(); + String expectedSet = "MemoizedSupplier[original=" + SUPPLIER + ", delegate=StableValue[" + VALUE + "]]"; + assertEquals(expectedSet, supplier.toString()); + }*/ + +} \ No newline at end of file diff --git a/test/jdk/jdk/internal/lang/stable/StableTestUtil.java b/test/jdk/jdk/internal/lang/stable/StableTestUtil.java new file mode 100644 index 0000000000000..8d03b1aa2f5e7 --- /dev/null +++ b/test/jdk/jdk/internal/lang/stable/StableTestUtil.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2024, 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. + */ + +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; +import java.util.function.IntFunction; +import java.util.function.Supplier; + +final class StableTestUtil { + + public static final class CountingSupplier + extends AbstractCounting> + implements Supplier { + + public CountingSupplier(Supplier delegate) { + super(delegate); + } + + @Override + public T get() { + incrementCounter(); + return delegate.get(); + } + + } + + public static final class CountingIntFunction + extends AbstractCounting> + implements IntFunction { + + public CountingIntFunction(IntFunction delegate) { + super(delegate); + } + + @Override + public T apply(int value) { + incrementCounter(); + return delegate.apply(value); + } + + } + + public static final class CountingFunction + extends AbstractCounting> + implements Function { + + public CountingFunction(Function delegate) { + super(delegate); + } + + @Override + public R apply(T t) { + incrementCounter(); + return delegate.apply(t); + } + } + + abstract static class AbstractCounting { + + private final AtomicInteger cnt = new AtomicInteger(); + protected final D delegate; + + protected AbstractCounting(D delegate) { + this.delegate = delegate; + } + + protected final void incrementCounter() { + cnt.incrementAndGet(); + } + + public final int cnt() { + return cnt.get(); + } + + @Override + public final String toString() { + return cnt.toString(); + } + } + +} \ No newline at end of file diff --git a/test/jdk/jdk/internal/lang/stable/StableValueTest.java b/test/jdk/jdk/internal/lang/stable/StableValueTest.java new file mode 100644 index 0000000000000..d15944a08c444 --- /dev/null +++ b/test/jdk/jdk/internal/lang/stable/StableValueTest.java @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2024, 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 StableValue implementations + * @modules java.base/jdk.internal.lang + * @compile --enable-preview -source ${jdk.version} StableValueTest.java + * @run junit/othervm --enable-preview StableValueTest + */ + +import jdk.internal.lang.StableValue; +import org.junit.jupiter.api.Test; + +import java.util.BitSet; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.LockSupport; +import java.util.function.BiPredicate; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; + +final class StableValueTest { + + @Test + void unset() { + StableValue stable = StableValue.of(); + assertNull(stable.orElse(null)); + assertThrows(NoSuchElementException.class, stable::orElseThrow); + assertEquals("StableValue.unset", stable.toString()); + assertTrue(stable.trySet(42)); + assertFalse(stable.trySet(null)); + assertFalse(stable.trySet(42)); + assertFalse(stable.trySet(2)); + } + + @Test + void setNull() { + StableValue stable = StableValue.of(); + assertTrue(stable.trySet(null)); + assertEquals("StableValue[null]", stable.toString()); + assertNull(stable.orElse(13)); + assertFalse(stable.trySet(null)); + assertFalse(stable.trySet(1)); + } + + @Test + void setNonNull() { + StableValue stable = StableValue.of(); + assertTrue(stable.trySet(42)); + assertEquals("StableValue[42]", stable.toString()); + assertEquals(42, stable.orElse(null)); + assertFalse(stable.trySet(null)); + assertFalse(stable.trySet(1)); + assertThrows(IllegalStateException.class, () -> stable.setOrThrow(1)); + assertEquals(42, stable.orElseThrow()); + } + + @Test + void computeIfUnset() { + StableValue stable = StableValue.of(); + assertEquals(42, stable.computeIfUnset(() -> 42)); + assertEquals(42, stable.computeIfUnset(() -> 13)); + assertEquals("StableValue[42]", stable.toString()); + assertEquals(42, stable.orElse(null)); + assertFalse(stable.trySet(null)); + assertFalse(stable.trySet(1)); + assertThrows(IllegalStateException.class, () -> stable.setOrThrow(1)); + } + + @Test + void computeIfUnsetException() { + StableValue stable = StableValue.of(); + Supplier supplier = () -> { + throw new UnsupportedOperationException("aaa"); + }; + var x = assertThrows(UnsupportedOperationException.class, () -> stable.computeIfUnset(supplier)); + assertTrue(x.getMessage().contains("aaa")); + assertEquals(42, stable.computeIfUnset(() -> 42)); + assertEquals("StableValue[42]", stable.toString()); + assertEquals(42, stable.orElse(13)); + assertFalse(stable.trySet(null)); + assertFalse(stable.trySet(1)); + assertThrows(IllegalStateException.class, () -> stable.setOrThrow(1)); + } + + @Test + void testHashCode() { + StableValue s0 = StableValue.of(); + StableValue s1 = StableValue.of(); + assertEquals(s0.hashCode(), s1.hashCode()); + s0.setOrThrow(42); + s1.setOrThrow(42); + assertEquals(s0.hashCode(), s1.hashCode()); + } + + @Test + void testEquals() { + StableValue s0 = StableValue.of(); + StableValue s1 = StableValue.of(); + assertEquals(s0, s1); + s0.setOrThrow(42); + s1.setOrThrow(42); + assertEquals(s0, s1); + StableValue other = StableValue.of(); + other.setOrThrow(13); + assertNotEquals(s0, other); + assertNotEquals(s0, "a"); + } + + private static final BiPredicate, Integer> TRY_SET = StableValue::trySet; + private static final BiPredicate, Integer> SET_OR_THROW = (s, i) -> { + try { + s.setOrThrow(i); + return true; + } catch (IllegalStateException e) { + return false; + } + }; + private static final BiPredicate, Integer> COMPUTE_IF_UNSET = (s, i) -> { + int r = s.computeIfUnset(() -> i); + return r == i; + }; + + @Test + void raceTrySet() { + race(TRY_SET); + } + + @Test + void raceSetOrThrow() { + race(SET_OR_THROW); + } + + @Test + void raceComputeIfUnset() { + race(COMPUTE_IF_UNSET); + } + + @Test + void raceMixed() { + race((s, i) -> switch (i % 3) { + case 0 -> TRY_SET.test(s, i); + case 1 -> SET_OR_THROW.test(s, i); + case 2 -> COMPUTE_IF_UNSET.test(s, i); + default -> fail("should not reach here"); + }); + } + + void race(BiPredicate, Integer> winnerPredicate) { + int noThreads = 10; + CountDownLatch starter = new CountDownLatch(1); + StableValue stable = StableValue.of(); + BitSet winner = new BitSet(noThreads); + List threads = IntStream.range(0, noThreads).mapToObj(i -> new Thread(() -> { + try { + // Ready, set ... + starter.await(); + // Here we go! + winner.set(i, winnerPredicate.test(stable, i)); + } catch (Throwable t) { + fail(t); + } + })) + .toList(); + threads.forEach(Thread::start); + LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1)); + // Start the race + starter.countDown(); + threads.forEach(StableValueTest::join); + // There can only be one winner + assertEquals(1, winner.cardinality()); + } + + private static void join(Thread thread) { + try { + thread.join(); + } catch (InterruptedException e) { + fail(e); + } + } + +} \ No newline at end of file diff --git a/test/jdk/jdk/internal/lang/stable/TrustedFieldTypeTest.java b/test/jdk/jdk/internal/lang/stable/TrustedFieldTypeTest.java new file mode 100644 index 0000000000000..3057bc4f26ce4 --- /dev/null +++ b/test/jdk/jdk/internal/lang/stable/TrustedFieldTypeTest.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2024, 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 TrustedFieldType implementations + * @modules jdk.unsupported/sun.misc + * @modules java.base/jdk.internal.lang + * @modules java.base/jdk.internal.lang.stable + * @compile --enable-preview -source ${jdk.version} TrustedFieldTypeTest.java + * @run junit/othervm --enable-preview TrustedFieldTypeTest + */ + +import jdk.internal.lang.StableValue; +import org.junit.jupiter.api.Test; + +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; +import java.lang.reflect.Field; +import java.lang.reflect.InaccessibleObjectException; + +import static org.junit.jupiter.api.Assertions.*; + +final class TrustedFieldTypeTest { + + @Test + void reflection() throws NoSuchFieldException { + final class Holder { + private final StableValue value = StableValue.of(); + } + final class HolderNonFinal { + private StableValue value = StableValue.of(); + } + + Field valueField = Holder.class.getDeclaredField("value"); + assertThrows(InaccessibleObjectException.class, () -> + valueField.setAccessible(true) + ); + Field valueNonFinal = HolderNonFinal.class.getDeclaredField("value"); + assertDoesNotThrow(() -> valueNonFinal.setAccessible(true)); + } + + @Test + void sunMiscUnsafe() throws NoSuchFieldException, IllegalAccessException { + Field unsafeField = sun.misc.Unsafe.class.getDeclaredField("theUnsafe"); + unsafeField.setAccessible(true); + sun.misc.Unsafe unsafe = (sun.misc.Unsafe)unsafeField.get(null); + + final class Holder { + private final StableValue value = StableValue.of(); + } + + Field valueField = Holder.class.getDeclaredField("value"); + assertThrows(UnsupportedOperationException.class, () -> + unsafe.objectFieldOffset(valueField) + ); + + } + + @Test + void varHandle() throws NoSuchFieldException, IllegalAccessException { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + + StableValue originalValue = StableValue.of(); + + final class Holder { + private final StableValue value = originalValue; + } + + VarHandle valueVarHandle = lookup.findVarHandle(Holder.class, "value", StableValue.class); + Holder holder = new Holder(); + + assertThrows(UnsupportedOperationException.class, () -> + valueVarHandle.set(holder, StableValue.of()) + ); + + assertThrows(UnsupportedOperationException.class, () -> + valueVarHandle.compareAndSet(holder, originalValue, StableValue.of()) + ); + + } + +} \ No newline at end of file From 2c27c9a1a8ec05999a984ea0c8e412853a0169b0 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 4 Jun 2024 12:26:04 +0200 Subject: [PATCH 002/327] Add method to StableValue --- .../jdk/internal/lang/StableValue.java | 12 +++++++ .../internal/lang/stable/StableValueImpl.java | 35 ++++++++++++++----- 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/src/java.base/share/classes/jdk/internal/lang/StableValue.java b/src/java.base/share/classes/jdk/internal/lang/StableValue.java index d30bfa4263ab1..e81afc21f63a8 100644 --- a/src/java.base/share/classes/jdk/internal/lang/StableValue.java +++ b/src/java.base/share/classes/jdk/internal/lang/StableValue.java @@ -26,6 +26,7 @@ package jdk.internal.lang; import jdk.internal.access.SharedSecrets; +import jdk.internal.lang.stable.StableUtil; import jdk.internal.lang.stable.StableValueImpl; import java.util.List; @@ -33,6 +34,7 @@ import java.util.NoSuchElementException; import java.util.Objects; import java.util.Set; +import java.util.function.Consumer; import java.util.function.Function; import java.util.function.IntFunction; import java.util.function.Supplier; @@ -93,6 +95,14 @@ public sealed interface StableValue */ boolean isSet(); + /** + * If a value is set, performs the given action with the set value, + * otherwise does nothing. + * + * @param action the action to be performed, if a value is set + */ + void ifSet(Consumer action); + /** * If the stable value is unset, attempts to compute its value using the given * supplier function and enters it into this stable value. @@ -178,6 +188,8 @@ default void setOrThrow(T value) { } } + + // Factories /** 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 aa0253bda5c55..cfd40c41efff5 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 @@ -32,6 +32,7 @@ import java.util.NoSuchElementException; import java.util.Objects; +import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; @@ -56,8 +57,7 @@ private StableValueImpl() {} @Override public boolean trySet(T value) { synchronized (mutex) { - return UNSAFE.compareAndSetReference(this, VALUE_OFFSET, - null, (value == null) ? nullSentinel() : value); + return UNSAFE.compareAndSetReference(this, VALUE_OFFSET, null, wrap(value)); } } @@ -66,7 +66,7 @@ public boolean trySet(T value) { public T orElseThrow() { final T t = value; if (t != null) { - return t == nullSentinel() ? null : t; + return unwrap(t); } throw new NoSuchElementException("No value set"); } @@ -76,7 +76,7 @@ public T orElseThrow() { public T orElse(T other) { final T t = value; if (t != null) { - return t == nullSentinel() ? null : t; + return unwrap(t); } return other; } @@ -86,12 +86,20 @@ public boolean isSet() { return value != null; } + @Override + public void ifSet(Consumer action) { + T t = value; + if (t != null) { + action.accept(unwrap(t)); + } + } + @ForceInline @Override public T computeIfUnset(Supplier supplier) { final T t = value; if (t != null) { - return t == nullSentinel() ? null : t; + return unwrap(t); } return compute(supplier); } @@ -101,7 +109,7 @@ private T compute(Supplier supplier) { synchronized (mutex) { T t = value; if (t != null) { - return t == nullSentinel() ? null : t; + return unwrap(t); } t = supplier.get(); trySet(t); @@ -114,7 +122,7 @@ private T compute(Supplier supplier) { public T computeIfUnset(I input, Function mapper) { final T t = value; if (t != null) { - return t == nullSentinel() ? null : t; + return unwrap(t); } return compute(input, mapper); } @@ -124,7 +132,7 @@ private T compute(I input, Function mapper) { synchronized (mutex) { T t = value; if (t != null) { - return t == nullSentinel() ? null : t; + return unwrap(t); } t = mapper.apply(input); trySet(t); @@ -132,7 +140,6 @@ private T compute(I input, Function mapper) { } } - @Override public int hashCode() { return Objects.hashCode(orElse(null)); @@ -149,6 +156,16 @@ public String toString() { return "StableValue" + render(value); } + // Wraps null values into a sentinel value + private static T wrap(T t) { + return (t == null) ? nullSentinel() : t; + } + + // Unwraps null sentinel values into null + private static T unwrap(T t) { + return t == nullSentinel() ? null : t; + } + // Factory public static StableValueImpl of() { return new StableValueImpl<>(); From 3f7a904f1493ba8dfd475a4a035f0310be558a15 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 5 Jun 2024 16:48:33 +0200 Subject: [PATCH 003/327] Scale down --- .../java/util/ImmutableCollections.java | 155 ------------ .../access/JavaUtilCollectionAccess.java | 7 - .../jdk/internal/lang/StableValue.java | 232 +++++------------- .../jdk/internal/lang/stable/StableUtil.java | 50 ---- .../internal/lang/stable/StableValueImpl.java | 55 ++--- .../lang/stable/MemoizedFunctionTest.java | 126 ---------- .../lang/stable/MemoizedIntFunctionTest.java | 121 --------- .../lang/stable/MemoizedSupplierTest.java | 63 ----- .../internal/lang/stable/StableValueTest.java | 24 +- .../lang/stable/TrustedFieldTypeTest.java | 12 +- 10 files changed, 100 insertions(+), 745 deletions(-) delete mode 100644 src/java.base/share/classes/jdk/internal/lang/stable/StableUtil.java delete mode 100644 test/jdk/jdk/internal/lang/stable/MemoizedFunctionTest.java delete mode 100644 test/jdk/jdk/internal/lang/stable/MemoizedIntFunctionTest.java delete mode 100644 test/jdk/jdk/internal/lang/stable/MemoizedSupplierTest.java diff --git a/src/java.base/share/classes/java/util/ImmutableCollections.java b/src/java.base/share/classes/java/util/ImmutableCollections.java index 65308b4936004..8fe7b761ef28b 100644 --- a/src/java.base/share/classes/java/util/ImmutableCollections.java +++ b/src/java.base/share/classes/java/util/ImmutableCollections.java @@ -34,14 +34,11 @@ import java.lang.reflect.Array; import java.util.function.BiFunction; import java.util.function.Function; -import java.util.function.IntFunction; import java.util.function.Predicate; import java.util.function.UnaryOperator; -import jdk.internal.ValueBased; import jdk.internal.access.JavaUtilCollectionAccess; import jdk.internal.access.SharedSecrets; -import jdk.internal.lang.StableValue; import jdk.internal.misc.CDS; import jdk.internal.vm.annotation.Stable; @@ -130,14 +127,6 @@ public List listFromTrustedArray(Object[] array) { public List listFromTrustedArrayNullsAllowed(Object[] array) { return ImmutableCollections.listFromTrustedArrayNullsAllowed(array); } - public List listFromStable(List> delegate, - IntFunction mapper) { - return new StableList<>(delegate, mapper); - } - public Map mapFromStable(Map> delegate, - Function mapper) { - return new StableMap<>(delegate, mapper); - } }); } } @@ -1373,150 +1362,6 @@ private Object writeReplace() { } } - @ValueBased - static final class StableList extends AbstractImmutableList { - - private final List> delegate; - private final IntFunction mapper; - - public StableList(List> delegate, - IntFunction mapper) { - this.delegate = delegate; - this.mapper = mapper; - } - - @Override - public int size() { - return delegate.size(); - } - - @Override - public E get(int index) { - return delegate.get(index) - .computeIfUnset(index, mapper::apply); - } - - @Override - public int indexOf(Object o) { - for (int i = 0; i < size(); i++) { - if (get(i).equals(o)) { - return i; - } - } - return -1; - } - - @Override - public int lastIndexOf(Object o) { - for (int i = size() - 1; i >= 0; i--) { - if (get(i).equals(o)) { - return i; - } - } - return -1; - } - } - - static final class StableMap extends AbstractImmutableMap { - - @Stable - private final Map> delegate; - @Stable - private final Function mapper; - - public StableMap(Map> delegate, - Function mapper) { - this.delegate = delegate; - this.mapper = mapper; - } - - @Override - public boolean containsKey(Object o) { - Objects.requireNonNull(o); - return delegate.containsKey(o); - } - - @Override - public boolean containsValue(Object o) { - return delegate.entrySet().stream() - .anyMatch(e -> value(e).equals(o)); - } - - @Override - public int hashCode() { - return delegate.entrySet().stream() - .reduce(0, - (h, e) -> h + (e.getKey().hashCode()) ^ ((value(e)).hashCode()), - (a, b) -> a); - } - - @Override - @SuppressWarnings("unchecked") - public V get(Object o) { - StableValue stable = delegate.get(o); - return stable == null - ? null - : stable.computeIfUnset((K)o, mapper); - } - - @Override - public int size() { - return delegate.size(); - } - - @Override - public boolean isEmpty() { - return delegate.isEmpty(); - } - - final class StableMapIterator implements Iterator> { - - @Stable - private final Iterator>> delegateIterator; - - StableMapIterator() { - delegateIterator = StableMap.this.delegate.entrySet().iterator(); - } - - @Override - public boolean hasNext() { - return delegateIterator.hasNext(); - } - - @Override - public Map.Entry next() { - Map.Entry> dNext = delegateIterator.next(); - return new KeyValueHolder<>(dNext.getKey(), value(dNext)); - } - } - - @Override - public Set> entrySet() { - return new AbstractImmutableSet<>() { - @Override - public int size() { - return StableMap.this.size(); - } - - @Override - public Iterator> iterator() { - return new StableMap.StableMapIterator(); - } - - @Override - public int hashCode() { - return StableMap.this.hashCode(); - } - }; - } - - // Unwraps a value - private V value(Entry> e) { - return e.getValue().computeIfUnset(e.getKey(), mapper); - } - - } - } // ---------- Serialization Proxy ---------- diff --git a/src/java.base/share/classes/jdk/internal/access/JavaUtilCollectionAccess.java b/src/java.base/share/classes/jdk/internal/access/JavaUtilCollectionAccess.java index fa2592b16df4b..f88d57521ac5a 100644 --- a/src/java.base/share/classes/jdk/internal/access/JavaUtilCollectionAccess.java +++ b/src/java.base/share/classes/jdk/internal/access/JavaUtilCollectionAccess.java @@ -25,16 +25,9 @@ package jdk.internal.access; -import jdk.internal.lang.StableValue; - import java.util.List; -import java.util.Map; -import java.util.function.Function; -import java.util.function.IntFunction; public interface JavaUtilCollectionAccess { List listFromTrustedArray(Object[] array); List listFromTrustedArrayNullsAllowed(Object[] array); - List listFromStable(List> delegate, IntFunction mapper); - Map mapFromStable(Map> delegate, Function mapper); } diff --git a/src/java.base/share/classes/jdk/internal/lang/StableValue.java b/src/java.base/share/classes/jdk/internal/lang/StableValue.java index e81afc21f63a8..0e91f1d3295c6 100644 --- a/src/java.base/share/classes/jdk/internal/lang/StableValue.java +++ b/src/java.base/share/classes/jdk/internal/lang/StableValue.java @@ -25,21 +25,10 @@ package jdk.internal.lang; -import jdk.internal.access.SharedSecrets; -import jdk.internal.lang.stable.StableUtil; import jdk.internal.lang.stable.StableValueImpl; -import java.util.List; -import java.util.Map; import java.util.NoSuchElementException; -import java.util.Objects; -import java.util.Set; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.IntFunction; import java.util.function.Supplier; -import java.util.stream.Collectors; -import java.util.stream.Stream; /** * A thin, atomic, thread-safe, set-at-most-once, stable value holder eligible for @@ -49,9 +38,67 @@ * from unset to set and consequently, a value can only be set * at most once.

- * To create a new fresh (unset) StableValue, use the {@linkplain StableValue#of()} + * To create a new fresh (unset) StableValue, use the {@linkplain StableValue#newInstance()} * factory. *

+ * A List of stable values with a given {@code size} can be created the following way: + * {@snippet lang = java : + * List> list = Stream.generate(StableValue::newInstance) + * .limit(size) + * .toList(); + * } + * The list can be used to model stable arrays of one dimensions. If two or more + * dimensional arrays are to be modeled, List of List of ... of StableValue can be used. + *

+ * A Map of stable values with a given set of {@code keys} can be created like this: + * {@snippet lang = java : + * Map> map = keys.stream() + * .collect(Collectors.toMap(Function.identity(), _ -> StableValue.newInstance())); + * } + * A memoized Supplier, where the given {@code original} supplier is guaranteed to be + * successfully invoked at most once even in a multi-threaded environment, can be + * created like this: + * {@snippet lang = java : + * static Supplier memoizedSupplier(Supplier original) { + * Objects.requireNonNull(original); + * final StableValue stable = StableValue.newInstance(); + * return () -> stable.computeIfUnset(original); + * } + * } + * A memoized IntFunction, for the allowed given {@code size} values and where the + * given {@code original} IntFunction is guaranteed to be successfully invoked at most + * once per inout index even in a multi-threaded environment, can be created like this: + * {@snippet lang = java : + * static IntFunction memoizedIntFunction(int size, + * IntFunction original) { + * List> backing = Stream.generate(StableValue::newInstance) + * .limit(size) + * .toList(); + * return i -> backing.get(i) + * .computeIfUnset(() -> original.apply(i)); + * } + * } + * A memoized Function, for the allowed given {@code input} values and where the + * given {@code original} function is guaranteed to be successfully invoked at most + * once per input value even in a multi-threaded environment, can be created like this: + * {@snippet lang = java : + * static Function memoizedFunction(Set inputs, + * Function original) { + * Map> backing = inputs.stream() + * .collect(Collectors.toMap(Function.identity(), _ -> StableValue.newInstance())); + * return t -> { + * if (!backing.containsKey(t)) { + * throw new IllegalArgumentException("Input not allowed: "+t); + * } + * return backing.get(t) + * .computeIfUnset(() -> original.apply(t)); + * }; + * } + * } + *

+ * The constructs above are eligible for similar JVM optimizations as the StableValue + * itself. + *

* All methods that can set the stable value's value are guarded such that competing * set operations (by other threads) will block if another set operation is * already in progress. @@ -61,7 +108,7 @@ * * @param type of the wrapped value * - * @since 23 + * @since 24 */ public sealed interface StableValue permits StableValueImpl { @@ -95,14 +142,6 @@ public sealed interface StableValue */ boolean isSet(); - /** - * If a value is set, performs the given action with the set value, - * otherwise does nothing. - * - * @param action the action to be performed, if a value is set - */ - void ifSet(Consumer action); - /** * If the stable value is unset, attempts to compute its value using the given * supplier function and enters it into this stable value. @@ -135,43 +174,6 @@ public sealed interface StableValue */ T computeIfUnset(Supplier supplier); - /** - * If the stable value is unset, attempts to compute its value using the given - * {@code input} parameter and the provided {@code mapper} function and enters - * it into this stable value. - * - *

If the mapper function itself throws an (unchecked) exception, the exception - * is rethrown, and no value is set. The most common usage is to construct a new - * object serving as an initial value or memoized result in a {@code mop}, as in: - * - *

 {@code
-     * Map> map = ...
-     * K key = ...
-     * T t = map.get(key).computeIfUnset(key, k -> new T(k));
-     * }
- * - * @implSpec - * The default implementation is equivalent to the following steps for this - * {@code stable} value: - * - *
 {@code
-     * if (stable.isSet()) {
-     *     return stable.getOrThrow();
-     * }
-     * T newValue = mapper.apply(input);
-     * stable.trySet(newValue);
-     * return newValue;
-     * }
- * Except, the method is atomic and thread-safe. - * -\ * @param input to be used with the provided {@code mapper} - * @param mapper the mapping function to compute a value from the {@code input} - * @return the current (existing or computed) value associated with - * the stable value - */ - T computeIfUnset(I input, Function mapper); - - // Convenience methods /** @@ -189,121 +191,15 @@ default void setOrThrow(T value) { } - - // Factories + // Factory /** - * {@return a fresh stable value with an unset ({@code null}) value} + * {@return a fresh stable value with an unset value} * * @param the value type to set */ - static StableValue of() { - return StableValueImpl.of(); - } - - /** - * {@return a lazily computed, atomic, thread-safe, set-at-most-once-per-index, - * List of stable elements eligible for certain JVM optimizations} - * - * @param size the size of the returned list - * @param mapper to invoke when an element is to be computed - * @param the {@code List}'s element type - */ - static List ofList(int size, - IntFunction mapper) { - if (size < 0) { - throw new IllegalArgumentException(); - } - if (size == 0) { - return List.of(); - } - Objects.requireNonNull(mapper); - List> backing = Stream.generate(StableValue::of) - .limit(size) - .toList(); - - return SharedSecrets.getJavaUtilCollectionAccess() - .listFromStable(backing, mapper); - }; - - /** - * {@return a lazily computed, atomic, thread-safe, set-at-most-once-per-key, - * Map of stable elements eligible for certain JVM optimizations} - * @param keys the keys in the {@code Map} - * @param mapper to invoke when a value is to be computed - * @param the {@code Map}'s key type - * @param the {@code Map}'s value type - */ - static Map ofMap(Set keys, - Function mapper) { - Objects.requireNonNull(keys); - Objects.requireNonNull(mapper); - Map> backing = keys.stream() - .collect(Collectors.toMap(Function.identity(), _ -> StableValue.of())); - return SharedSecrets.getJavaUtilCollectionAccess() - .mapFromStable(backing, mapper); + static StableValue newInstance() { + return StableValueImpl.newInstance(); } - /** - * {@return a new thread-safe, stable, lazily computed {@linkplain Supplier supplier} - * that records the value of the provided {@code original} supplier upon being first - * accessed via {@linkplain Supplier#get()}} - *

- * The provided {@code original} supplier is guaranteed to be successfully invoked - * at most once even in a multi-threaded environment. Competing threads invoking the - * {@linkplain Supplier#get()} method when a value is already under computation - * will block until a value is computed or an exception is thrown by the - * computing thread. - *

- * If the {@code original} Supplier invokes the returned Supplier recursively, - * a StackOverflowError will be thrown when the returned - * Supplier's {@linkplain Function#apply(Object)}} method is invoked. - *

- * If the provided {@code original} supplier throws an exception, it is relayed - * to the initial caller. Subsequent read operations will incur a new invocation - * of the provided {@code original}. - * - * @param original supplier - * @param the type of results supplied by the returned supplier - */ - static Supplier memoizedSupplier(Supplier original) { - Objects.requireNonNull(original); - final StableValue stable = StableValue.of(); - return () -> stable.computeIfUnset(original); - } - - /** - * {@return a memoized IntFunction backed by a {@linkplain #ofList(int, IntFunction) - * stable list} where the provided {@code original} will be invoked at most once per - * index} - * @param size the allowed input values, [0, size) - * @param original to invoke when an element is to be computed - * @param the type of the result of the function - */ - static IntFunction memoizedIntFunction(int size, - IntFunction original) { - return ofList(size, original)::get; - } - - /** - * {@return a memoized Function backed by a {@linkplain #ofMap(Set, Function) - * stable map} where the provided {@code original} will be invoked at most once per - * index} - * @param inputs the allowed input values - * @param original to invoke when an element is to be computed - * @param the type of the input to the function - * @param the type of the result of the function - */ - static Function memoizedFunction(Set inputs, - Function original) { - final Map backing = ofMap(inputs, original); - return t -> { - if (!backing.containsKey(t)) { - throw new IllegalArgumentException("Input not allowed: "+t); - } - return backing.get(t); - }; - } - - } \ No newline at end of file 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 deleted file mode 100644 index 29d3bb8a22919..0000000000000 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableUtil.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) 2024, 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; - -final class StableUtil { - - private StableUtil() {} - - static final Unsafe UNSAFE = Unsafe.getUnsafe(); - - private static final Object NULL_SENTINEL = new Object(); - - @SuppressWarnings("unchecked") - static T nullSentinel() { - return (T) NULL_SENTINEL; - } - - static String render(T t) { - if (t != null) { - return t == nullSentinel() ? "[null]" : "[" + t + "]"; - } - return ".unset"; - } - -} \ No newline at end of file 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 cfd40c41efff5..79ba781f3956b 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 @@ -26,20 +26,21 @@ package jdk.internal.lang.stable; import jdk.internal.lang.StableValue; +import jdk.internal.misc.Unsafe; import jdk.internal.vm.annotation.DontInline; import jdk.internal.vm.annotation.ForceInline; import jdk.internal.vm.annotation.Stable; import java.util.NoSuchElementException; import java.util.Objects; -import java.util.function.Consumer; -import java.util.function.Function; import java.util.function.Supplier; -import static jdk.internal.lang.stable.StableUtil.*; - public final class StableValueImpl implements StableValue { + static final Unsafe UNSAFE = Unsafe.getUnsafe(); + + private static final Object NULL_SENTINEL = new Object(); + private static final long VALUE_OFFSET = UNSAFE.objectFieldOffset(StableValueImpl.class, "value"); @@ -86,14 +87,6 @@ public boolean isSet() { return value != null; } - @Override - public void ifSet(Consumer action) { - T t = value; - if (t != null) { - action.accept(unwrap(t)); - } - } - @ForceInline @Override public T computeIfUnset(Supplier supplier) { @@ -117,29 +110,6 @@ private T compute(Supplier supplier) { } } - @ForceInline - @Override - public T computeIfUnset(I input, Function mapper) { - final T t = value; - if (t != null) { - return unwrap(t); - } - return compute(input, mapper); - } - - @DontInline - private T compute(I input, Function mapper) { - synchronized (mutex) { - T t = value; - if (t != null) { - return unwrap(t); - } - t = mapper.apply(input); - trySet(t); - return orElseThrow(); - } - } - @Override public int hashCode() { return Objects.hashCode(orElse(null)); @@ -166,8 +136,21 @@ private static T unwrap(T t) { return t == nullSentinel() ? null : t; } + @SuppressWarnings("unchecked") + static T nullSentinel() { + return (T) NULL_SENTINEL; + } + + static String render(T t) { + if (t != null) { + return t == nullSentinel() ? "[null]" : "[" + t + "]"; + } + return ".unset"; + } + + // Factory - public static StableValueImpl of() { + public static StableValueImpl newInstance() { return new StableValueImpl<>(); } diff --git a/test/jdk/jdk/internal/lang/stable/MemoizedFunctionTest.java b/test/jdk/jdk/internal/lang/stable/MemoizedFunctionTest.java deleted file mode 100644 index 5be311013f14a..0000000000000 --- a/test/jdk/jdk/internal/lang/stable/MemoizedFunctionTest.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (c) 2024, 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 StableValue implementations - * @modules java.base/jdk.internal.lang - * @compile --enable-preview -source ${jdk.version} MemoizedFunctionTest.java - * @run junit/othervm --enable-preview MemoizedFunctionTest - */ - -import jdk.internal.lang.StableValue; -import org.junit.jupiter.api.Test; - -import java.util.List; -import java.util.NoSuchElementException; -import java.util.Set; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.LockSupport; -import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.stream.IntStream; -import java.util.stream.Stream; - -import static org.junit.jupiter.api.Assertions.*; - -final class MemoizedFunctionTest { - - private static final int SIZE = 3; - private static final int INDEX = 1; - private static final Set INPUTS = IntStream.range(0, SIZE).boxed().collect(Collectors.toSet()); - private static final Function FUNCTION = Function.identity(); - - @Test - void basic() { - StableTestUtil.CountingFunction original = new StableTestUtil.CountingFunction<>(FUNCTION); - Function function = StableValue.memoizedFunction(INPUTS, original); - assertEquals(INDEX, function.apply(INDEX)); - assertEquals(1, original.cnt()); - assertEquals(INDEX, function.apply(INDEX)); - assertEquals(1, original.cnt()); - assertThrows(IllegalArgumentException.class, () -> function.apply(SIZE)); - } - - @Test - void empty() { - StableTestUtil.CountingFunction original = new StableTestUtil.CountingFunction<>(FUNCTION); - Function function = StableValue.memoizedFunction(Set.of(), original); - assertThrows(IllegalArgumentException.class, () -> function.apply(INDEX)); - } - -/* @Test - void toStringTest() { - Function function = StableValue.memoizedFunction(INPUTS, FUNCTION); - String expectedEmpty = "MemoizedFunction[original=" + FUNCTION; - assertTrue(function.toString().startsWith(expectedEmpty), function.toString()); - function.apply(INDEX); - assertTrue(function.toString().contains("[" + INDEX + "]"), function.toString()); - }*/ - - @Test - void shakedownSingleThread() { - for (int size = 0; size < 128; size++) { - Function memo = StableValue.memoizedFunction(IntStream.range(0, size).boxed().collect(Collectors.toSet()), Function.identity()); - for (int i = 0; i < size; i++) { - assertEquals(i, memo.apply(i)); - } - } - } - - @Test - void shakedownMultiThread() { - int threadCount = 8; - var startGate = new CountDownLatch(1); - for (int size = 0; size < 128; size++) { - Function memo = StableValue.memoizedFunction(IntStream.range(0, size).boxed().collect(Collectors.toSet()), Function.identity()); - final int fSize = size; - List threads = Stream.generate(() -> new Thread(() -> { - while (startGate.getCount() != 0) { - Thread.onSpinWait(); - } - for (int i = 0; i < fSize; i++) { - int value = memo.apply(i); - assertEquals(i, value); - } - })) - .limit(threadCount) - .toList(); - threads.forEach(Thread::start); - // Give some time for the threads to start - LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1)); - // Release them as close as possible - startGate.countDown(); - threads.forEach(MemoizedFunctionTest::join); - } - } - - static void join(Thread t) { - try { - t.join(); - } catch (InterruptedException e) { - fail(e); - } - } - -} \ No newline at end of file diff --git a/test/jdk/jdk/internal/lang/stable/MemoizedIntFunctionTest.java b/test/jdk/jdk/internal/lang/stable/MemoizedIntFunctionTest.java deleted file mode 100644 index 491e458dbfd51..0000000000000 --- a/test/jdk/jdk/internal/lang/stable/MemoizedIntFunctionTest.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (c) 2024, 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 StableValue implementations - * @modules java.base/jdk.internal.lang - * @compile --enable-preview -source ${jdk.version} MemoizedIntFunctionTest.java - * @run junit/othervm --enable-preview MemoizedIntFunctionTest - */ - -import jdk.internal.lang.StableValue; -import org.junit.jupiter.api.Test; - -import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.LockSupport; -import java.util.function.IntFunction; -import java.util.stream.Stream; - -import static org.junit.jupiter.api.Assertions.*; - -final class MemoizedIntFunctionTest { - - private static final int SIZE = 3; - private static final int INDEX = 1; - private static final IntFunction FUNCTION = i -> i; - - @Test - void basic() { - StableTestUtil.CountingIntFunction original = new StableTestUtil.CountingIntFunction<>(FUNCTION); - IntFunction function = StableValue.memoizedIntFunction(SIZE, original); - assertEquals(INDEX, function.apply(INDEX)); - assertEquals(1, original.cnt()); - assertEquals(INDEX, function.apply(INDEX)); - assertEquals(1, original.cnt()); - assertThrows(IndexOutOfBoundsException.class, () -> function.apply(SIZE)); - } - - @Test - void empty() { - StableTestUtil.CountingIntFunction original = new StableTestUtil.CountingIntFunction<>(FUNCTION); - IntFunction function = StableValue.memoizedIntFunction(0, original); - assertThrows(IndexOutOfBoundsException.class, () -> function.apply(INDEX)); - } - -/* @Test - void toStringTest() { - IntFunction function = StableValue.memoizedIntFunction(SIZE, FUNCTION); - String expectedEmpty = "MemoizedIntFunction[original=" + FUNCTION + ", delegate=StableArray[.unset, .unset, .unset]"; - assertTrue(function.toString().startsWith(expectedEmpty), function.toString()); - function.apply(INDEX); - assertTrue(function.toString().contains("delegate=StableArray[.unset, [" + INDEX + "], .unset]"), function.toString()); - }*/ - - @Test - void shakedownSingleThread() { - for (int size = 0; size < 128; size++) { - IntFunction memo = StableValue.memoizedIntFunction(size, i -> i); - for (int i = 0; i < size; i++) { - assertEquals(i, memo.apply(i)); - } - } - } - - @Test - void shakedownMultiThread() { - int threadCount = 8; - var startGate = new CountDownLatch(1); - for (int size = 0; size < 128; size++) { - IntFunction memo = StableValue.memoizedIntFunction(size, i -> i); - final int fSize = size; - List threads = Stream.generate(() -> new Thread(() -> { - while (startGate.getCount() != 0) { - Thread.onSpinWait(); - } - for (int i = 0; i < fSize; i++) { - int value = memo.apply(i); - assertEquals(i, value); - } - })) - .limit(threadCount) - .toList(); - threads.forEach(Thread::start); - // Give some time for the threads to start - LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1)); - // Release them as close as possible - startGate.countDown(); - threads.forEach(MemoizedIntFunctionTest::join); - } - } - - static void join(Thread t) { - try { - t.join(); - } catch (InterruptedException e) { - fail(e); - } - } - -} \ No newline at end of file diff --git a/test/jdk/jdk/internal/lang/stable/MemoizedSupplierTest.java b/test/jdk/jdk/internal/lang/stable/MemoizedSupplierTest.java deleted file mode 100644 index dcc552702823d..0000000000000 --- a/test/jdk/jdk/internal/lang/stable/MemoizedSupplierTest.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (c) 2024, 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 StableValue implementations - * @modules java.base/jdk.internal.lang - * @compile --enable-preview -source ${jdk.version} MemoizedSupplierTest.java - * @run junit/othervm --enable-preview MemoizedSupplierTest - */ - -import jdk.internal.lang.StableValue; -import org.junit.jupiter.api.Test; - -import java.util.function.Supplier; - -import static org.junit.jupiter.api.Assertions.*; - -final class MemoizedSupplierTest { - - private static final int VALUE = 42; - private static final Supplier SUPPLIER = () -> VALUE; - - @Test - void basic() { - StableTestUtil.CountingSupplier original = new StableTestUtil.CountingSupplier<>(SUPPLIER); - Supplier supplier = StableValue.memoizedSupplier(original); - assertEquals(VALUE, supplier.get()); - assertEquals(1, original.cnt()); - assertEquals(VALUE, supplier.get()); - assertEquals(1, original.cnt()); - } - -/* @Test - void toStringTest() { - Supplier supplier = StableValue.memoizedSupplier(SUPPLIER); - String expectedEmpty = "MemoizedSupplier[original=" + SUPPLIER + ", delegate=StableValue.unset]"; - assertEquals(expectedEmpty, supplier.toString()); - supplier.get(); - String expectedSet = "MemoizedSupplier[original=" + SUPPLIER + ", delegate=StableValue[" + VALUE + "]]"; - assertEquals(expectedSet, supplier.toString()); - }*/ - -} \ No newline at end of file diff --git a/test/jdk/jdk/internal/lang/stable/StableValueTest.java b/test/jdk/jdk/internal/lang/stable/StableValueTest.java index d15944a08c444..05813a35bc8c8 100644 --- a/test/jdk/jdk/internal/lang/stable/StableValueTest.java +++ b/test/jdk/jdk/internal/lang/stable/StableValueTest.java @@ -39,9 +39,7 @@ import java.util.concurrent.locks.LockSupport; import java.util.function.BiPredicate; import java.util.function.Supplier; -import java.util.stream.Collectors; import java.util.stream.IntStream; -import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -50,7 +48,7 @@ final class StableValueTest { @Test void unset() { - StableValue stable = StableValue.of(); + StableValue stable = StableValue.newInstance(); assertNull(stable.orElse(null)); assertThrows(NoSuchElementException.class, stable::orElseThrow); assertEquals("StableValue.unset", stable.toString()); @@ -62,7 +60,7 @@ void unset() { @Test void setNull() { - StableValue stable = StableValue.of(); + StableValue stable = StableValue.newInstance(); assertTrue(stable.trySet(null)); assertEquals("StableValue[null]", stable.toString()); assertNull(stable.orElse(13)); @@ -72,7 +70,7 @@ void setNull() { @Test void setNonNull() { - StableValue stable = StableValue.of(); + StableValue stable = StableValue.newInstance(); assertTrue(stable.trySet(42)); assertEquals("StableValue[42]", stable.toString()); assertEquals(42, stable.orElse(null)); @@ -84,7 +82,7 @@ void setNonNull() { @Test void computeIfUnset() { - StableValue stable = StableValue.of(); + StableValue stable = StableValue.newInstance(); assertEquals(42, stable.computeIfUnset(() -> 42)); assertEquals(42, stable.computeIfUnset(() -> 13)); assertEquals("StableValue[42]", stable.toString()); @@ -96,7 +94,7 @@ void computeIfUnset() { @Test void computeIfUnsetException() { - StableValue stable = StableValue.of(); + StableValue stable = StableValue.newInstance(); Supplier supplier = () -> { throw new UnsupportedOperationException("aaa"); }; @@ -112,8 +110,8 @@ void computeIfUnsetException() { @Test void testHashCode() { - StableValue s0 = StableValue.of(); - StableValue s1 = StableValue.of(); + StableValue s0 = StableValue.newInstance(); + StableValue s1 = StableValue.newInstance(); assertEquals(s0.hashCode(), s1.hashCode()); s0.setOrThrow(42); s1.setOrThrow(42); @@ -122,13 +120,13 @@ void testHashCode() { @Test void testEquals() { - StableValue s0 = StableValue.of(); - StableValue s1 = StableValue.of(); + StableValue s0 = StableValue.newInstance(); + StableValue s1 = StableValue.newInstance(); assertEquals(s0, s1); s0.setOrThrow(42); s1.setOrThrow(42); assertEquals(s0, s1); - StableValue other = StableValue.of(); + StableValue other = StableValue.newInstance(); other.setOrThrow(13); assertNotEquals(s0, other); assertNotEquals(s0, "a"); @@ -176,7 +174,7 @@ void raceMixed() { void race(BiPredicate, Integer> winnerPredicate) { int noThreads = 10; CountDownLatch starter = new CountDownLatch(1); - StableValue stable = StableValue.of(); + StableValue stable = StableValue.newInstance(); BitSet winner = new BitSet(noThreads); List threads = IntStream.range(0, noThreads).mapToObj(i -> new Thread(() -> { try { diff --git a/test/jdk/jdk/internal/lang/stable/TrustedFieldTypeTest.java b/test/jdk/jdk/internal/lang/stable/TrustedFieldTypeTest.java index 3057bc4f26ce4..e01fe429fc776 100644 --- a/test/jdk/jdk/internal/lang/stable/TrustedFieldTypeTest.java +++ b/test/jdk/jdk/internal/lang/stable/TrustedFieldTypeTest.java @@ -45,10 +45,10 @@ final class TrustedFieldTypeTest { @Test void reflection() throws NoSuchFieldException { final class Holder { - private final StableValue value = StableValue.of(); + private final StableValue value = StableValue.newInstance(); } final class HolderNonFinal { - private StableValue value = StableValue.of(); + private StableValue value = StableValue.newInstance(); } Field valueField = Holder.class.getDeclaredField("value"); @@ -66,7 +66,7 @@ void sunMiscUnsafe() throws NoSuchFieldException, IllegalAccessException { sun.misc.Unsafe unsafe = (sun.misc.Unsafe)unsafeField.get(null); final class Holder { - private final StableValue value = StableValue.of(); + private final StableValue value = StableValue.newInstance(); } Field valueField = Holder.class.getDeclaredField("value"); @@ -80,7 +80,7 @@ final class Holder { void varHandle() throws NoSuchFieldException, IllegalAccessException { MethodHandles.Lookup lookup = MethodHandles.lookup(); - StableValue originalValue = StableValue.of(); + StableValue originalValue = StableValue.newInstance(); final class Holder { private final StableValue value = originalValue; @@ -90,11 +90,11 @@ final class Holder { Holder holder = new Holder(); assertThrows(UnsupportedOperationException.class, () -> - valueVarHandle.set(holder, StableValue.of()) + valueVarHandle.set(holder, StableValue.newInstance()) ); assertThrows(UnsupportedOperationException.class, () -> - valueVarHandle.compareAndSet(holder, originalValue, StableValue.of()) + valueVarHandle.compareAndSet(holder, originalValue, StableValue.newInstance()) ); } From 65c5c2ebca651ae65e1235aec6cd27301ab4b4c8 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 5 Jun 2024 16:54:28 +0200 Subject: [PATCH 004/327] Revert update to java.lang.Class --- .../share/classes/java/lang/Class.java | 21 ++++--------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/src/java.base/share/classes/java/lang/Class.java b/src/java.base/share/classes/java/lang/Class.java index efed107dccdfb..c0b4cd456fedd 100644 --- a/src/java.base/share/classes/java/lang/Class.java +++ b/src/java.base/share/classes/java/lang/Class.java @@ -4766,23 +4766,10 @@ public Class[] getPermittedSubclasses() { return null; } if (subClasses.length > 0) { - // Neither Streams nor lambdas are used here in order to allow - // this method to be invoked early in the boot sequence. - boolean anyIsNotDirectSubclass = false; - for (Class c : subClasses) { - if (!isDirectSubType(c)) { - anyIsNotDirectSubclass = true; - break; - } - } - if (anyIsNotDirectSubclass) { - List> list = new ArrayList<>(); - for (Class subClass : subClasses) { - if (isDirectSubType(subClass)) { - list.add(subClass); - } - } - subClasses = list.toArray(new Class[0]); + if (Arrays.stream(subClasses).anyMatch(c -> !isDirectSubType(c))) { + subClasses = Arrays.stream(subClasses) + .filter(this::isDirectSubType) + .toArray(s -> new Class[s]); } } if (subClasses.length > 0) { From e0eb298c2310554d229953a45252ec8f322ffbb9 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 5 Jun 2024 17:50:39 +0200 Subject: [PATCH 005/327] Fix test --- .../build/tools/classlist/HelloClasslist.java | 12 ------------ make/test/BuildMicrobenchmark.gmk | 1 - .../share/classes/sun/misc/Unsafe.java | 2 +- .../internal/lang/stable/TrustedFieldTypeTest.java | 2 +- 4 files changed, 2 insertions(+), 15 deletions(-) diff --git a/make/jdk/src/classes/build/tools/classlist/HelloClasslist.java b/make/jdk/src/classes/build/tools/classlist/HelloClasslist.java index f178a1b5d2a03..ccf58a923ebc1 100644 --- a/make/jdk/src/classes/build/tools/classlist/HelloClasslist.java +++ b/make/jdk/src/classes/build/tools/classlist/HelloClasslist.java @@ -193,16 +193,4 @@ private static Object invoke(MethodHandle mh, Object ... args) throws Throwable } } - // Generate enhanced switch statements - // Todo: Enable when in a public API -/* MemoizedSupplier memoizedSupplier = - StableValue.memoizedSupplier(new Supplier() { - @Override public Integer get() { return 42; } - }); - memoizedSupplier.get(); - MemoizedIntSupplier intFunction = - StableArray.memoizedIntFunction(new IntFunction<>() { - @Override public Object apply(int value) { return 42; } - }); - intFunction.apply(0);*/ } diff --git a/make/test/BuildMicrobenchmark.gmk b/make/test/BuildMicrobenchmark.gmk index 5fedb5b74533a..b57040245a958 100644 --- a/make/test/BuildMicrobenchmark.gmk +++ b/make/test/BuildMicrobenchmark.gmk @@ -110,7 +110,6 @@ $(eval $(call SetupJavaCompilation, BUILD_JDK_MICROBENCHMARK, \ --add-exports java.base/sun.invoke.util=ALL-UNNAMED \ --add-exports java.base/sun.security.util=ALL-UNNAMED \ --add-exports java.base/jdk.internal.lang=ALL-UNNAMED \ - --add-exports java.base/jdk.internal.access=ALL-UNNAMED \ --add-exports java.base/sun.security.util.math=ALL-UNNAMED \ --add-exports java.base/sun.security.util.math.intpoly=ALL-UNNAMED \ --enable-preview \ diff --git a/src/jdk.unsupported/share/classes/sun/misc/Unsafe.java b/src/jdk.unsupported/share/classes/sun/misc/Unsafe.java index 68e76a14b16d9..ea90b86ffda06 100644 --- a/src/jdk.unsupported/share/classes/sun/misc/Unsafe.java +++ b/src/jdk.unsupported/share/classes/sun/misc/Unsafe.java @@ -734,7 +734,7 @@ private static void assertNotTrusted(Field f) { throw new UnsupportedOperationException("can't get base address on a record class: " + f); } Class fieldType = f.getType(); - if (fieldType.getName().equals("jdk.internal.lang.stable.StableValue")) { + if (fieldType.getName().equals("jdk.internal.lang.StableValue")) { throw new UnsupportedOperationException("can't get field offset for a field of type " + fieldType.getName() + ": " + f); } } diff --git a/test/jdk/jdk/internal/lang/stable/TrustedFieldTypeTest.java b/test/jdk/jdk/internal/lang/stable/TrustedFieldTypeTest.java index e01fe429fc776..bf0b6e7e60aaf 100644 --- a/test/jdk/jdk/internal/lang/stable/TrustedFieldTypeTest.java +++ b/test/jdk/jdk/internal/lang/stable/TrustedFieldTypeTest.java @@ -62,7 +62,7 @@ final class HolderNonFinal { @Test void sunMiscUnsafe() throws NoSuchFieldException, IllegalAccessException { Field unsafeField = sun.misc.Unsafe.class.getDeclaredField("theUnsafe"); - unsafeField.setAccessible(true); + assertTrue(unsafeField.trySetAccessible()); sun.misc.Unsafe unsafe = (sun.misc.Unsafe)unsafeField.get(null); final class Holder { From ddf73e8c9f918e247aba9f51174c9ae25edb551e Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 5 Jun 2024 18:02:19 +0200 Subject: [PATCH 006/327] Add tests and revert old changes --- .../build/tools/classlist/HelloClasslist.java | 1 - .../share/classes/java/lang/Class.java | 4 +-- .../classes/java/lang/reflect/Field.java | 2 +- .../java/util/ImmutableCollections.java | 2 -- .../internal/lang/stable/StableTestUtil.java | 35 ++----------------- .../internal/lang/stable/StableValueTest.java | 10 ++++-- 6 files changed, 13 insertions(+), 41 deletions(-) diff --git a/make/jdk/src/classes/build/tools/classlist/HelloClasslist.java b/make/jdk/src/classes/build/tools/classlist/HelloClasslist.java index ccf58a923ebc1..1b930ca752777 100644 --- a/make/jdk/src/classes/build/tools/classlist/HelloClasslist.java +++ b/make/jdk/src/classes/build/tools/classlist/HelloClasslist.java @@ -192,5 +192,4 @@ private static Object invoke(MethodHandle mh, Object ... args) throws Throwable throw t; } } - } diff --git a/src/java.base/share/classes/java/lang/Class.java b/src/java.base/share/classes/java/lang/Class.java index c0b4cd456fedd..4573a6dc69004 100644 --- a/src/java.base/share/classes/java/lang/Class.java +++ b/src/java.base/share/classes/java/lang/Class.java @@ -4768,8 +4768,8 @@ public Class[] getPermittedSubclasses() { if (subClasses.length > 0) { if (Arrays.stream(subClasses).anyMatch(c -> !isDirectSubType(c))) { subClasses = Arrays.stream(subClasses) - .filter(this::isDirectSubType) - .toArray(s -> new Class[s]); + .filter(this::isDirectSubType) + .toArray(s -> new Class[s]); } } if (subClasses.length > 0) { diff --git a/src/java.base/share/classes/java/lang/reflect/Field.java b/src/java.base/share/classes/java/lang/reflect/Field.java index c1f9c7d10ce25..284af63e81744 100644 --- a/src/java.base/share/classes/java/lang/reflect/Field.java +++ b/src/java.base/share/classes/java/lang/reflect/Field.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2024, 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 diff --git a/src/java.base/share/classes/java/util/ImmutableCollections.java b/src/java.base/share/classes/java/util/ImmutableCollections.java index 8fe7b761ef28b..726c7bb923b25 100644 --- a/src/java.base/share/classes/java/util/ImmutableCollections.java +++ b/src/java.base/share/classes/java/util/ImmutableCollections.java @@ -36,7 +36,6 @@ import java.util.function.Function; import java.util.function.Predicate; import java.util.function.UnaryOperator; - import jdk.internal.access.JavaUtilCollectionAccess; import jdk.internal.access.SharedSecrets; import jdk.internal.misc.CDS; @@ -1361,7 +1360,6 @@ private Object writeReplace() { return new CollSer(CollSer.IMM_MAP, array); } } - } // ---------- Serialization Proxy ---------- diff --git a/test/jdk/jdk/internal/lang/stable/StableTestUtil.java b/test/jdk/jdk/internal/lang/stable/StableTestUtil.java index 8d03b1aa2f5e7..da8cdfcac531f 100644 --- a/test/jdk/jdk/internal/lang/stable/StableTestUtil.java +++ b/test/jdk/jdk/internal/lang/stable/StableTestUtil.java @@ -22,12 +22,12 @@ */ import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Function; -import java.util.function.IntFunction; import java.util.function.Supplier; final class StableTestUtil { + private StableTestUtil() {} + public static final class CountingSupplier extends AbstractCounting> implements Supplier { @@ -44,37 +44,6 @@ public T get() { } - public static final class CountingIntFunction - extends AbstractCounting> - implements IntFunction { - - public CountingIntFunction(IntFunction delegate) { - super(delegate); - } - - @Override - public T apply(int value) { - incrementCounter(); - return delegate.apply(value); - } - - } - - public static final class CountingFunction - extends AbstractCounting> - implements Function { - - public CountingFunction(Function delegate) { - super(delegate); - } - - @Override - public R apply(T t) { - incrementCounter(); - return delegate.apply(t); - } - } - abstract static class AbstractCounting { private final AtomicInteger cnt = new AtomicInteger(); diff --git a/test/jdk/jdk/internal/lang/stable/StableValueTest.java b/test/jdk/jdk/internal/lang/stable/StableValueTest.java index 05813a35bc8c8..8661f42e53649 100644 --- a/test/jdk/jdk/internal/lang/stable/StableValueTest.java +++ b/test/jdk/jdk/internal/lang/stable/StableValueTest.java @@ -83,8 +83,14 @@ void setNonNull() { @Test void computeIfUnset() { StableValue stable = StableValue.newInstance(); - assertEquals(42, stable.computeIfUnset(() -> 42)); - assertEquals(42, stable.computeIfUnset(() -> 13)); + StableTestUtil.CountingSupplier cntSupplier = new StableTestUtil.CountingSupplier<>(() -> 42); + StableTestUtil.CountingSupplier cntSupplier2 = new StableTestUtil.CountingSupplier<>(() -> 13); + assertEquals(42, stable.computeIfUnset(cntSupplier)); + assertEquals(1, cntSupplier.cnt()); + assertEquals(42, stable.computeIfUnset(cntSupplier)); + assertEquals(1, cntSupplier.cnt()); + assertEquals(42, stable.computeIfUnset(cntSupplier2)); + assertEquals(0, cntSupplier2.cnt()); assertEquals("StableValue[42]", stable.toString()); assertEquals(42, stable.orElse(null)); assertFalse(stable.trySet(null)); From dae3e7686cf6f82516156c666e3e634da13764e9 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Fri, 7 Jun 2024 15:58:44 +0200 Subject: [PATCH 007/327] Add StableValues --- .../jdk/internal/lang/StableValue.java | 23 +-- .../jdk/internal/lang/StableValues.java | 140 ++++++++++++++++++ .../lang/stable/StableValuesTest.java | 89 +++++++++++ 3 files changed, 242 insertions(+), 10 deletions(-) create mode 100644 src/java.base/share/classes/jdk/internal/lang/StableValues.java create mode 100644 test/jdk/jdk/internal/lang/stable/StableValuesTest.java diff --git a/src/java.base/share/classes/jdk/internal/lang/StableValue.java b/src/java.base/share/classes/jdk/internal/lang/StableValue.java index 0e91f1d3295c6..91ccfb544acdb 100644 --- a/src/java.base/share/classes/jdk/internal/lang/StableValue.java +++ b/src/java.base/share/classes/jdk/internal/lang/StableValue.java @@ -41,19 +41,25 @@ * To create a new fresh (unset) StableValue, use the {@linkplain StableValue#newInstance()} * factory. *

+ * A StableValue can be created and computed by a background thread like this: + * {@snippet lang = java: + * StableValue stableValue = StableValues.ofBackground( + * Thread.ofVirtual().factory(), Value::new); + *} + * A new background thread will be created from a factory (e.g. `Thread.ofVirtual.factory`) + * and said thread will compute the returned StableValue's value using a supplier + * (e.g. `Value::new`). + *

* A List of stable values with a given {@code size} can be created the following way: * {@snippet lang = java : - * List> list = Stream.generate(StableValue::newInstance) - * .limit(size) - * .toList(); + * List> list = StableValues.ofList(size); * } * The list can be used to model stable arrays of one dimensions. If two or more * dimensional arrays are to be modeled, List of List of ... of StableValue can be used. *

* A Map of stable values with a given set of {@code keys} can be created like this: * {@snippet lang = java : - * Map> map = keys.stream() - * .collect(Collectors.toMap(Function.identity(), _ -> StableValue.newInstance())); + * Map> map = StableValues.ofMap(keys); * } * A memoized Supplier, where the given {@code original} supplier is guaranteed to be * successfully invoked at most once even in a multi-threaded environment, can be @@ -71,9 +77,7 @@ * {@snippet lang = java : * static IntFunction memoizedIntFunction(int size, * IntFunction original) { - * List> backing = Stream.generate(StableValue::newInstance) - * .limit(size) - * .toList(); + * List> backing = StableValues.ofList(size); * return i -> backing.get(i) * .computeIfUnset(() -> original.apply(i)); * } @@ -84,8 +88,7 @@ * {@snippet lang = java : * static Function memoizedFunction(Set inputs, * Function original) { - * Map> backing = inputs.stream() - * .collect(Collectors.toMap(Function.identity(), _ -> StableValue.newInstance())); + * Map> backing = StableValues.ofMap(keys); * return t -> { * if (!backing.containsKey(t)) { * throw new IllegalArgumentException("Input not allowed: "+t); diff --git a/src/java.base/share/classes/jdk/internal/lang/StableValues.java b/src/java.base/share/classes/jdk/internal/lang/StableValues.java new file mode 100644 index 0000000000000..d9f9ae69c2145 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/lang/StableValues.java @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2024, 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; + +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ThreadFactory; +import java.util.function.Supplier; + +/** + * A number of static methods returning constructs with StableValues. + * + *

The methods of this class all throw {@code NullPointerException} + * if provided with {@code null} arguments. + *

+ * The constructs returned are eligible for similar JVM optimizations as the + * {@linkplain StableValue} itself. + * + * @since 24 + */ +public final class StableValues { + + // Suppresses default constructor, ensuring non-instantiability. + private StableValues() {} + + /** + * {@return a fresh unset StableValue who's value is computed by a new Thread created + * via the provided {@code factory} where the new Thread will invoke the provided + * {@code supplier}}. + *

+ * If an Exception is thrown by the supplier, it be caught by the new Thread's + * {@linkplain Thread#getUncaughtExceptionHandler() uncaught exception handler} and + * no value will be set in the returned StableValue. + *

+ * The method is equivalent to the following for a given non-null {@code factory} and + * {@code supplier}: + * {@snippet lang = java : + * StableValue stable = StableValue.newInstance(); + * Thread thread = factory.newThread(() -> stable.computeIfUnset(supplier)); + * thread.start(); + * } + * + * @param factory to create new background threads from + * @param supplier that can provide a value for the returned StableValue + * @param type for the returned StableValue + */ + public static StableValue ofBackground(ThreadFactory factory, + Supplier supplier) { + Objects.requireNonNull(factory); + Objects.requireNonNull(supplier); + final StableValue stable = StableValue.newInstance(); + final Thread thread = factory.newThread(new Runnable() { + @Override public void run() { stable.computeIfUnset(supplier); } + }); + thread.start(); + return stable; + } + + /** + * {@return a shallowly immutable, stable List of distinct fresh stable values} + *

+ * The method is equivalent to the following for a given non-negative {@code size}: + * {@snippet lang = java : + * List> list = Stream.generate(StableValue::newInstance) + * .limit(size) + * .toList(); + * } + * @param size the size of the returned list + * @param the {@code StableValue}s' element type + */ + public static List> ofList(int size) { + if (size < 0) { + throw new IllegalArgumentException(); + } + if (size == 0) { + return List.of(); + } + @SuppressWarnings("unchecked") + final StableValue[] stableValues = (StableValue[]) new StableValue[size]; + for (int i = 0; i < size; i++) { + stableValues[i] = StableValue.newInstance(); + } + return List.of(stableValues); + } + + /** + * {@return a shallowly immutable, stable Map with the provided {@code keys} + * and associated distinct fresh stable values} + *

+ * The method is equivalent to the following for a given non-null {@code keys}: + * {@snippet lang = java : + * Map> map = keys.stream() + * .collect(Collectors.toMap( + * Function.identity(), + * _ -> StableValue.newInstance())); + * } + * @param keys the keys in the {@code Map} + * @param the {@code Map}'s key type + * @param the StableValue's type for the {@code Map}'s value type + */ + public static Map> ofMap(Set keys) { + Objects.requireNonNull(keys); + if (keys.isEmpty()) { + return Map.of(); + } + @SuppressWarnings("unchecked") + Map.Entry>[] entries = (Map.Entry>[]) new Map.Entry[keys.size()]; + int i = 0; + for (K key : keys) { + entries[i++] = Map.entry(key, StableValue.newInstance()); + } + return Map.ofEntries(entries); + } + +} diff --git a/test/jdk/jdk/internal/lang/stable/StableValuesTest.java b/test/jdk/jdk/internal/lang/stable/StableValuesTest.java new file mode 100644 index 0000000000000..c8d4aa652ed86 --- /dev/null +++ b/test/jdk/jdk/internal/lang/stable/StableValuesTest.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2024, 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 StableValues methods + * @modules java.base/jdk.internal.lang + * @compile --enable-preview -source ${jdk.version} StableValuesTest.java + * @run junit/othervm --enable-preview StableValuesTest + */ + +import jdk.internal.lang.StableValue; +import jdk.internal.lang.StableValues; +import org.junit.jupiter.api.Test; + +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.*; + +final class StableValuesTest { + + @Test + void ofBackground() { + + final AtomicInteger cnt = new AtomicInteger(0); + ThreadFactory factory = new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + return new Thread(() -> { + r.run(); + cnt.incrementAndGet(); + }); + } + }; + StableValue stable = StableValues.ofBackground(factory, () -> 42); + while (cnt.get() < 1) { + Thread.onSpinWait(); + } + assertEquals(42, stable.orElseThrow()); + } + + @Test + void ofList() { + List> list = StableValues.ofList(13); + assertEquals(13, list.size()); + // Check, every StableValue is distinct + Map, Boolean> idMap = new IdentityHashMap<>(); + list.forEach(e -> idMap.put(e, true)); + assertEquals(13, idMap.size()); + } + + @Test + void ofMap() { + Map> map = StableValues.ofMap(Set.of(1, 2, 3)); + assertEquals(3, map.size()); + // Check, every StableValue is distinct + Map, Boolean> idMap = new IdentityHashMap<>(); + map.forEach((k, v) -> idMap.put(v, true)); + assertEquals(3, idMap.size()); + } + +} \ No newline at end of file From 888c555f0265040dc16d857d4fff44337009b144 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 10 Jun 2024 11:39:12 +0200 Subject: [PATCH 008/327] Add benchmark --- .../lang/stable/StableValuesTest.java | 3 - .../lang/stable/StableValueBenchmark.java | 159 ++++++++++++++++++ 2 files changed, 159 insertions(+), 3 deletions(-) create mode 100644 test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java diff --git a/test/jdk/jdk/internal/lang/stable/StableValuesTest.java b/test/jdk/jdk/internal/lang/stable/StableValuesTest.java index c8d4aa652ed86..8098101320a04 100644 --- a/test/jdk/jdk/internal/lang/stable/StableValuesTest.java +++ b/test/jdk/jdk/internal/lang/stable/StableValuesTest.java @@ -32,15 +32,12 @@ import jdk.internal.lang.StableValues; import org.junit.jupiter.api.Test; -import java.util.HashSet; import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Function; -import java.util.stream.Collectors; import static org.junit.jupiter.api.Assertions.*; diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java new file mode 100644 index 0000000000000..4de29ffb270b0 --- /dev/null +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2024, 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.StableValue; +import org.openjdk.jmh.annotations.*; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; + +/** + * 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, jvmArgsAppend = {"--add-exports=java.base/jdk.internal.lang=ALL-UNNAMED", "--enable-preview", "--XX:-UseLSE"}) +// -XX:PerMethodTrapLimit=0 means no uncommon traps will be generated +@Fork(value = 2, jvmArgsAppend = { + "--add-exports=java.base/jdk.internal.lang=ALL-UNNAMED", + "--add-exports=jdk.unsupported/sun.misc=ALL-UNNAMED", + "--enable-preview", + "-XX:PerMethodTrapLimit=0"}) +@Threads(Threads.MAX) // Benchmark under contention +@OperationsPerInvocation(2) +public class StableValueBenchmark { + + private static final int VALUE = 42; + private static final int VALUE2 = 23; + + private final StableValue stable = init(StableValue.newInstance(), VALUE); + private final StableValue stable2 = init(StableValue.newInstance(), VALUE2); + private final StableValue stableNull = StableValue.newInstance(); + private final StableValue stableNull2 = StableValue.newInstance(); + private final Supplier dcl = new Dcl<>(() -> VALUE); + private final Supplier dcl2 = new Dcl<>(() -> VALUE2); + private final AtomicReference atomic = new AtomicReference<>(VALUE); + private final AtomicReference atomic2 = new AtomicReference<>(VALUE2); + private final Supplier supplier = () -> VALUE; + private final Supplier supplier2 = () -> VALUE2; + + private static final StableValue STABLE = init(StableValue.newInstance(), VALUE); + private static final StableValue STABLE2 = init(StableValue.newInstance(), VALUE2); + private static final StableValue DCL = init(StableValue.newInstance(), VALUE); + private static final StableValue DCL2 = init(StableValue.newInstance(), VALUE2); + + @Setup + public void setup() { + stableNull.trySet(null); + stableNull2.trySet(VALUE2); + // Create pollution + for (int i = 0; i < 500_000; i++) { + final int v = i; + Dcl dclX = new Dcl<>(() -> v); + dclX.get(); + StableValue stableX = StableValue.newInstance(); + stableX.trySet(i); + stableX.orElseThrow(); + } + } + + @Benchmark + public int atomic() { + return atomic.get() + atomic2.get(); + } + + @Benchmark + public int dcl() { + return dcl.get() + dcl2.get(); + } + + @Benchmark + public int stable() { + return stable.orElseThrow() + stable2.orElseThrow(); + } + + @Benchmark + public int stableNull() { + return (stableNull.orElseThrow() == null ? VALUE : VALUE2) + (stableNull2.orElseThrow() == null ? VALUE : VALUE2); + } + + // Reference case + @Benchmark + public int refSupplier() { + return supplier.get() + supplier2.get(); + } + + @Benchmark + public int staticStable() { + return STABLE.orElseThrow() + STABLE2.orElseThrow(); + } + + @Benchmark + public int staticDcl() { + return DCL.orElseThrow() + DCL2.orElseThrow(); + } + + private static StableValue init(StableValue m, Integer value) { + m.trySet(value); + return m; + } + + // Handles null values + private static class Dcl implements Supplier { + + private final Supplier supplier; + + private volatile V value; + private boolean bound; + + public Dcl(Supplier supplier) { + this.supplier = supplier; + } + + @Override + public V get() { + V v = value; + if (v == null) { + if (!bound) { + synchronized (this) { + v = value; + if (v == null) { + if (!bound) { + value = v = supplier.get(); + bound = true; + } + } + } + } + } + return v; + } + } + +} From e2ff37846646c7662c0ebad015aacdeb11586258 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 10 Jun 2024 12:40:32 +0200 Subject: [PATCH 009/327] Fix a bug in Field.java --- src/java.base/share/classes/java/lang/reflect/Field.java | 1 + .../openjdk/bench/java/lang/stable/StableValueBenchmark.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/java.base/share/classes/java/lang/reflect/Field.java b/src/java.base/share/classes/java/lang/reflect/Field.java index 284af63e81744..d2f95149f4e62 100644 --- a/src/java.base/share/classes/java/lang/reflect/Field.java +++ b/src/java.base/share/classes/java/lang/reflect/Field.java @@ -184,6 +184,7 @@ public void setAccessible(boolean flag) { if (flag) { checkCanSetAccessible(Reflection.getCallerClass()); } + setAccessible0(flag); } @Override diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java index 4de29ffb270b0..fce510364361d 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java @@ -42,8 +42,8 @@ // -XX:PerMethodTrapLimit=0 means no uncommon traps will be generated @Fork(value = 2, jvmArgsAppend = { "--add-exports=java.base/jdk.internal.lang=ALL-UNNAMED", - "--add-exports=jdk.unsupported/sun.misc=ALL-UNNAMED", "--enable-preview", + // Prevent the use of uncommon traps "-XX:PerMethodTrapLimit=0"}) @Threads(Threads.MAX) // Benchmark under contention @OperationsPerInvocation(2) From 2bf1d9de39f88eefa3addc5cc5ebb81132d5fc7c Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 10 Jun 2024 13:36:57 +0200 Subject: [PATCH 010/327] Improve docs --- .../jdk/internal/lang/StableValue.java | 68 ++++++++++--------- .../jdk/internal/lang/StableValues.java | 33 ++++----- 2 files changed, 50 insertions(+), 51 deletions(-) diff --git a/src/java.base/share/classes/jdk/internal/lang/StableValue.java b/src/java.base/share/classes/jdk/internal/lang/StableValue.java index 91ccfb544acdb..eb8a39892d429 100644 --- a/src/java.base/share/classes/jdk/internal/lang/StableValue.java +++ b/src/java.base/share/classes/jdk/internal/lang/StableValue.java @@ -37,17 +37,20 @@ * A stable value is said to be monotonic because the state of a stable value can only go * from unset to set and consequently, a value can only be set * at most once. -

+ *

* To create a new fresh (unset) StableValue, use the {@linkplain StableValue#newInstance()} * factory. *

+ * The utility class {@linkplain StableValues} contains a number of convenience methods + * for creating constructs involving StableValues: + *

* A StableValue can be created and computed by a background thread like this: * {@snippet lang = java: * StableValue stableValue = StableValues.ofBackground( * Thread.ofVirtual().factory(), Value::new); *} * A new background thread will be created from a factory (e.g. `Thread.ofVirtual.factory`) - * and said thread will compute the returned StableValue's value using a supplier + * and said thread will compute the returned StableValue's holder value using a supplier * (e.g. `Value::new`). *

* A List of stable values with a given {@code size} can be created the following way: @@ -55,7 +58,7 @@ * List> list = StableValues.ofList(size); * } * The list can be used to model stable arrays of one dimensions. If two or more - * dimensional arrays are to be modeled, List of List of ... of StableValue can be used. + * dimensional arrays are to be modeled, a List of List of ... of StableValue can be used. *

* A Map of stable values with a given set of {@code keys} can be created like this: * {@snippet lang = java : @@ -100,16 +103,16 @@ * } *

* The constructs above are eligible for similar JVM optimizations as the StableValue - * itself. + * class itself. *

- * All methods that can set the stable value's value are guarded such that competing + * All methods that can set the stable value's holder value are guarded such that competing * set operations (by other threads) will block if another set operation is * already in progress. *

- * Except for a StableValue's value itself, all method parameters must be non-null - * or a {@link NullPointerException} will be thrown. + * Except for a StableValue's holder value itself, all method parameters must be + * non-null or a {@link NullPointerException} will be thrown. * - * @param type of the wrapped value + * @param type of the holder value * * @since 24 */ @@ -119,7 +122,7 @@ public sealed interface StableValue // Principal methods /** - * {@return {@code true} if the stable value was set to the provided {@code value}, + * {@return {@code true} if the holder value was set to the provided {@code value}, * otherwise returns {@code false}} * * @param value to set (nullable) @@ -127,39 +130,39 @@ public sealed interface StableValue boolean trySet(T value); /** - * {@return the set value (nullable) if set, otherwise return the {@code other} value} - * @param other to return if the stable value is not set + * {@return the set holder value (nullable) if set, otherwise return the + * {@code other} value} + * @param other to return if the stable holder value is not set */ T orElse(T other); /** - * {@return the set value if set, otherwise throws - * {@code NoSuchElementException}} + * {@return the set holder value if set, otherwise throws {@code NoSuchElementException}} * * @throws NoSuchElementException if no value is set */ T orElseThrow(); /** - * {@return {@code true} if a value is set, {@code false} otherwise} + * {@return {@code true} if a holder value is set, {@code false} otherwise} */ boolean isSet(); /** - * If the stable value is unset, attempts to compute its value using the given - * supplier function and enters it into this stable value. + * If the holder value is unset, attempts to compute the holder value using the given + * {@code supplier} and enters it into the holder value. * - *

If the supplier function itself throws an (unchecked) exception, the exception - * is rethrown, and no value is set. The most common usage is to construct a new - * object serving as an initial value or memoized result, as in: + *

If the {@code supplier} itself throws an (unchecked) exception, the exception + * is rethrown, and no holder value is set. The most common usage is to construct a + * new object serving as an initial value or memoized result, as in: * *

 {@code
      * T t = stable.computeIfUnset(T::new);
      * }
* * @implSpec - * The default implementation is equivalent to the following steps for this - * {@code stable}: + * The implementation of this method is equivalent to the following steps for this + * {@code stable} and a given non-null {@code supplier}: * *
 {@code
      * if (stable.isSet()) {
@@ -169,27 +172,28 @@ public sealed interface StableValue
      * stable.trySet(newValue);
      * return newValue;
      * }
- * Except, the method is atomic and thread-safe. + * Except, the method is atomic and thread-safe with respect to this and all other + * methods that can set the StableValue's holder value. * - * @param supplier the mapping supplier to compute a value - * @return the current (existing or computed) value associated with - * the stable value + * @param supplier the supplier to be used to compute a holder value + * @return the current (existing or computed) holder value associated with + * this stable value */ T computeIfUnset(Supplier supplier); // Convenience methods /** - * Sets the stable value to the provided {@code value}, or, if already set to a - * non-null value, throws {@linkplain IllegalStateException}} + * Sets the holder value to the provided {@code value}, or, if already set, + * throws {@linkplain IllegalStateException}} * * @param value to set (nullable) - * @throws IllegalArgumentException if a non-null value is already set + * @throws IllegalArgumentException if a holder value is already set */ default void setOrThrow(T value) { if (!trySet(value)) { - throw new IllegalStateException("Cannot set value to " + value + - " because a value is alredy set: " + this); + throw new IllegalStateException("Cannot set the holder value to " + value + + " because a holder value is alredy set: " + this); } } @@ -197,9 +201,9 @@ default void setOrThrow(T value) { // Factory /** - * {@return a fresh stable value with an unset value} + * {@return a fresh stable value with an unset holder value} * - * @param the value type to set + * @param type of the holder value */ static StableValue newInstance() { return StableValueImpl.newInstance(); diff --git a/src/java.base/share/classes/jdk/internal/lang/StableValues.java b/src/java.base/share/classes/jdk/internal/lang/StableValues.java index d9f9ae69c2145..c5caf2ba8d1aa 100644 --- a/src/java.base/share/classes/jdk/internal/lang/StableValues.java +++ b/src/java.base/share/classes/jdk/internal/lang/StableValues.java @@ -33,7 +33,7 @@ import java.util.function.Supplier; /** - * A number of static methods returning constructs with StableValues. + * This class consists of static methods returning constructs involving StableValue. * *

The methods of this class all throw {@code NullPointerException} * if provided with {@code null} arguments. @@ -41,6 +41,7 @@ * The constructs returned are eligible for similar JVM optimizations as the * {@linkplain StableValue} itself. * + * @see StableValue * @since 24 */ public final class StableValues { @@ -49,23 +50,23 @@ public final class StableValues { private StableValues() {} /** - * {@return a fresh unset StableValue who's value is computed by a new Thread created - * via the provided {@code factory} where the new Thread will invoke the provided - * {@code supplier}}. + * {@return a fresh unset StableValue who's value is computed in the background by + * a new Thread created via the provided {@code factory} where the new Thread will + * invoke the provided {@code supplier}}. *

- * If an Exception is thrown by the supplier, it be caught by the new Thread's - * {@linkplain Thread#getUncaughtExceptionHandler() uncaught exception handler} and - * no value will be set in the returned StableValue. + * If an Exception is thrown by the provided {@code supplier}, it be caught by the new + * Thread's {@linkplain Thread#getUncaughtExceptionHandler() uncaught exception handler} + * and no value will be set in the returned StableValue. *

* The method is equivalent to the following for a given non-null {@code factory} and - * {@code supplier}: + * non-null {@code supplier}: * {@snippet lang = java : * StableValue stable = StableValue.newInstance(); * Thread thread = factory.newThread(() -> stable.computeIfUnset(supplier)); * thread.start(); * } * - * @param factory to create new background threads from + * @param factory used to create new background threads * @param supplier that can provide a value for the returned StableValue * @param type for the returned StableValue */ @@ -97,9 +98,6 @@ public static List> ofList(int size) { if (size < 0) { throw new IllegalArgumentException(); } - if (size == 0) { - return List.of(); - } @SuppressWarnings("unchecked") final StableValue[] stableValues = (StableValue[]) new StableValue[size]; for (int i = 0; i < size; i++) { @@ -112,22 +110,19 @@ public static List> ofList(int size) { * {@return a shallowly immutable, stable Map with the provided {@code keys} * and associated distinct fresh stable values} *

- * The method is equivalent to the following for a given non-null {@code keys}: + * The method is equivalent to the following for a given non-null set of {@code keys}: * {@snippet lang = java : * Map> map = keys.stream() * .collect(Collectors.toMap( * Function.identity(), * _ -> StableValue.newInstance())); * } - * @param keys the keys in the {@code Map} - * @param the {@code Map}'s key type - * @param the StableValue's type for the {@code Map}'s value type + * @param keys the keys in the {@code Map} + * @param the {@code Map}'s key type + * @param the StableValue's type for the {@code Map}'s value type */ public static Map> ofMap(Set keys) { Objects.requireNonNull(keys); - if (keys.isEmpty()) { - return Map.of(); - } @SuppressWarnings("unchecked") Map.Entry>[] entries = (Map.Entry>[]) new Map.Entry[keys.size()]; int i = 0; From 95446e222aca92f3c5f6bf02b6f711084f2a58ae Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 10 Jun 2024 13:45:34 +0200 Subject: [PATCH 011/327] Add @ForceInline annotations --- .../share/classes/jdk/internal/lang/stable/StableValueImpl.java | 2 ++ 1 file changed, 2 insertions(+) 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 79ba781f3956b..bf12bfc775411 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 @@ -82,6 +82,7 @@ public T orElse(T other) { return other; } + @ForceInline @Override public boolean isSet() { return value != null; @@ -132,6 +133,7 @@ private static T wrap(T t) { } // Unwraps null sentinel values into null + @ForceInline private static T unwrap(T t) { return t == nullSentinel() ? null : t; } From 885fac660795a346449cc9c5c16c62abb09d2b6d Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 10 Jun 2024 14:08:44 +0200 Subject: [PATCH 012/327] Clean up benchmark --- .../openjdk/bench/java/lang/stable/StableValueBenchmark.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java index fce510364361d..341def510e5aa 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java @@ -38,8 +38,6 @@ @State(Scope.Benchmark) // Share the same state instance (for contention) @Warmup(iterations = 5, time = 1) @Measurement(iterations = 5, time = 2) -//@Fork(value = 2, jvmArgsAppend = {"--add-exports=java.base/jdk.internal.lang=ALL-UNNAMED", "--enable-preview", "--XX:-UseLSE"}) -// -XX:PerMethodTrapLimit=0 means no uncommon traps will be generated @Fork(value = 2, jvmArgsAppend = { "--add-exports=java.base/jdk.internal.lang=ALL-UNNAMED", "--enable-preview", From 37e782a6d702351f00c6477a2384073531f89f4e Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 10 Jun 2024 16:28:15 +0200 Subject: [PATCH 013/327] Add memoizedSupplier --- .../jdk/internal/lang/StableValue.java | 14 ++-- .../jdk/internal/lang/StableValues.java | 76 ++++++++++++------- .../lang/stable/StableValuesTest.java | 18 ++++- 3 files changed, 71 insertions(+), 37 deletions(-) diff --git a/src/java.base/share/classes/jdk/internal/lang/StableValue.java b/src/java.base/share/classes/jdk/internal/lang/StableValue.java index eb8a39892d429..0830bc9ed219f 100644 --- a/src/java.base/share/classes/jdk/internal/lang/StableValue.java +++ b/src/java.base/share/classes/jdk/internal/lang/StableValue.java @@ -64,16 +64,18 @@ * {@snippet lang = java : * Map> map = StableValues.ofMap(keys); * } - * A memoized Supplier, where the given {@code original} supplier is guaranteed to be + * A memoized Supplier, where a given {@code original} supplier is guaranteed to be * successfully invoked at most once even in a multi-threaded environment, can be * created like this: * {@snippet lang = java : - * static Supplier memoizedSupplier(Supplier original) { - * Objects.requireNonNull(original); - * final StableValue stable = StableValue.newInstance(); - * return () -> stable.computeIfUnset(original); - * } + * Supplier memoized = StableValues.memoizedSupplier(original, null); + * } + * The memoized supplier can also be lazily computed using a fresh background thread if a + * thread factory is provided as a second parameter as shown here: + * {@snippet lang = java : + * Supplier memoized = StableValues.memoizedSupplier(original, Thread.ofVirtual().factory()); * } + *

* A memoized IntFunction, for the allowed given {@code size} values and where the * given {@code original} IntFunction is guaranteed to be successfully invoked at most * once per inout index even in a multi-threaded environment, can be created like this: diff --git a/src/java.base/share/classes/jdk/internal/lang/StableValues.java b/src/java.base/share/classes/jdk/internal/lang/StableValues.java index c5caf2ba8d1aa..fd23d40edf31c 100644 --- a/src/java.base/share/classes/jdk/internal/lang/StableValues.java +++ b/src/java.base/share/classes/jdk/internal/lang/StableValues.java @@ -36,7 +36,7 @@ * This class consists of static methods returning constructs involving StableValue. * *

The methods of this class all throw {@code NullPointerException} - * if provided with {@code null} arguments. + * if provided with {@code null} arguments unless otherwise specified. *

* The constructs returned are eligible for similar JVM optimizations as the * {@linkplain StableValue} itself. @@ -50,37 +50,57 @@ public final class StableValues { private StableValues() {} /** - * {@return a fresh unset StableValue who's value is computed in the background by - * a new Thread created via the provided {@code factory} where the new Thread will - * invoke the provided {@code supplier}}. + * {@return a new thread-safe, stable, lazily computed {@linkplain Supplier supplier} + * that records the value of the provided {@code original} supplier upon being first + * accessed via {@linkplain Supplier#get()}, or via a background thread created from + * the provided {@code factory} (if non-null)} *

- * If an Exception is thrown by the provided {@code supplier}, it be caught by the new - * Thread's {@linkplain Thread#getUncaughtExceptionHandler() uncaught exception handler} - * and no value will be set in the returned StableValue. + * The provided {@code original} supplier is guaranteed to be successfully invoked + * at most once even in a multi-threaded environment. Competing threads invoking the + * {@linkplain Supplier#get()} method when a value is already under computation + * will block until a value is computed or an exception is thrown by the + * computing thread. *

- * The method is equivalent to the following for a given non-null {@code factory} and - * non-null {@code supplier}: - * {@snippet lang = java : - * StableValue stable = StableValue.newInstance(); - * Thread thread = factory.newThread(() -> stable.computeIfUnset(supplier)); - * thread.start(); - * } + * If the {@code original} Supplier invokes the returned Supplier recursively, + * a StackOverflowError will be thrown when the returned + * Supplier's {@linkplain Supplier#get()} method is invoked. + *

+ * If the provided {@code original} supplier throws an exception, it is relayed + * to the initial caller. If the memoized supplier is computed by a background thread, + * exceptions from the provided {@code original} supplier will be relayed to the + * background thread's {@linkplain Thread#getUncaughtExceptionHandler() uncaught + * exception handler}. * - * @param factory used to create new background threads - * @param supplier that can provide a value for the returned StableValue - * @param type for the returned StableValue + * @param original supplier used to compute a memoized value + * @param factory an optional factory that, if non-null, will be used to create + * a background thread that will compute the memoized value. If the + * factory is {@code null}, no background thread will be started. + * @param the type of results supplied by the returned supplier */ - public static StableValue ofBackground(ThreadFactory factory, - Supplier supplier) { - Objects.requireNonNull(factory); - Objects.requireNonNull(supplier); - final StableValue stable = StableValue.newInstance(); - final Thread thread = factory.newThread(new Runnable() { - @Override public void run() { stable.computeIfUnset(supplier); } - }); - thread.start(); - return stable; - } + + public static Supplier memoizedSupplier(Supplier original, + ThreadFactory factory) { + Objects.requireNonNull(original); + // `factory` is nullable + + // The memoized value is backed by a StableValue + final StableValue stable = StableValue.newInstance(); + // A record provides better debug capabilities than a lambda + record MemoizedSupplier(StableValue stable, + Supplier original) implements Supplier { + @Override public T get() { return stable.computeIfUnset(original); } + } + final Supplier memoized = new MemoizedSupplier<>(stable, original); + + if (factory != null) { + final Thread thread = factory.newThread(new Runnable() { + @Override public void run() { memoized.get(); } + }); + thread.start(); + } + + return memoized; + } /** * {@return a shallowly immutable, stable List of distinct fresh stable values} diff --git a/test/jdk/jdk/internal/lang/stable/StableValuesTest.java b/test/jdk/jdk/internal/lang/stable/StableValuesTest.java index 8098101320a04..3e263c9f63788 100644 --- a/test/jdk/jdk/internal/lang/stable/StableValuesTest.java +++ b/test/jdk/jdk/internal/lang/stable/StableValuesTest.java @@ -38,13 +38,25 @@ import java.util.Set; import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Supplier; import static org.junit.jupiter.api.Assertions.*; final class StableValuesTest { @Test - void ofBackground() { + void ofMemoized() { + StableTestUtil.CountingSupplier cs = new StableTestUtil.CountingSupplier<>(() -> 42); + Supplier memoizeded = StableValues.memoizedSupplier(cs, null); + assertEquals(42, memoizeded.get()); + assertEquals(1, cs.cnt()); + assertEquals(42, memoizeded.get()); + assertEquals(1, cs.cnt()); + assertEquals("MemoizedSupplier[stable=StableValue[42], original=" + cs + "]", memoizeded.toString()); + } + + @Test + void ofMemoizedBackground() { final AtomicInteger cnt = new AtomicInteger(0); ThreadFactory factory = new ThreadFactory() { @@ -56,11 +68,11 @@ public Thread newThread(Runnable r) { }); } }; - StableValue stable = StableValues.ofBackground(factory, () -> 42); + Supplier memoizeded = StableValues.memoizedSupplier(() -> 42, factory); while (cnt.get() < 1) { Thread.onSpinWait(); } - assertEquals(42, stable.orElseThrow()); + assertEquals(42, memoizeded.get()); } @Test From 83a9de23df650740a57eee06b76e00724e68f533 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 11 Jun 2024 10:29:43 +0200 Subject: [PATCH 014/327] Explicitly use acquire/release semantics --- .../internal/lang/stable/StableValueImpl.java | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) 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 bf12bfc775411..c60d37338640b 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 @@ -49,8 +49,9 @@ public final class StableValueImpl implements StableValue { // Unset: null // Set(non-null): The set value // Set(null): nullSentinel() + // This field is reflectively accessed via Unsafe using acquire/release semantics. @Stable - private volatile T value; + private T value; private StableValueImpl() {} @@ -58,14 +59,14 @@ private StableValueImpl() {} @Override public boolean trySet(T value) { synchronized (mutex) { - return UNSAFE.compareAndSetReference(this, VALUE_OFFSET, null, wrap(value)); + return UNSAFE.weakCompareAndSetReferenceRelease(this, VALUE_OFFSET, null, wrap(value)); } } @ForceInline @Override public T orElseThrow() { - final T t = value; + final T t = valueAcquire(); if (t != null) { return unwrap(t); } @@ -75,7 +76,7 @@ public T orElseThrow() { @ForceInline @Override public T orElse(T other) { - final T t = value; + final T t = valueAcquire(); if (t != null) { return unwrap(t); } @@ -85,13 +86,13 @@ public T orElse(T other) { @ForceInline @Override public boolean isSet() { - return value != null; + return valueAcquire() != null; } @ForceInline @Override public T computeIfUnset(Supplier supplier) { - final T t = value; + final T t = valueAcquire(); if (t != null) { return unwrap(t); } @@ -101,7 +102,7 @@ public T computeIfUnset(Supplier supplier) { @DontInline private T compute(Supplier supplier) { synchronized (mutex) { - T t = value; + T t = valueAcquire(); if (t != null) { return unwrap(t); } @@ -119,12 +120,18 @@ public int hashCode() { @Override public boolean equals(Object obj) { return obj instanceof StableValueImpl other && - Objects.equals(value, other.value); + Objects.equals(valueAcquire(), other.valueAcquire()); } @Override public String toString() { - return "StableValue" + render(value); + return "StableValue" + render(valueAcquire()); + } + + @SuppressWarnings("unchecked") + @ForceInline + private T valueAcquire() { + return (T) UNSAFE.getReferenceAcquire(this, VALUE_OFFSET); } // Wraps null values into a sentinel value From 0bdf19a1a068e211f054cb8353592b207f896971 Mon Sep 17 00:00:00 2001 From: Per-Ake Minborg Date: Tue, 11 Jun 2024 10:30:08 +0200 Subject: [PATCH 015/327] Update src/java.base/share/classes/jdk/internal/lang/StableValue.java Co-authored-by: Chen Liang --- src/java.base/share/classes/jdk/internal/lang/StableValue.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/java.base/share/classes/jdk/internal/lang/StableValue.java b/src/java.base/share/classes/jdk/internal/lang/StableValue.java index 0830bc9ed219f..b83b962322e0f 100644 --- a/src/java.base/share/classes/jdk/internal/lang/StableValue.java +++ b/src/java.base/share/classes/jdk/internal/lang/StableValue.java @@ -190,7 +190,7 @@ public sealed interface StableValue * throws {@linkplain IllegalStateException}} * * @param value to set (nullable) - * @throws IllegalArgumentException if a holder value is already set + * @throws IllegalStateException if a holder value is already set */ default void setOrThrow(T value) { if (!trySet(value)) { From 9e848a3f18f21b7c146a418bd13164e514c4d56d Mon Sep 17 00:00:00 2001 From: Per-Ake Minborg Date: Tue, 11 Jun 2024 10:30:33 +0200 Subject: [PATCH 016/327] Update src/java.base/share/classes/jdk/internal/lang/StableValues.java Co-authored-by: Chen Liang --- src/java.base/share/classes/jdk/internal/lang/StableValues.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/java.base/share/classes/jdk/internal/lang/StableValues.java b/src/java.base/share/classes/jdk/internal/lang/StableValues.java index fd23d40edf31c..c02d0328e97ff 100644 --- a/src/java.base/share/classes/jdk/internal/lang/StableValues.java +++ b/src/java.base/share/classes/jdk/internal/lang/StableValues.java @@ -144,7 +144,7 @@ public static List> ofList(int size) { public static Map> ofMap(Set keys) { Objects.requireNonNull(keys); @SuppressWarnings("unchecked") - Map.Entry>[] entries = (Map.Entry>[]) new Map.Entry[keys.size()]; + var entries = (Map.Entry>[]) new Map.Entry[keys.size()]; int i = 0; for (K key : keys) { entries[i++] = Map.entry(key, StableValue.newInstance()); From 4a7768ba9a7b765724ebb9f53321e012cbd18d15 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 11 Jun 2024 10:38:04 +0200 Subject: [PATCH 017/327] Clean up --- .../classes/jdk/internal/lang/StableValues.java | 4 ++-- .../jdk/internal/lang/stable/StableValueImpl.java | 12 +++++------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/java.base/share/classes/jdk/internal/lang/StableValues.java b/src/java.base/share/classes/jdk/internal/lang/StableValues.java index c02d0328e97ff..624190166275c 100644 --- a/src/java.base/share/classes/jdk/internal/lang/StableValues.java +++ b/src/java.base/share/classes/jdk/internal/lang/StableValues.java @@ -119,7 +119,7 @@ public static List> ofList(int size) { throw new IllegalArgumentException(); } @SuppressWarnings("unchecked") - final StableValue[] stableValues = (StableValue[]) new StableValue[size]; + final var stableValues = (StableValue[]) new StableValue[size]; for (int i = 0; i < size; i++) { stableValues[i] = StableValue.newInstance(); } @@ -144,7 +144,7 @@ public static List> ofList(int size) { public static Map> ofMap(Set keys) { Objects.requireNonNull(keys); @SuppressWarnings("unchecked") - var entries = (Map.Entry>[]) new Map.Entry[keys.size()]; + final var entries = (Map.Entry>[]) new Map.Entry[keys.size()]; int i = 0; for (K key : keys) { entries[i++] = Map.entry(key, StableValue.newInstance()); 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 c60d37338640b..4ce2e48f7ad45 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 @@ -37,19 +37,17 @@ public final class StableValueImpl implements StableValue { - static final Unsafe UNSAFE = Unsafe.getUnsafe(); - + private static final Unsafe UNSAFE = Unsafe.getUnsafe(); private static final Object NULL_SENTINEL = new Object(); - private static final long VALUE_OFFSET = UNSAFE.objectFieldOffset(StableValueImpl.class, "value"); private final Object mutex = new Object(); + // This field is reflectively accessed via Unsafe using acquire/release semantics. // Unset: null - // Set(non-null): The set value + // Set(non-null): The set value (!= nullSentinel()) // Set(null): nullSentinel() - // This field is reflectively accessed via Unsafe using acquire/release semantics. @Stable private T value; @@ -146,11 +144,11 @@ private static T unwrap(T t) { } @SuppressWarnings("unchecked") - static T nullSentinel() { + private static T nullSentinel() { return (T) NULL_SENTINEL; } - static String render(T t) { + private static String render(T t) { if (t != null) { return t == nullSentinel() ? "[null]" : "[" + t + "]"; } From 773f23f7b6751d784d2147bb55d1a089b09a5f01 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 11 Jun 2024 14:01:22 +0200 Subject: [PATCH 018/327] Add JEP and clean up --- test/jdk/jdk/internal/lang/stable/JEP.adoc | 474 ++++++++++++++++++ .../jdk/jdk/internal/lang/stable/JepTest.java | 171 +++++++ .../internal/lang/stable/StableTestUtil.java | 2 +- .../internal/lang/stable/StableValueTest.java | 2 +- .../lang/stable/StableValuesTest.java | 2 +- .../lang/stable/TrustedFieldTypeTest.java | 2 +- .../lang/stable/StableValueBenchmark.java | 14 +- 7 files changed, 661 insertions(+), 6 deletions(-) create mode 100644 test/jdk/jdk/internal/lang/stable/JEP.adoc create mode 100644 test/jdk/jdk/internal/lang/stable/JepTest.java diff --git a/test/jdk/jdk/internal/lang/stable/JEP.adoc b/test/jdk/jdk/internal/lang/stable/JEP.adoc new file mode 100644 index 0000000000000..7d0bfe960a337 --- /dev/null +++ b/test/jdk/jdk/internal/lang/stable/JEP.adoc @@ -0,0 +1,474 @@ +# Stable Values (Preview) + +## Summary + +Introduce a _Stable Values_ API, which provides immutable value holders where elements are initialized +_at most once_. Stable Values offer the performance and safety benefits of final fields, while offering greater flexibility as to the timing of initialization. This is a [preview API](https://openjdk.org/jeps/12). + +## Goals + +- Provide an easy and intuitive API to describe value holders that can change at most once. +- Decouple declaration from initialization without significant footprint or performance penalties. +- Reduce the amount of static initializer and/or field initialization code. +- Uphold integrity and consistency, even in a multi-threaded environment. + +## Non-goals + +- It is not a goal to provide additional language support for expressing lazy computation. +This might be the subject of a future JEP. +- It is not a goal to prevent or deprecate existing idioms for expressing lazy initialization. + +## Motivation + +Most Java developers have heard the advice "prefer immutability" (Effective +Java, Item 17). Immutability confers many advantages including: + +* an immutable object can only be in one state +* the invariants of an immutable object can be enforced by its constructor +* immutable objects can be freely shared across threads +* immutability enables all manner of runtime optimizations. + +Java's main tool for managing immutability is `final` fields (and more recently, `record` classes). +Unfortunately, `final` fields come with restrictions. Final instance fields must be set by the end of +the constructor, and `static final` fields during class initialization. Moreover, the order in which `final` field initializers are executed is determined by the [textual order](https://docs.oracle.com/javase/specs/jls/se7/html/jls-13.html#jls-12.4.1) +and is then made explicit in the resulting class file. As such, the initialization of a `final` +field is fixed in time; it cannot be arbitrarily moved forward. In other words, developers +cannot cause specific constants to be initialized after the class or object is initialized. +This means that developers are forced to choose between finality and all its +benefits, and flexibility over the timing of initialization. Developers have +devised several strategies to ameliorate this imbalance, but none are +ideal. + +For instance, monolithic class initializers can be broken up by leveraging the +laziness already built into class loading. Often referred to as the +[_class-holder idiom_](https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom), +this technique moves lazily initialized state into a helper class which is then +loaded on-demand, so its initialization is only performed when the data is +actually needed, rather than unconditionally initializing constants when a class +is first referenced: +``` +// ordinary static initialization +private static final Logger LOGGER = Logger.getLogger("com.foo.Bar"); +... +LOGGER.log(Level.DEBUG, ...); +``` +we can defer initialization until we actually need it: +``` +// Initialization-on-demand holder idiom +Logger logger() { + class Holder { + static final Logger LOGGER = Logger.getLogger("com.foo.Bar"); + } + return Holder.LOGGER; +} +... +LOGGER.log(Level.DEBUG, ...); +``` +The code above ensures that the `Logger` object is created only when actually +required. The (possibly expensive) initializer for the logger lives in the +nested `Holder` class, which will only be initialized when the `logger` method +accesses the `LOGGER` field. While this idiom works well, its reliance on the +class loading process comes with significant drawbacks. First, each constant +whose computation needs to be deferred generally requires its own holder +class, thus introducing a significant static footprint cost. Second, this idiom +is only really applicable if the field initialization is suitably isolated, not +relying on any other parts of the object state. + +It should be noted that even though eventually outputting a message is slow compared to +obtaining the `Logger` instance itself, the `LOGGER::log`method starts with checking if +the selected `Level` is enabled or not. This latter check is a relatively fast operation +and so, in the case of disabled loggers, the `Logger` instance retrieval performance is +important. For example, logger output for `Level.DEBUG` is almost always disabled in production +environments. + +Alternatively, the [_double-checked locking idiom_](https://en.wikipedia.org/wiki/Double-checked_locking), can also be used +for deferring the evaluation of field initializers. The idea is to optimistically +check if the field's value is non-null and if so, use that value directly; but +if the value observed is null, then the field must be initialized, which, to be +safe under multi-threaded access, requires acquiring a lock to ensure +correctness: +``` +// Double-checked locking idiom +class Foo { + private volatile Logger logger; + public Logger logger() { + Logger v = logger; + if (v == null) { + synchronized (this) { + v = logger; + if (v == null) { + logger = v = Logger.getLogger("com.foo.Bar"); + } + } + } + return v; + } +} +``` +The double-checked locking idiom is brittle and easy to get +subtly wrong (see _Java Concurrency in Practice_, 16.2.4.) For example, a common error +is forgetting to declare the field `volatile` resulting in the risk of observing incomplete objects. + +While the double-checked locking idiom can be used for both class and instance +variables, its usage requires that the field subject to initialization is marked +as non-final. This is not ideal for several reasons: + +* it would be possible for code to accidentally modify the field value, thus violating +the immutability assumption of the enclosing class. +* access to the field cannot be adequately optimized by just-in-time compilers, as they +cannot reliably assume that the field value will, in fact, never change. An example of +similar optimizations in existing Java implementations is when a `MethodHandle` is held +in a `static final` field, allowing the runtime to generate machine code that is competitive +with direct invocation of the corresponding method. + +Furthermore, the idiom shown above needs to be modified to properly handle `null` values, for example using a [sentinel](https://en.wikipedia.org/wiki/Sentinel_value) value. + +The situation is even worse when clients need to operate on a _collection_ of immutable values. + +An example of this is an array that holds HTML pages that correspond to an error code in the range [0, 7] +where each element is pulled in from the file system on-demand, once actually used: + +``` +class ErrorMessages { + + private static final int SIZE = 8; + + // 1. Declare an array of error pages to serve up + private static final String[] MESSAGES = new String[SIZE]; + + // 2. Define a function that is to be called the first + // time a particular message number is referenced + private static String readFromFile(int messageNumber) { + try { + return Files.readString(Path.of("message-" + messageNumber + ".html")); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + static synchronized String message(int messageNumber) { + // 3. Access the memoized array element under synchronization + // and compute-and-store if absent. + String page = MESSAGES[messageNumber]; + if (page == null) { + page = readFromFile(messageNumber); + MESSAGES[messageNumber] = page; + } + return page; + } + + } +``` +We can now retrieve an error page like so: +``` +String errorPage = ErrorMessages.errorPage(2); + +// +// +// +// Payment was denied: Insufficient funds. +// +``` + +Unfortunately, this approach provides a plethora of challenges. First, retrieving the values +from a static array is slow, as said values cannot be [constant-folded](https://en.wikipedia.org/wiki/Constant_folding). Even worse, access to the array is guarded by synchronization that is +not only slow but will block access to the array for all elements whenever one of the elements is +under computation. Furthermore, the class holder idiom (see above) is undoubtedly insufficient in +this case, as the number of required holder classes is *statically unbounded* - it +depends on the value of the parameter `SIZE` which may change in future variants of the code. + +What we are missing -- in all cases -- is a way to *promise* that a constant will be initialized +by the time it is used, with a value that is computed at most once. Such a mechanism would give +the Java runtime maximum opportunity to stage and optimize its computation, thus avoiding the penalties (static footprint, loss of runtime optimizations) that plague the workarounds shown above. Moreover, such a mechanism should gracefully scale to handle collections of constant values, while retaining efficient computer resource management. + +The attentive reader might have noticed the similarity between what is sought after here and the JDK +internal annotation`jdk.internal.vm.annotation.@Stable`. This annotation is used by *JDK code* to +mark scalar and array variables whose values or elements will change *at most once*. This annotation +is powerful and often crucial to achieving optimal performance, but it is also easy to misuse: +further updating a `@Stable` field after its initial update will result in undefined behavior, as the +JIT compiler might have *already* constant-folded the (now overwritten) field value. In other words, +what we are after is a *safe* and *efficient* wrapper around the `@Stable` mechanism - in the form of +a new Java SE API that might be enjoyed by _all_ client and 3rd-party Java code (and not the JDK +alone). + +## Description + +### Preview feature + +Stable Values is a [preview API](https://openjdk.org/jeps/12), disabled by default. +To use the Stable Value APIs, the JVM flag `--enable-preview` must be passed in, as follows: + +- Compile the program with `javac --release 24 --enable-preview Main.java` and run it with `java --enable-preview Main`; or, + +- When using the source code launcher, run the program with `java --source 24 --enable-preview Main.java`; or, + +- When using `jshell`, start it with `jshell --enable-preview`. + +### Outline + +The Stable Values API defines functions and an interface so that client code in libraries and applications can + +- Define and use stable (scalar) values: +- [`StableValue.newInstance()`](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/java/lang/StableValue.html) +- Define collections: +- [`StableValues.ofList(int size)`](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/java/lang/StableValues.html#ofList(int)), +- [`StableValues.ofMap(Set keys)`](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/java/lang/StableValues.html#ofMap(java.util.Set)) + +The Stable Values API resides in the [java.lang](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/java/lang/package-summary.html) package of the [java.base](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/module-summary.html) module. + +### Stable values + +A _stable value_ is a holder object that is set at most once whereby it +goes from "unset" to "set". It is expressed as an object of type `java.lang.StableValue`, +which, like `Future`, is a holder for some computation that may or may not have occurred yet. +Fresh (unset) `StableValue` instances are created via the factory method `StableValue::of`: + +``` +class Bar { + // 1. Declare a Stable field + private static final StableValue LOGGER = StableValue.newInstance()(); + + static Logger logger() { + + if (!LOGGER.isSet()) { + // 2. Set the stable value _after_ the field was declared + LOGGER.trySet(Logger.getLogger("com.foo.Bar")); + } + + // 3. Access the stable value with as-declared-final performance + return LOGGER.orElseThrow(); + } +} +``` +Setting a stable value is an atomic, thread-safe operation, i.e. `StableValue::setIfUnset`, +either results in successfully initializing the `StableValue` to a value, or returns +an already set value. This is true regardless of whether the stable value is accessed by a single +thread, or concurrently, by multiple threads. + +A stable value may be set to `null` which then will be considered its set value. +Null-averse applications can also use `StableValue>`. + +In many ways, this is similar to the holder-class idiom in the sense it offers the same +performance and constant-folding characteristics. It also incurs a lower static footprint +since no additional class is required. + +However, there is _an important distinction_; several threads may invoke the `Logger::getLogger` +method simultaneously if they call the `logger()` method at about the same time. Even though +`StableValue` will guarantee, that only one of these results will ever be exposed to the many +competing threads, there might be applications where it is a requirement, that a supplying method is +only called once. + +In such cases, it is possible to compute and set an unset value on-demand as shown in this example in which case `StableValue` will uphold the invoke-at-most-once invariant for the provided `Supplier`: + +``` +class Bar { + // 1. Declare a stable field + private static final StableValue LOGGER = StableValue.newInstance(); + + static Logger logger() { + // 2. Access the stable value with as-declared-final performance + // (single evaluation made before the first access) + return LOGGER.computeIfUnset( () -> Logger.getLogger("com.foo.Bar") ); + } +} +``` + +When retrieving values, `StableValue` instances holding reference values are faster +than reference values managed via double-checked-idiom constructs as stable values rely +on explicit memory barriers rather than performing volatile access on each retrieval +operation. In addition, stable values are eligible for constant folding optimizations. + +### Stable collections + +The Stable Values API provides constructs that allow the creation and handling of a +*`List` of stable elements*. Lists of lazily computed values are objects of type +`List>`. Consequently, each element in the list enjoys the same properties as a +`StableValue`. + +Like a `StableValue` object, a `List` of stable value elements is created via a factory method by providing the size of the desired `List`: + +``` +static List> StableValues.ofList(int size) { ... } +``` + +This allows for improving the handling of lists with stable values and enables a much better +implementation of the `ErrorMessages` class mentioned earlier. Here is an improved version +of the class which is now using the newly proposed API: + +``` +class ErrorMessages { + + private static final int SIZE = 8; + + // 1. Declare a stable list of default error pages to serve up + private static final List> MESSAGES = StableValues.ofList(SIZE); + + // 2. Define a function that is to be called the first + // time a particular message number is referenced + private static String readFromFile(int messageNumber) { + try { + return Files.readString(Path.of("message-" + messageNumber + ".html")); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + static String errorPage(int messageNumber) { + // 3. Access the stable list element with as-declared-final performance + // (evaluation made before the first access) + return MESSAGES.get(messageNumber) + .computeIfUnset(() -> readFromFile(messageNumber)); + } + +} +``` + +Just like before, we can perform retrieval of error pages like this: + +``` +String errorPage = ErrorMessages.errorPage(2); + +// +// +// +// Payment was denied: Insufficient funds. +// +``` + +Note how there's only one field of type `List>` to initialize even though every +computation is performed independently of the other element of the list when accessed (i.e. no +blocking will occur across threads computing distinct elements simultaneously). Also, the +`IntSupplier` provided at computation is only invoked at most once for each distinct index. The +Stable Values API allows modeling this cleanly, while still preserving good constant-folding +guarantees and integrity of updates in the case of multi-threaded access. + +It should be noted that even though a lazily computed list of stable elements might mutate its +internal state upon external access, it is _still shallowly immutable_ because _no first-level +change can ever be observed by an external observer_. This is similar to other immutable classes, +such as `String` (which internally caches its `hash` value), where they might rely on mutable +internal states that are carefully kept internally and that never +shine through to the outside world. + +Just as a `List` can be lazily computed, a `Map` of lazily computed stable values can also be defined +and used similarly. In the example below, we lazily compute a map's stable values for an enumerated +collection of pre-defined keys: + +``` +class MapDemo { + + // 1. Declare a stable map of loggers with two allowable keys: + // "com.foo.Bar" and "com.foo.Baz" + static final Map> LOGGERS = + StableValues.ofMap(Set.of("com.foo.Bar", "com.foo.Baz")); + + // 2. Access the memoized map with as-declared-final performance + // (evaluation made before the first access) + static Logger logger(String name) { + return LOGGERS.get(name) + .computeIfUnset(() -> Logger.getLogger(name)); + } +} +``` + +This concept allows declaring a large number of stable values which can be easily retrieved using +arbitrarily, but pre-specified, keys in a resource-efficient and performant way. For example, +high-performance, non-evicting caches may now be easily and reliably realized. + +It is worth remembering, that the stable collections all promise the function provided at computation +(used to lazily compute elements or values) is invoked at most once per index or key; even +though used from several threads. + +### Memoized functions + +So far, we have talked about the fundamental features of Stable Values & Collections as securely +wrapped `@Stable` value holders. However, it has become apparent, stable primitives are amenable +to composition with other constructs in order to create more high-level and powerful features. + +[Memoized functions](https://en.wikipedia.org/wiki/Memoization) are functions where the output for a +particular input value is computed only once and is remembered such that remembered outputs can be +reused for subsequent calls with recurring input values. Here is how we could make sure +`Logger.getLogger("com.foo.Bar")` in one of the first examples above is invoked at most once +(provided it executes successfully) in a multi-threaded environment: + +``` +class Memoized { + + // 1. Declare a map with stable values + private static final Map> MAP = + StableValues.ofMap(Set.of("com.foo.Bar", "com.foo.Baz")); + + // 2. Declare a memoized (cached) function backed by the stable map + private static final Function LOGGERS = + n -> MAP.get(n).computeIfUnset(() -> Logger.getLogger(n)); + + ... + + private static final String NAME = "com.foo.Baz"; + + // 3. Access the memoized value via the function with as-declared-final + // performance (evaluation made before the first access) + Logger logger = LOGGERS.apply(NAME); +} +``` + +In the example above, for each key, the function is invoked at most once per loading of the containing class +`MapDemo` (`MapDemo`, in turn, can be loaded at most once into any given `ClassLoader`) as it is backed by a +`Map` with lazily computed values which upholds the invoke-at-most-once-per-key invariant. + +It should be noted that the enumerated collection of keys given at creation time constitutes the only valid +input keys for the memoized function. + +Similarly to how a `Function` can be memoized using a backing lazily computed map, the same pattern +can be used for an `IntFunction` that will record its cached value in a backing _stable list_: + +``` +// 1. Declare a stable list of default error pages to serve up +private static final List> ERROR_PAGES = + StableValues.ofList(SIZE); + +// 2. Declare a memoized IntFunction backed by the stable list +private static final IntFunction ERROR_FUNCTION = + i -> ERROR_PAGES.get(i).computeIfUnset(() -> readFromFile(i)); + +// 3. Define a function that is to be called the first +// time a particular message number is referenced +private static String readFromFile(int messageNumber) { + try { + return Files.readString(Path.of("message-" + messageNumber + ".html")); + } catch (IOException e) { + throw new UncheckedIOException(e); + } +} + +// 4. Access the memoized list element with as-declared-final performance +// (evaluation made before the first access) +String msg = ERROR_FUNCTION.apply(2); + +// +// +// +// Payment was denied: Insufficient funds. +// +``` + +The same paradigm can be used for creating a memoized `Supplier` (backed by a single `StableValue` instance) or a memoized `Predicate`(backed by a lazily computed `Map>`). An astute reader will be able to write such constructs in a few lines. + +## Alternatives + +There are other classes in the JDK that support lazy computation including `Map`, `AtomicReference`, `ClassValue`, +and `ThreadLocal` all of which, unfortunately, support arbitrary mutation and thus, hinder the JVM from reasoning +about constantness thereby preventing constant folding and other optimizations. + +So, alternatives would be to keep using explicit double-checked locking, maps, holder classes, Atomic classes, +and third-party frameworks. Another alternative would be to add language support for immutable value holders. + +## Risks and assumptions + +Creating an API to provide thread-safe computed constant fields with an on-par performance with holder +classes efficiently is a non-trivial task. It is, however, assumed that the current JIT implementations +will likely suffice to reach the goals of this JEP. + +## Dependencies + +The work described here will likely enable subsequent work to provide pre-evaluated computed +constant fields at compile, condensation, and/or runtime. diff --git a/test/jdk/jdk/internal/lang/stable/JepTest.java b/test/jdk/jdk/internal/lang/stable/JepTest.java new file mode 100644 index 0000000000000..be52616d73086 --- /dev/null +++ b/test/jdk/jdk/internal/lang/stable/JepTest.java @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2024, 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 JepTest implementations + * @modules java.base/jdk.internal.lang + * @compile --enable-preview -source ${jdk.version} JepTest.java + * @run junit/othervm --enable-preview JepTest + */ + +import jdk.internal.lang.StableValue; +import jdk.internal.lang.StableValues; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.BitSet; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.LockSupport; +import java.util.function.BiPredicate; +import java.util.function.Function; +import java.util.function.IntFunction; +import java.util.function.Supplier; +import java.util.logging.Logger; +import java.util.stream.IntStream; + +import static org.junit.jupiter.api.Assertions.*; + +final class JepTest { + + class Bar { + // 1. Declare a Stable field + private static final StableValue LOGGER = StableValue.newInstance(); + + static Logger logger() { + + if (!LOGGER.isSet()) { + // 2. Set the stable value _after_ the field was declared + LOGGER.trySet(Logger.getLogger("com.foo.Bar")); + } + + // 3. Access the stable value with as-declared-final performance + return LOGGER.orElseThrow(); + } + } + + class Bar2 { + // 1. Declare a stable field + private static final StableValue LOGGER = StableValue.newInstance(); + + static Logger logger() { + // 2. Access the stable value with as-declared-final performance + // (single evaluation made before the first access) + return LOGGER.computeIfUnset( () -> Logger.getLogger("com.foo.Bar") ); + } + } + + class ErrorMessages { + + private static final int SIZE = 8; + + // 1. Declare a stable list of default error pages to serve up + private static final List> MESSAGES = StableValues.ofList(SIZE); + + // 2. Define a function that is to be called the first + // time a particular message number is referenced + private static String readFromFile(int messageNumber) { + try { + return Files.readString(Path.of("message-" + messageNumber + ".html")); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + static String errorPage(int messageNumber) { + // 3. Access the stable list element with as-declared-final performance + // (evaluation made before the first access) + return MESSAGES.get(messageNumber) + .computeIfUnset(() -> readFromFile(messageNumber)); + } + + } + + class MapDemo { + + // 1. Declare a stable map of loggers with two allowable keys: + // "com.foo.Bar" and "com.foo.Baz" + static final Map> LOGGERS = + StableValues.ofMap(Set.of("com.foo.Bar", "com.foo.Baz")); + + // 2. Access the memoized map with as-declared-final performance + // (evaluation made before the first access) + static Logger logger(String name) { + return LOGGERS.get(name) + .computeIfUnset(() -> Logger.getLogger(name)); + } + } + + class Memoized { + + // 1. Declare a map with stable values + private static final Map> MAP = + StableValues.ofMap(Set.of("com.foo.Bar", "com.foo.Baz")); + + // 2. Declare a memoized (cached) function backed by the stable map + private static final Function LOGGERS = + n -> MAP.get(n).computeIfUnset(() -> Logger.getLogger(n)); + + + private static final String NAME = "com.foo.Baz"; + + // 3. Access the memoized value via the function with as-declared-final + // performance (evaluation made before the first access) + Logger logger = LOGGERS.apply(NAME); + } + + class A { + private static final int SIZE = 8; + + + // 1. Declare a stable list of default error pages to serve up + private static final List> ERROR_PAGES = + StableValues.ofList(SIZE); + + // 2. Declare a memoized IntFunction backed by the stable list + private static final IntFunction ERROR_FUNCTION = + i -> ERROR_PAGES.get(i).computeIfUnset(() -> readFromFile(i)); + + // 3. Define a function that is to be called the first +// time a particular message number is referenced + private static String readFromFile(int messageNumber) { + try { + return Files.readString(Path.of("message-" + messageNumber + ".html")); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + // 4. Access the memoized list element with as-declared-final performance +// (evaluation made before the first access) + String msg = ERROR_FUNCTION.apply(2); + } + +} diff --git a/test/jdk/jdk/internal/lang/stable/StableTestUtil.java b/test/jdk/jdk/internal/lang/stable/StableTestUtil.java index da8cdfcac531f..2f248039b751e 100644 --- a/test/jdk/jdk/internal/lang/stable/StableTestUtil.java +++ b/test/jdk/jdk/internal/lang/stable/StableTestUtil.java @@ -67,4 +67,4 @@ public final String toString() { } } -} \ No newline at end of file +} diff --git a/test/jdk/jdk/internal/lang/stable/StableValueTest.java b/test/jdk/jdk/internal/lang/stable/StableValueTest.java index 8661f42e53649..7fd87ffce6b24 100644 --- a/test/jdk/jdk/internal/lang/stable/StableValueTest.java +++ b/test/jdk/jdk/internal/lang/stable/StableValueTest.java @@ -210,4 +210,4 @@ private static void join(Thread thread) { } } -} \ No newline at end of file +} diff --git a/test/jdk/jdk/internal/lang/stable/StableValuesTest.java b/test/jdk/jdk/internal/lang/stable/StableValuesTest.java index 3e263c9f63788..b33f701bace78 100644 --- a/test/jdk/jdk/internal/lang/stable/StableValuesTest.java +++ b/test/jdk/jdk/internal/lang/stable/StableValuesTest.java @@ -95,4 +95,4 @@ void ofMap() { assertEquals(3, idMap.size()); } -} \ No newline at end of file +} diff --git a/test/jdk/jdk/internal/lang/stable/TrustedFieldTypeTest.java b/test/jdk/jdk/internal/lang/stable/TrustedFieldTypeTest.java index bf0b6e7e60aaf..d1a2253882528 100644 --- a/test/jdk/jdk/internal/lang/stable/TrustedFieldTypeTest.java +++ b/test/jdk/jdk/internal/lang/stable/TrustedFieldTypeTest.java @@ -99,4 +99,4 @@ final class Holder { } -} \ No newline at end of file +} diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java index 341def510e5aa..697e998ec26a2 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java @@ -65,20 +65,25 @@ public class StableValueBenchmark { private static final StableValue STABLE2 = init(StableValue.newInstance(), VALUE2); private static final StableValue DCL = init(StableValue.newInstance(), VALUE); private static final StableValue DCL2 = init(StableValue.newInstance(), VALUE2); + private static final AtomicReference ATOMIC = new AtomicReference<>(VALUE); + private static final AtomicReference ATOMIC2 = new AtomicReference<>(VALUE2); + @Setup public void setup() { stableNull.trySet(null); stableNull2.trySet(VALUE2); // Create pollution + int sum = 0; for (int i = 0; i < 500_000; i++) { final int v = i; Dcl dclX = new Dcl<>(() -> v); - dclX.get(); + sum += dclX.get(); StableValue stableX = StableValue.newInstance(); stableX.trySet(i); - stableX.orElseThrow(); + sum += stableX.orElseThrow(); } + System.out.println("sum = " + sum); } @Benchmark @@ -117,6 +122,11 @@ public int staticDcl() { return DCL.orElseThrow() + DCL2.orElseThrow(); } + @Benchmark + public int staticAtomic() { + return ATOMIC.get() + ATOMIC2.get(); + } + private static StableValue init(StableValue m, Integer value) { m.trySet(value); return m; From 5e26de9536694d83a3340c9f0f9b3c50712972c6 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 11 Jun 2024 14:44:32 +0200 Subject: [PATCH 019/327] Update JEP --- test/jdk/jdk/internal/lang/stable/{JEP.adoc => JEP.md} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename test/jdk/jdk/internal/lang/stable/{JEP.adoc => JEP.md} (99%) diff --git a/test/jdk/jdk/internal/lang/stable/JEP.adoc b/test/jdk/jdk/internal/lang/stable/JEP.md similarity index 99% rename from test/jdk/jdk/internal/lang/stable/JEP.adoc rename to test/jdk/jdk/internal/lang/stable/JEP.md index 7d0bfe960a337..e7affcb730b9c 100644 --- a/test/jdk/jdk/internal/lang/stable/JEP.adoc +++ b/test/jdk/jdk/internal/lang/stable/JEP.md @@ -21,7 +21,7 @@ This might be the subject of a future JEP. ## Motivation Most Java developers have heard the advice "prefer immutability" (Effective -Java, Item 17). Immutability confers many advantages including: +Java, Third Edition, Item 17, by Joshua Bloch). Immutability confers many advantages including: * an immutable object can only be in one state * the invariants of an immutable object can be enforced by its constructor From 953056974b4f8fb69c31e3043f760593fd1dfb47 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 11 Jun 2024 14:57:32 +0200 Subject: [PATCH 020/327] Update JEP --- test/jdk/jdk/internal/lang/stable/JEP.md | 29 ++++++++++++------------ 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/test/jdk/jdk/internal/lang/stable/JEP.md b/test/jdk/jdk/internal/lang/stable/JEP.md index e7affcb730b9c..29487264271eb 100644 --- a/test/jdk/jdk/internal/lang/stable/JEP.md +++ b/test/jdk/jdk/internal/lang/stable/JEP.md @@ -209,10 +209,10 @@ To use the Stable Value APIs, the JVM flag `--enable-preview` must be passed in, The Stable Values API defines functions and an interface so that client code in libraries and applications can - Define and use stable (scalar) values: -- [`StableValue.newInstance()`](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/java/lang/StableValue.html) + - [`StableValue.newInstance()`](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/java/lang/StableValue.html) - Define collections: -- [`StableValues.ofList(int size)`](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/java/lang/StableValues.html#ofList(int)), -- [`StableValues.ofMap(Set keys)`](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/java/lang/StableValues.html#ofMap(java.util.Set)) + - [`StableValues.ofList(int size)`](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/java/lang/StableValues.html#ofList(int)) + - [`StableValues.ofMap(Set keys)`](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/java/lang/StableValues.html#ofMap(java.util.Set)) The Stable Values API resides in the [java.lang](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/java/lang/package-summary.html) package of the [java.base](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/module-summary.html) module. @@ -221,7 +221,7 @@ The Stable Values API resides in the [java.lang](https://cr.openjdk.org/~pminbor A _stable value_ is a holder object that is set at most once whereby it goes from "unset" to "set". It is expressed as an object of type `java.lang.StableValue`, which, like `Future`, is a holder for some computation that may or may not have occurred yet. -Fresh (unset) `StableValue` instances are created via the factory method `StableValue::of`: +Fresh (unset) `StableValue` instances are created via the factory method `StableValue::newInstance`: ``` class Bar { @@ -240,8 +240,8 @@ class Bar { } } ``` -Setting a stable value is an atomic, thread-safe operation, i.e. `StableValue::setIfUnset`, -either results in successfully initializing the `StableValue` to a value, or returns +Setting a stable value is an atomic, thread-safe operation, i.e. `StableValue::trySet`, +either results in successfully initializing the `StableValue` to a value, or retaining an already set value. This is true regardless of whether the stable value is accessed by a single thread, or concurrently, by multiple threads. @@ -273,15 +273,15 @@ class Bar { } ``` -When retrieving values, `StableValue` instances holding reference values are faster +When retrieving values, `StableValue` instances holding reference values can be faster than reference values managed via double-checked-idiom constructs as stable values rely on explicit memory barriers rather than performing volatile access on each retrieval -operation. In addition, stable values are eligible for constant folding optimizations. +operation. In addition, stable values are eligible for constant folding optimizations by the JVM. ### Stable collections The Stable Values API provides constructs that allow the creation and handling of a -*`List` of stable elements*. Lists of lazily computed values are objects of type +*`List` of stable value elements*. Lists of lazily computed values are objects of type `List>`. Consequently, each element in the list enjoys the same properties as a `StableValue`. @@ -343,11 +343,10 @@ Stable Values API allows modeling this cleanly, while still preserving good con guarantees and integrity of updates in the case of multi-threaded access. It should be noted that even though a lazily computed list of stable elements might mutate its -internal state upon external access, it is _still shallowly immutable_ because _no first-level +internal state upon external access, it is _still shallowly immutable_ because _no first-level change can ever be observed by an external observer_. This is similar to other immutable classes, such as `String` (which internally caches its `hash` value), where they might rely on mutable -internal states that are carefully kept internally and that never -shine through to the outside world. +internal states that are carefully kept internally and that never shine through to the outside world. Just as a `List` can be lazily computed, a `Map` of lazily computed stable values can also be defined and used similarly. In the example below, we lazily compute a map's stable values for an enumerated @@ -380,7 +379,7 @@ though used from several threads. ### Memoized functions -So far, we have talked about the fundamental features of Stable Values & Collections as securely +So far, we have talked about the fundamental features of Stable Values as securely wrapped `@Stable` value holders. However, it has become apparent, stable primitives are amenable to composition with other constructs in order to create more high-level and powerful features. @@ -388,7 +387,7 @@ to composition with other constructs in order to create more high-level and powe particular input value is computed only once and is remembered such that remembered outputs can be reused for subsequent calls with recurring input values. Here is how we could make sure `Logger.getLogger("com.foo.Bar")` in one of the first examples above is invoked at most once -(provided it executes successfully) in a multi-threaded environment: +(provided it executes successfully) in a multi-threaded environment: ``` class Memoized { @@ -411,7 +410,7 @@ class Memoized { } ``` -In the example above, for each key, the function is invoked at most once per loading of the containing class +In the example above, for each key, the function is invoked at most once per loading of the containing class `MapDemo` (`MapDemo`, in turn, can be loaded at most once into any given `ClassLoader`) as it is backed by a `Map` with lazily computed values which upholds the invoke-at-most-once-per-key invariant. From 0f610134256e875cad2ad6484d9afcbaf6b8a91e Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 11 Jun 2024 15:00:08 +0200 Subject: [PATCH 021/327] Fix typo --- test/jdk/jdk/internal/lang/stable/JEP.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/jdk/jdk/internal/lang/stable/JEP.md b/test/jdk/jdk/internal/lang/stable/JEP.md index 29487264271eb..65b76e737a685 100644 --- a/test/jdk/jdk/internal/lang/stable/JEP.md +++ b/test/jdk/jdk/internal/lang/stable/JEP.md @@ -226,7 +226,7 @@ Fresh (unset) `StableValue` instances are created via the factory method `Stable ``` class Bar { // 1. Declare a Stable field - private static final StableValue LOGGER = StableValue.newInstance()(); + private static final StableValue LOGGER = StableValue.newInstance(); static Logger logger() { From 634ab25dca9edc77b26dc7137cb3a4ea3990ffb1 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 11 Jun 2024 16:15:13 +0200 Subject: [PATCH 022/327] Update docs --- .../jdk/internal/lang/StableValue.java | 39 ++++++++----------- .../jdk/internal/lang/StableValues.java | 3 +- 2 files changed, 17 insertions(+), 25 deletions(-) diff --git a/src/java.base/share/classes/jdk/internal/lang/StableValue.java b/src/java.base/share/classes/jdk/internal/lang/StableValue.java index b83b962322e0f..48b74d97ff0cf 100644 --- a/src/java.base/share/classes/jdk/internal/lang/StableValue.java +++ b/src/java.base/share/classes/jdk/internal/lang/StableValue.java @@ -42,43 +42,36 @@ * factory. *

* The utility class {@linkplain StableValues} contains a number of convenience methods - * for creating constructs involving StableValues: + * for creating constructs involving StableValue: *

- * A StableValue can be created and computed by a background thread like this: - * {@snippet lang = java: - * StableValue stableValue = StableValues.ofBackground( - * Thread.ofVirtual().factory(), Value::new); - *} - * A new background thread will be created from a factory (e.g. `Thread.ofVirtual.factory`) - * and said thread will compute the returned StableValue's holder value using a supplier - * (e.g. `Value::new`). - *

- * A List of stable values with a given {@code size} can be created the following way: + * A List of StableValue elements with a given {@code size} can be created the following way: * {@snippet lang = java : * List> list = StableValues.ofList(size); * } - * The list can be used to model stable arrays of one dimensions. If two or more + * The list can be used to model stable one-dimensional arrays. If two- or more * dimensional arrays are to be modeled, a List of List of ... of StableValue can be used. *

- * A Map of stable values with a given set of {@code keys} can be created like this: + * A Map with a given set of {@code keys} associated with StableValue objects can be + * created like this: * {@snippet lang = java : * Map> map = StableValues.ofMap(keys); * } - * A memoized Supplier, where a given {@code original} supplier is guaranteed to be - * successfully invoked at most once even in a multi-threaded environment, can be + * A memoized Supplier, where a given {@code original} Supplier is guaranteed to + * be successfully invoked at most once even in a multithreaded environment, can be * created like this: * {@snippet lang = java : * Supplier memoized = StableValues.memoizedSupplier(original, null); * } - * The memoized supplier can also be lazily computed using a fresh background thread if a + * The memoized supplier can also be lazily computed by a fresh background thread if a * thread factory is provided as a second parameter as shown here: * {@snippet lang = java : * Supplier memoized = StableValues.memoizedSupplier(original, Thread.ofVirtual().factory()); * } *

- * A memoized IntFunction, for the allowed given {@code size} values and where the - * given {@code original} IntFunction is guaranteed to be successfully invoked at most - * once per inout index even in a multi-threaded environment, can be created like this: + * A memoized IntFunction, for the allowed given {@code size} values {@code [0, size)} + * and where the given {@code original} IntFunction is guaranteed to be successfully + * invoked at most once per inout index even in a multithreaded environment, can be + * created like this: * {@snippet lang = java : * static IntFunction memoizedIntFunction(int size, * IntFunction original) { @@ -89,7 +82,7 @@ * } * A memoized Function, for the allowed given {@code input} values and where the * given {@code original} function is guaranteed to be successfully invoked at most - * once per input value even in a multi-threaded environment, can be created like this: + * once per input value even in a multithreaded environment, can be created like this: * {@snippet lang = java : * static Function memoizedFunction(Set inputs, * Function original) { @@ -107,8 +100,8 @@ * The constructs above are eligible for similar JVM optimizations as the StableValue * class itself. *

- * All methods that can set the stable value's holder value are guarded such that competing - * set operations (by other threads) will block if another set operation is + * All methods that can set the stable value's holder value are guarded such that + * competing set operations (by other threads) will block if another set operation is * already in progress. *

* Except for a StableValue's holder value itself, all method parameters must be @@ -175,7 +168,7 @@ public sealed interface StableValue * return newValue; * } * Except, the method is atomic and thread-safe with respect to this and all other - * methods that can set the StableValue's holder value. + * methods that can set this StableValue's holder value. * * @param supplier the supplier to be used to compute a holder value * @return the current (existing or computed) holder value associated with diff --git a/src/java.base/share/classes/jdk/internal/lang/StableValues.java b/src/java.base/share/classes/jdk/internal/lang/StableValues.java index 624190166275c..e8ed58097c10c 100644 --- a/src/java.base/share/classes/jdk/internal/lang/StableValues.java +++ b/src/java.base/share/classes/jdk/internal/lang/StableValues.java @@ -134,8 +134,7 @@ public static List> ofList(int size) { * {@snippet lang = java : * Map> map = keys.stream() * .collect(Collectors.toMap( - * Function.identity(), - * _ -> StableValue.newInstance())); + * Function.identity(), _ -> StableValue.newInstance())); * } * @param keys the keys in the {@code Map} * @param the {@code Map}'s key type From 86d6917dba829682e111272d12ec6a358df43920 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 12 Jun 2024 11:22:46 +0200 Subject: [PATCH 023/327] Rework memory semantics --- .../internal/lang/stable/StableValueImpl.java | 57 +++++++++++++++---- 1 file changed, 46 insertions(+), 11 deletions(-) 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 4ce2e48f7ad45..9970dda858601 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 @@ -44,7 +44,12 @@ public final class StableValueImpl implements StableValue { private final Object mutex = new Object(); - // This field is reflectively accessed via Unsafe using acquire/release semantics. + // Generally, fields annotated with `@Stable` are accessed by the JVM using special + // memory semantics rules (see `parse.hpp` and `parse(1|2|3).cpp`). + // Here, this field is reflectively accessed via Unsafe using explicit memory semantics. + // + // Meaning Value + // ------- ----- // Unset: null // Set(non-null): The set value (!= nullSentinel()) // Set(null): nullSentinel() @@ -57,14 +62,28 @@ private StableValueImpl() {} @Override public boolean trySet(T value) { synchronized (mutex) { - return UNSAFE.weakCompareAndSetReferenceRelease(this, VALUE_OFFSET, null, wrap(value)); + // Prevents reordering of store operations with other store operations. + // This means any stores made to fields in the `value` object prior to this + // point cannot be reordered with the CAS operation of the reference to the + // `value` field. + // In other words, if a reader (using plain memory semantics) can observe a + // `value` reference, any field updates made prior to this fence are + // guaranteed to be seen. + UNSAFE.storeStoreFence(); + // This upholds the invariant, the `@Stable value` field is written to + // at most once. + return UNSAFE.compareAndSetReference(this, VALUE_OFFSET, null, wrap(value)); } } @ForceInline @Override public T orElseThrow() { - final T t = valueAcquire(); + T t = valuePlain(); + if (t != null) { + return unwrap(t); + } + t = valueVolatile(); if (t != null) { return unwrap(t); } @@ -74,7 +93,11 @@ public T orElseThrow() { @ForceInline @Override public T orElse(T other) { - final T t = valueAcquire(); + T t = valuePlain(); + if (t != null) { + return unwrap(t); + } + t = valueVolatile(); if (t != null) { return unwrap(t); } @@ -84,13 +107,17 @@ public T orElse(T other) { @ForceInline @Override public boolean isSet() { - return valueAcquire() != null; + return valuePlain() != null || valueVolatile() != null; } @ForceInline @Override public T computeIfUnset(Supplier supplier) { - final T t = valueAcquire(); + T t = valuePlain(); + if (t != null) { + return unwrap(t); + } + t = valueVolatile(); if (t != null) { return unwrap(t); } @@ -100,7 +127,9 @@ public T computeIfUnset(Supplier supplier) { @DontInline private T compute(Supplier supplier) { synchronized (mutex) { - T t = valueAcquire(); + // Updates to the `value` is always made under `mutex` synchronization + // meaning plain memory semantics is enough here. + T t = valuePlain(); if (t != null) { return unwrap(t); } @@ -118,18 +147,24 @@ public int hashCode() { @Override public boolean equals(Object obj) { return obj instanceof StableValueImpl other && - Objects.equals(valueAcquire(), other.valueAcquire()); + Objects.equals(orElse(null), other.orElse(null)); } @Override public String toString() { - return "StableValue" + render(valueAcquire()); + return "StableValue" + render(valueVolatile()); + } + + @ForceInline + private T valuePlain() { + // Appears to be faster than `(T) UNSAFE.getReference(this, VALUE_OFFSET)` + return value; } @SuppressWarnings("unchecked") @ForceInline - private T valueAcquire() { - return (T) UNSAFE.getReferenceAcquire(this, VALUE_OFFSET); + private T valueVolatile() { + return (T) UNSAFE.getReferenceVolatile(this, VALUE_OFFSET); } // Wraps null values into a sentinel value From 092da854c64dda960a810afcea9a81d769be9f05 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 12 Jun 2024 15:32:58 +0200 Subject: [PATCH 024/327] Add a new overload of computeIfUnset and a test --- .../jdk/internal/lang/StableValue.java | 49 +++++- .../internal/lang/stable/StableValueImpl.java | 43 ++++- test/jdk/jdk/internal/lang/stable/JEP.md | 59 ++++--- .../jdk/jdk/internal/lang/stable/JepTest.java | 43 ++--- .../internal/lang/stable/StableTestUtil.java | 17 ++ .../internal/lang/stable/StableValueTest.java | 35 ++++ .../StableValuesSafePublicationTest.java | 154 ++++++++++++++++++ 7 files changed, 344 insertions(+), 56 deletions(-) create mode 100644 test/jdk/jdk/internal/lang/stable/StableValuesSafePublicationTest.java diff --git a/src/java.base/share/classes/jdk/internal/lang/StableValue.java b/src/java.base/share/classes/jdk/internal/lang/StableValue.java index 48b74d97ff0cf..c49fdd3d010f4 100644 --- a/src/java.base/share/classes/jdk/internal/lang/StableValue.java +++ b/src/java.base/share/classes/jdk/internal/lang/StableValue.java @@ -28,6 +28,7 @@ import jdk.internal.lang.stable.StableValueImpl; import java.util.NoSuchElementException; +import java.util.function.Function; import java.util.function.Supplier; /** @@ -144,8 +145,8 @@ public sealed interface StableValue boolean isSet(); /** - * If the holder value is unset, attempts to compute the holder value using the given - * {@code supplier} and enters it into the holder value. + * If the holder value is unset, attempts to compute the holder value using the + * provided {@code supplier} and enters the result into the holder value. * *

If the {@code supplier} itself throws an (unchecked) exception, the exception * is rethrown, and no holder value is set. The most common usage is to construct a @@ -163,7 +164,7 @@ public sealed interface StableValue * if (stable.isSet()) { * return stable.getOrThrow(); * } - * T newValue = supplier.apply(key); + * T newValue = supplier.get(); * stable.trySet(newValue); * return newValue; * } @@ -176,6 +177,48 @@ public sealed interface StableValue */ T computeIfUnset(Supplier supplier); + /** + * If the holder value is unset, attempts to compute the holder value using the + * provided {@code mapper} applied to the provided {@code input} context and enters + * the result into the holder value. + * + *

If the {@code mapper} itself throws an (unchecked) exception, the exception + * is rethrown, and no holder value is set. The most common usage is to construct a + * new object serving as an initial value or memoized result, as in: + * + *

 {@code
+     * Map> map = StableValues.ofMap(...);
+     * K key = ...;
+     * T t = map.get(key)
+     *          .computeIfUnset(key, Foo::valueFromKey);
+     * }
+ * + * The method also allows static Functions/lambdas to be used, for example by + * providing `this` as an {@code input} and the static Function/lambda accessing + * properties of the `this` input. + * + * @implSpec + * The implementation of this method is equivalent to the following steps for this + * {@code stable} and a given non-null {@code mapper} and {@code inout}: + * + *
 {@code
+     * if (stable.isSet()) {
+     *     return stable.getOrThrow();
+     * }
+     * T newValue = mapper.apply(input);
+     * stable.trySet(newValue);
+     * return newValue;
+     * }
+ * Except, the method is atomic and thread-safe with respect to this and all other + * methods that can set this StableValue's holder value. + * + * @param input to be applied to the {@code mapper} + * @param mapper the mapper to be used to compute a holder value + * @return the current (existing or computed) holder value associated with + * this stable value + */ + T computeIfUnset(I input, Function mapper); + // Convenience methods /** 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 9970dda858601..5c42bf5ca45f7 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 @@ -33,6 +33,7 @@ import java.util.NoSuchElementException; import java.util.Objects; +import java.util.function.Function; import java.util.function.Supplier; public final class StableValueImpl implements StableValue { @@ -46,7 +47,8 @@ public final class StableValueImpl implements StableValue { // Generally, fields annotated with `@Stable` are accessed by the JVM using special // memory semantics rules (see `parse.hpp` and `parse(1|2|3).cpp`). - // Here, this field is reflectively accessed via Unsafe using explicit memory semantics. + // + // This field is reflectively accessed via Unsafe using explicit memory semantics. // // Meaning Value // ------- ----- @@ -61,7 +63,13 @@ private StableValueImpl() {} @ForceInline @Override public boolean trySet(T value) { + if (valuePlain() != null) { + return false; + } synchronized (mutex) { + if (valuePlain() != null) { + return false; + } // Prevents reordering of store operations with other store operations. // This means any stores made to fields in the `value` object prior to this // point cannot be reordered with the CAS operation of the reference to the @@ -69,10 +77,13 @@ public boolean trySet(T value) { // In other words, if a reader (using plain memory semantics) can observe a // `value` reference, any field updates made prior to this fence are // guaranteed to be seen. - UNSAFE.storeStoreFence(); + UNSAFE.storeStoreFence(); // Redundant as a volatile put provides a store barrier? + + // We are alone here under the `mutex` // This upholds the invariant, the `@Stable value` field is written to // at most once. - return UNSAFE.compareAndSetReference(this, VALUE_OFFSET, null, wrap(value)); + UNSAFE.putReferenceVolatile(this, VALUE_OFFSET, wrap(value)); + return true; } } @@ -121,11 +132,26 @@ public T computeIfUnset(Supplier supplier) { if (t != null) { return unwrap(t); } - return compute(supplier); + return tryCompute(null, supplier); + } + + @ForceInline + @Override + public T computeIfUnset(I input, Function function) { + T t = valuePlain(); + if (t != null) { + return unwrap(t); + } + t = valueVolatile(); + if (t != null) { + return unwrap(t); + } + return tryCompute(input, function); } + @SuppressWarnings("unchecked") @DontInline - private T compute(Supplier supplier) { + private T tryCompute(I input, Object provider) { synchronized (mutex) { // Updates to the `value` is always made under `mutex` synchronization // meaning plain memory semantics is enough here. @@ -133,7 +159,12 @@ private T compute(Supplier supplier) { if (t != null) { return unwrap(t); } - t = supplier.get(); + if (provider instanceof Supplier supplier) { + t = (T) supplier.get(); + } else { + t = ((Function) provider).apply(input);; + } + // Todo: Do not go into sync again... trySet(t); return orElseThrow(); } diff --git a/test/jdk/jdk/internal/lang/stable/JEP.md b/test/jdk/jdk/internal/lang/stable/JEP.md index 65b76e737a685..477b1dcb32658 100644 --- a/test/jdk/jdk/internal/lang/stable/JEP.md +++ b/test/jdk/jdk/internal/lang/stable/JEP.md @@ -317,7 +317,7 @@ class ErrorMessages { // 3. Access the stable list element with as-declared-final performance // (evaluation made before the first access) return MESSAGES.get(messageNumber) - .computeIfUnset(() -> readFromFile(messageNumber)); + .computeIfUnset(messageNumber, ErrorMessages::readFromFile); } } @@ -364,7 +364,7 @@ class MapDemo { // (evaluation made before the first access) static Logger logger(String name) { return LOGGERS.get(name) - .computeIfUnset(() -> Logger.getLogger(name)); + .computeIfUnset(name, Logger::getLogger); } } ``` @@ -398,7 +398,7 @@ class Memoized { // 2. Declare a memoized (cached) function backed by the stable map private static final Function LOGGERS = - n -> MAP.get(n).computeIfUnset(() -> Logger.getLogger(n)); + n -> MAP.get(n).computeIfUnset(n , Logger::getLogger); ... @@ -421,33 +421,38 @@ Similarly to how a `Function` can be memoized using a backing lazily computed ma can be used for an `IntFunction` that will record its cached value in a backing _stable list_: ``` -// 1. Declare a stable list of default error pages to serve up -private static final List> ERROR_PAGES = - StableValues.ofList(SIZE); - -// 2. Declare a memoized IntFunction backed by the stable list -private static final IntFunction ERROR_FUNCTION = - i -> ERROR_PAGES.get(i).computeIfUnset(() -> readFromFile(i)); - -// 3. Define a function that is to be called the first -// time a particular message number is referenced -private static String readFromFile(int messageNumber) { - try { - return Files.readString(Path.of("message-" + messageNumber + ".html")); - } catch (IOException e) { - throw new UncheckedIOException(e); +class ErrorMessages { + + // 1. Declare a stable list of default error pages to serve up + private static final List> ERROR_PAGES = + StableValues.ofList(SIZE); + + // 2. Declare a memoized IntFunction backed by the stable list + private static final IntFunction ERROR_FUNCTION = + i -> ERROR_PAGES.get(i).computeIfUnset(i , ErrorMessages::readFromFile); + + // 3. Define a function that is to be called the first + // time a particular message number is referenced + private static String readFromFile(int messageNumber) { + try { + return Files.readString(Path.of("message-" + messageNumber + ".html")); + } catch (IOException e) { + throw new UncheckedIOException(e); + } } -} -// 4. Access the memoized list element with as-declared-final performance -// (evaluation made before the first access) -String msg = ERROR_FUNCTION.apply(2); + ... -// -// -// -// Payment was denied: Insufficient funds. -// + // 4. Access the memoized list element with as-declared-final performance + // (evaluation made before the first access) + String msg = ERROR_FUNCTION.apply(2); + + // + // + // + // Payment was denied: Insufficient funds. + // +} ``` The same paradigm can be used for creating a memoized `Supplier` (backed by a single `StableValue` instance) or a memoized `Predicate`(backed by a lazily computed `Map>`). An astute reader will be able to write such constructs in a few lines. diff --git a/test/jdk/jdk/internal/lang/stable/JepTest.java b/test/jdk/jdk/internal/lang/stable/JepTest.java index be52616d73086..f9be22fe726dd 100644 --- a/test/jdk/jdk/internal/lang/stable/JepTest.java +++ b/test/jdk/jdk/internal/lang/stable/JepTest.java @@ -103,7 +103,7 @@ static String errorPage(int messageNumber) { // 3. Access the stable list element with as-declared-final performance // (evaluation made before the first access) return MESSAGES.get(messageNumber) - .computeIfUnset(() -> readFromFile(messageNumber)); + .computeIfUnset(messageNumber, ErrorMessages::readFromFile); } } @@ -119,7 +119,7 @@ class MapDemo { // (evaluation made before the first access) static Logger logger(String name) { return LOGGERS.get(name) - .computeIfUnset(() -> Logger.getLogger(name)); + .computeIfUnset(name, Logger::getLogger); } } @@ -131,7 +131,7 @@ class Memoized { // 2. Declare a memoized (cached) function backed by the stable map private static final Function LOGGERS = - n -> MAP.get(n).computeIfUnset(() -> Logger.getLogger(n)); + n -> MAP.get(n).computeIfUnset(n , Logger::getLogger); private static final String NAME = "com.foo.Baz"; @@ -142,30 +142,33 @@ class Memoized { } class A { - private static final int SIZE = 8; + static + class ErrorMessages { + private static final int SIZE = 8; - // 1. Declare a stable list of default error pages to serve up - private static final List> ERROR_PAGES = - StableValues.ofList(SIZE); - // 2. Declare a memoized IntFunction backed by the stable list - private static final IntFunction ERROR_FUNCTION = - i -> ERROR_PAGES.get(i).computeIfUnset(() -> readFromFile(i)); + // 1. Declare a stable list of default error pages to serve up + private static final List> ERROR_PAGES = + StableValues.ofList(SIZE); + + // 2. Declare a memoized IntFunction backed by the stable list + private static final IntFunction ERROR_FUNCTION = + i -> ERROR_PAGES.get(i).computeIfUnset(i, ErrorMessages::readFromFile); - // 3. Define a function that is to be called the first + // 3. Define a function that is to be called the first // time a particular message number is referenced - private static String readFromFile(int messageNumber) { - try { - return Files.readString(Path.of("message-" + messageNumber + ".html")); - } catch (IOException e) { - throw new UncheckedIOException(e); + private static String readFromFile(int messageNumber) { + try { + return Files.readString(Path.of("message-" + messageNumber + ".html")); + } catch (IOException e) { + throw new UncheckedIOException(e); + } } - } - // 4. Access the memoized list element with as-declared-final performance + // 4. Access the memoized list element with as-declared-final performance // (evaluation made before the first access) - String msg = ERROR_FUNCTION.apply(2); + String msg = ERROR_FUNCTION.apply(2); + } } - } diff --git a/test/jdk/jdk/internal/lang/stable/StableTestUtil.java b/test/jdk/jdk/internal/lang/stable/StableTestUtil.java index 2f248039b751e..8809e1d188c3d 100644 --- a/test/jdk/jdk/internal/lang/stable/StableTestUtil.java +++ b/test/jdk/jdk/internal/lang/stable/StableTestUtil.java @@ -22,6 +22,7 @@ */ import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; import java.util.function.Supplier; final class StableTestUtil { @@ -44,6 +45,22 @@ public T get() { } + public static final class CountingFunction + extends AbstractCounting> + implements Function { + + public CountingFunction(Function delegate) { + super(delegate); + } + + @Override + public R apply(T t) { + incrementCounter(); + return delegate.apply(t); + } + + } + abstract static class AbstractCounting { private final AtomicInteger cnt = new AtomicInteger(); diff --git a/test/jdk/jdk/internal/lang/stable/StableValueTest.java b/test/jdk/jdk/internal/lang/stable/StableValueTest.java index 7fd87ffce6b24..8471ea04894ad 100644 --- a/test/jdk/jdk/internal/lang/stable/StableValueTest.java +++ b/test/jdk/jdk/internal/lang/stable/StableValueTest.java @@ -38,6 +38,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.LockSupport; import java.util.function.BiPredicate; +import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.IntStream; @@ -114,6 +115,40 @@ void computeIfUnsetException() { assertThrows(IllegalStateException.class, () -> stable.setOrThrow(1)); } + @Test + void computeIfUnset2Arg() { + StableValue stable = StableValue.newInstance(); + StableTestUtil.CountingFunction cntFunction = new StableTestUtil.CountingFunction<>(Function.identity()); + StableTestUtil.CountingFunction cntFunction2 = new StableTestUtil.CountingFunction<>(Function.identity()); + assertEquals(42, stable.computeIfUnset(42, cntFunction)); + assertEquals(1, cntFunction.cnt()); + assertEquals(42, stable.computeIfUnset(42, cntFunction)); + assertEquals(1, cntFunction.cnt()); + assertEquals(42, stable.computeIfUnset(13, cntFunction2)); + assertEquals(0, cntFunction2.cnt()); + assertEquals("StableValue[42]", stable.toString()); + assertEquals(42, stable.orElse(null)); + assertFalse(stable.trySet(null)); + assertFalse(stable.trySet(1)); + assertThrows(IllegalStateException.class, () -> stable.setOrThrow(1)); + } + + @Test + void computeIfUnset2ArgException() { + StableValue stable = StableValue.newInstance(); + Function function = _ -> { + throw new UnsupportedOperationException("aaa"); + }; + var x = assertThrows(UnsupportedOperationException.class, () -> stable.computeIfUnset(42, function)); + assertTrue(x.getMessage().contains("aaa")); + assertEquals(42, stable.computeIfUnset(42, Function.identity())); + assertEquals("StableValue[42]", stable.toString()); + assertEquals(42, stable.orElse(13)); + assertFalse(stable.trySet(null)); + assertFalse(stable.trySet(1)); + assertThrows(IllegalStateException.class, () -> stable.setOrThrow(1)); + } + @Test void testHashCode() { StableValue s0 = StableValue.newInstance(); diff --git a/test/jdk/jdk/internal/lang/stable/StableValuesSafePublicationTest.java b/test/jdk/jdk/internal/lang/stable/StableValuesSafePublicationTest.java new file mode 100644 index 0000000000000..0e1c416135191 --- /dev/null +++ b/test/jdk/jdk/internal/lang/stable/StableValuesSafePublicationTest.java @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2024, 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 making sure StableValue publishes values safely + * @modules java.base/jdk.internal.lang + * @compile --enable-preview -source ${jdk.version} StableValuesSafePublicationTest.java + * @run junit/othervm --enable-preview StableValuesSafePublicationTest + */ + +import jdk.internal.lang.StableValue; +import jdk.internal.lang.StableValues; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.function.Supplier; +import java.util.stream.IntStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +final class StableValuesSafePublicationTest { + + private static final int SIZE = 100_000; + private static final int THREADS = Runtime.getRuntime().availableProcessors() / 2; + + static final class Holder { + final int a; + final int b; + + Holder() { + a = 1; + b = 1; + } + } + + static final class Consumer implements Runnable { + + final int[] observations = new int[SIZE]; + final StableValue[] stables; + + Consumer(StableValue[] stables) { + this.stables = stables; + } + + @Override + public void run() { + StableValue s; + Holder h; + for (int i = 0; i < SIZE; i++) { + // Wait until we see a new StableValue + while ((s = stables[i]) == null) {} + // Wait until the StableValue has a holder value + while ((h = s.orElse(null)) == null) {} + int a = h.a; + int b = h.b; + observations[i] = a + (b << 1); + } + } + } + + static final class Producer implements Runnable { + + final StableValue[] stables; + + Producer(StableValue[] stables) { + this.stables = stables; + } + + @Override + public void run() { + int dummy = 0; + StableValue s; + for (int i = 0; i < SIZE; i++) { + s = StableValue.newInstance(); + s.trySet(new Holder()); + stables[i] = s; + // Wait for a while + for (int j = 0; j < 100; j++) { + dummy++; + } + } + System.out.println(dummy); + } + } + + @Test + void main() { + @SuppressWarnings("unchecked") + final StableValue[] stables = (StableValue[]) new StableValue[SIZE]; + + List consumers = IntStream.range(0, THREADS) + .mapToObj(_ -> new Consumer(stables)) + .toList(); + + List consumersThreads = IntStream.range(0, THREADS) + .mapToObj(i -> Thread.ofPlatform() + .name("Consumer Thread " + i) + .start(consumers.get(i))) + .toList(); + + Thread producerThread = Thread.ofPlatform() + .name("Producer Thread") + .start(new Producer(stables)); + + join(producerThread); + join(consumersThreads.toArray(Thread[]::new)); + + int[] histogram = new int[4]; + for (Consumer consumer : consumers) { + for (int i = 0; i < SIZE; i++) { + histogram[consumer.observations[i]]++; + } + } + + assertEquals(0, histogram[0]); + assertEquals(0, histogram[1]); + assertEquals(0, histogram[2]); + // We should only observe a = b = 1 -> 3 + assertEquals(THREADS * SIZE, histogram[3]); + } + + + static void join(Thread... threads) { + try { + for (Thread t:threads) { + t.join(); + } + } catch (InterruptedException ie) { + fail(ie); + } + } + +} From fc69389653023fd9d08e48227e9f95c4ad47dfe5 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 12 Jun 2024 16:34:59 +0200 Subject: [PATCH 025/327] Move tests --- .../lang/stable => java/lang/StableValue}/JEP.md | 0 .../stable => java/lang/StableValue}/JepTest.java | 11 ----------- .../lang/StableValue}/StableTestUtil.java | 0 .../lang/StableValue}/StableValueTest.java | 0 .../StableValuesSafePublicationTest.java | 14 +++++++++++--- .../lang/StableValue}/StableValuesTest.java | 0 .../lang/StableValue}/TrustedFieldTypeTest.java | 0 7 files changed, 11 insertions(+), 14 deletions(-) rename test/jdk/{jdk/internal/lang/stable => java/lang/StableValue}/JEP.md (100%) rename test/jdk/{jdk/internal/lang/stable => java/lang/StableValue}/JepTest.java (93%) rename test/jdk/{jdk/internal/lang/stable => java/lang/StableValue}/StableTestUtil.java (100%) rename test/jdk/{jdk/internal/lang/stable => java/lang/StableValue}/StableValueTest.java (100%) rename test/jdk/{jdk/internal/lang/stable => java/lang/StableValue}/StableValuesSafePublicationTest.java (90%) rename test/jdk/{jdk/internal/lang/stable => java/lang/StableValue}/StableValuesTest.java (100%) rename test/jdk/{jdk/internal/lang/stable => java/lang/StableValue}/TrustedFieldTypeTest.java (100%) diff --git a/test/jdk/jdk/internal/lang/stable/JEP.md b/test/jdk/java/lang/StableValue/JEP.md similarity index 100% rename from test/jdk/jdk/internal/lang/stable/JEP.md rename to test/jdk/java/lang/StableValue/JEP.md diff --git a/test/jdk/jdk/internal/lang/stable/JepTest.java b/test/jdk/java/lang/StableValue/JepTest.java similarity index 93% rename from test/jdk/jdk/internal/lang/stable/JepTest.java rename to test/jdk/java/lang/StableValue/JepTest.java index f9be22fe726dd..14eb1989ed37c 100644 --- a/test/jdk/jdk/internal/lang/stable/JepTest.java +++ b/test/jdk/java/lang/StableValue/JepTest.java @@ -30,28 +30,17 @@ import jdk.internal.lang.StableValue; import jdk.internal.lang.StableValues; -import org.junit.jupiter.api.Test; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.BitSet; import java.util.List; import java.util.Map; -import java.util.NoSuchElementException; import java.util.Set; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.LockSupport; -import java.util.function.BiPredicate; import java.util.function.Function; import java.util.function.IntFunction; -import java.util.function.Supplier; import java.util.logging.Logger; -import java.util.stream.IntStream; - -import static org.junit.jupiter.api.Assertions.*; final class JepTest { diff --git a/test/jdk/jdk/internal/lang/stable/StableTestUtil.java b/test/jdk/java/lang/StableValue/StableTestUtil.java similarity index 100% rename from test/jdk/jdk/internal/lang/stable/StableTestUtil.java rename to test/jdk/java/lang/StableValue/StableTestUtil.java diff --git a/test/jdk/jdk/internal/lang/stable/StableValueTest.java b/test/jdk/java/lang/StableValue/StableValueTest.java similarity index 100% rename from test/jdk/jdk/internal/lang/stable/StableValueTest.java rename to test/jdk/java/lang/StableValue/StableValueTest.java diff --git a/test/jdk/jdk/internal/lang/stable/StableValuesSafePublicationTest.java b/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java similarity index 90% rename from test/jdk/jdk/internal/lang/stable/StableValuesSafePublicationTest.java rename to test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java index 0e1c416135191..3bb18b0f136d1 100644 --- a/test/jdk/jdk/internal/lang/stable/StableValuesSafePublicationTest.java +++ b/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java @@ -29,11 +29,13 @@ */ import jdk.internal.lang.StableValue; -import jdk.internal.lang.StableValues; import org.junit.jupiter.api.Test; +import java.util.Arrays; import java.util.List; -import java.util.function.Supplier; +import java.util.Objects; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; import java.util.stream.IntStream; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -144,7 +146,13 @@ void main() { static void join(Thread... threads) { try { for (Thread t:threads) { - t.join(); + t.join(TimeUnit.MINUTES.toMillis(1)); + if (t.isAlive()) { + String stack = Arrays.stream(t.getStackTrace()) + .map(Objects::toString) + .collect(Collectors.joining(System.lineSeparator())); + fail("Thread did not complete: " + t + System.lineSeparator() + stack); + } } } catch (InterruptedException ie) { fail(ie); diff --git a/test/jdk/jdk/internal/lang/stable/StableValuesTest.java b/test/jdk/java/lang/StableValue/StableValuesTest.java similarity index 100% rename from test/jdk/jdk/internal/lang/stable/StableValuesTest.java rename to test/jdk/java/lang/StableValue/StableValuesTest.java diff --git a/test/jdk/jdk/internal/lang/stable/TrustedFieldTypeTest.java b/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java similarity index 100% rename from test/jdk/jdk/internal/lang/stable/TrustedFieldTypeTest.java rename to test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java From 32579ba2fd20067e052b780eb8ba0d95e74860f6 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 12 Jun 2024 17:24:59 +0200 Subject: [PATCH 026/327] Add debug --- .../StableValuesSafePublicationTest.java | 82 +++++++++++-------- 1 file changed, 50 insertions(+), 32 deletions(-) diff --git a/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java b/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java index 3bb18b0f136d1..34ab8ea784112 100644 --- a/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java +++ b/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java @@ -31,12 +31,17 @@ import jdk.internal.lang.StableValue; import org.junit.jupiter.api.Test; +import java.sql.Time; import java.util.Arrays; import java.util.List; import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import java.util.stream.IntStream; +import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; @@ -45,6 +50,9 @@ final class StableValuesSafePublicationTest { private static final int SIZE = 100_000; private static final int THREADS = Runtime.getRuntime().availableProcessors() / 2; + @SuppressWarnings("unchecked") + private static final StableValue[] STABLES = (StableValue[]) new StableValue[SIZE]; + private static final CountDownLatch LATCH = new CountDownLatch(1); static final class Holder { final int a; @@ -59,19 +67,21 @@ static final class Holder { static final class Consumer implements Runnable { final int[] observations = new int[SIZE]; - final StableValue[] stables; - - Consumer(StableValue[] stables) { - this.stables = stables; - } + int i = 0; @Override public void run() { + try { + LATCH.await(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + StableValue s; Holder h; - for (int i = 0; i < SIZE; i++) { + for (; i < SIZE; i++) { // Wait until we see a new StableValue - while ((s = stables[i]) == null) {} + while ((s = STABLES[i]) == null) {} // Wait until the StableValue has a holder value while ((h = s.orElse(null)) == null) {} int a = h.a; @@ -83,36 +93,30 @@ public void run() { static final class Producer implements Runnable { - final StableValue[] stables; - - Producer(StableValue[] stables) { - this.stables = stables; - } + static final int LOOP_DELAY = 100; @Override public void run() { - int dummy = 0; + LATCH.countDown(); + int sum = 0; StableValue s; for (int i = 0; i < SIZE; i++) { s = StableValue.newInstance(); s.trySet(new Holder()); - stables[i] = s; + STABLES[i] = s; // Wait for a while - for (int j = 0; j < 100; j++) { - dummy++; + for (int j = 0; j < LOOP_DELAY; j++) { + sum++; } } - System.out.println(dummy); + System.out.println("The producer completed with " + (sum / LOOP_DELAY) + " values."); } } @Test void main() { - @SuppressWarnings("unchecked") - final StableValue[] stables = (StableValue[]) new StableValue[SIZE]; - List consumers = IntStream.range(0, THREADS) - .mapToObj(_ -> new Consumer(stables)) + .mapToObj(_ -> new Consumer()) .toList(); List consumersThreads = IntStream.range(0, THREADS) @@ -121,12 +125,14 @@ void main() { .start(consumers.get(i))) .toList(); + Producer producer = new Producer(); + Thread producerThread = Thread.ofPlatform() .name("Producer Thread") - .start(new Producer(stables)); + .start(producer); - join(producerThread); - join(consumersThreads.toArray(Thread[]::new)); + join(consumers, producerThread); + join(consumers, consumersThreads.toArray(Thread[]::new)); int[] histogram = new int[4]; for (Consumer consumer : consumers) { @@ -142,16 +148,28 @@ void main() { assertEquals(THREADS * SIZE, histogram[3]); } - - static void join(Thread... threads) { + static void join(List consumers, Thread... threads) { try { for (Thread t:threads) { - t.join(TimeUnit.MINUTES.toMillis(1)); - if (t.isAlive()) { - String stack = Arrays.stream(t.getStackTrace()) - .map(Objects::toString) - .collect(Collectors.joining(System.lineSeparator())); - fail("Thread did not complete: " + t + System.lineSeparator() + stack); + long deadline = System.currentTimeMillis()+TimeUnit.MINUTES.toMillis(1); + while (t.isAlive()) { + t.join(TimeUnit.SECONDS.toMillis(10)); + if (t.isAlive()) { + String stack = Arrays.stream(t.getStackTrace()) + .map(Objects::toString) + .collect(Collectors.joining(System.lineSeparator())); + System.err.println(t + ": " + stack); + for (int i = 0; i < consumers.size(); i++) { + System.err.println("Consumer " + i + ": " + consumers.get(i).i); + } + } + if (System.currentTimeMillis() > deadline) { + long nonNulls = CompletableFuture.supplyAsync(() -> + Stream.of(STABLES) + .filter(Objects::nonNull) + .count(), Executors.newSingleThreadExecutor()).join(); + fail("Giving up! Non-nulls seen by a new thread: " + nonNulls); + } } } } catch (InterruptedException ie) { From 6de38a48e715e09bead34fec67315c06fca0475a Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 12 Jun 2024 19:05:48 +0200 Subject: [PATCH 027/327] Fix error in test --- .../StableValuesSafePublicationTest.java | 49 +++++++++---------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java b/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java index 34ab8ea784112..1e6d545dec14c 100644 --- a/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java +++ b/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java @@ -24,21 +24,22 @@ /* @test * @summary Basic tests for making sure StableValue publishes values safely * @modules java.base/jdk.internal.lang + * @modules java.base/jdk.internal.misc * @compile --enable-preview -source ${jdk.version} StableValuesSafePublicationTest.java * @run junit/othervm --enable-preview StableValuesSafePublicationTest */ import jdk.internal.lang.StableValue; +import jdk.internal.misc.Unsafe; import org.junit.jupiter.api.Test; -import java.sql.Time; import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.LockSupport; import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; @@ -47,12 +48,18 @@ import static org.junit.jupiter.api.Assertions.fail; final class StableValuesSafePublicationTest { - private static final int SIZE = 100_000; private static final int THREADS = Runtime.getRuntime().availableProcessors() / 2; - @SuppressWarnings("unchecked") - private static final StableValue[] STABLES = (StableValue[]) new StableValue[SIZE]; - private static final CountDownLatch LATCH = new CountDownLatch(1); + private static final StableValue[] STABLES = stables(); + + static StableValue[] stables() { + @SuppressWarnings("unchecked") + StableValue[] stables = (StableValue[]) new StableValue[SIZE]; + for (int i = 0; i < SIZE; i++) { + stables[i] = StableValue.newInstance(); + } + return stables; + } static final class Holder { final int a; @@ -67,21 +74,14 @@ static final class Holder { static final class Consumer implements Runnable { final int[] observations = new int[SIZE]; + final StableValue[] stables = STABLES; int i = 0; @Override public void run() { - try { - LATCH.await(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - - StableValue s; - Holder h; for (; i < SIZE; i++) { - // Wait until we see a new StableValue - while ((s = STABLES[i]) == null) {} + StableValue s = stables[i]; + Holder h; // Wait until the StableValue has a holder value while ((h = s.orElse(null)) == null) {} int a = h.a; @@ -93,23 +93,16 @@ public void run() { static final class Producer implements Runnable { - static final int LOOP_DELAY = 100; + final StableValue[] stables = STABLES; @Override public void run() { - LATCH.countDown(); - int sum = 0; StableValue s; for (int i = 0; i < SIZE; i++) { - s = StableValue.newInstance(); + s = stables[i]; s.trySet(new Holder()); - STABLES[i] = s; - // Wait for a while - for (int j = 0; j < LOOP_DELAY; j++) { - sum++; - } + LockSupport.parkNanos(1000); } - System.out.println("The producer completed with " + (sum / LOOP_DELAY) + " values."); } } @@ -177,4 +170,8 @@ static void join(List consumers, Thread... threads) { } } + private static long objOffset(int i) { + return Unsafe.ARRAY_OBJECT_BASE_OFFSET + Unsafe.ARRAY_OBJECT_INDEX_SCALE * (long) i; + } + } From 8cf79dc7d5e190253f4ed417e70ef1b17600ddd3 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 12 Jun 2024 19:13:28 +0200 Subject: [PATCH 028/327] Fix problem in equals() method --- .../classes/jdk/internal/lang/stable/StableValueImpl.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 5c42bf5ca45f7..c707ef008a262 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 @@ -178,7 +178,10 @@ public int hashCode() { @Override public boolean equals(Object obj) { return obj instanceof StableValueImpl other && - Objects.equals(orElse(null), other.orElse(null)); + // Note that the value returned from `orElse()` can never be + // `nullSentinel()` (because it will be unwrapped to `null`). + // Therefore, we can safely use this as a marker for "no value set". + Objects.equals(orElse(nullSentinel()), other.orElse(nullSentinel())); } @Override From 38a76e0e604e3a5df91866abce6b6a949ca397c4 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Thu, 13 Jun 2024 09:06:54 +0200 Subject: [PATCH 029/327] Clean up test --- .../StableValue/StableValuesSafePublicationTest.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java b/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java index 1e6d545dec14c..60d7dd532f419 100644 --- a/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java +++ b/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java @@ -30,7 +30,6 @@ */ import jdk.internal.lang.StableValue; -import jdk.internal.misc.Unsafe; import org.junit.jupiter.api.Test; import java.util.Arrays; @@ -48,6 +47,7 @@ import static org.junit.jupiter.api.Assertions.fail; final class StableValuesSafePublicationTest { + private static final int SIZE = 100_000; private static final int THREADS = Runtime.getRuntime().availableProcessors() / 2; private static final StableValue[] STABLES = stables(); @@ -134,10 +134,14 @@ void main() { } } + // a = 0, b = 0 : index 0 assertEquals(0, histogram[0]); + // a = 1, b = 0 : index 1 assertEquals(0, histogram[1]); + // a = 0, b = 1 : index 2 assertEquals(0, histogram[2]); - // We should only observe a = b = 1 -> 3 + // a = 1, b = 1 : index 3 + // All observations should end up in this bucket assertEquals(THREADS * SIZE, histogram[3]); } @@ -170,8 +174,4 @@ static void join(List consumers, Thread... threads) { } } - private static long objOffset(int i) { - return Unsafe.ARRAY_OBJECT_BASE_OFFSET + Unsafe.ARRAY_OBJECT_INDEX_SCALE * (long) i; - } - } From a8cc7b53e6eee215bf358a7ab3b3c0f3ae2e4254 Mon Sep 17 00:00:00 2001 From: Per-Ake Minborg Date: Thu, 13 Jun 2024 09:58:52 +0200 Subject: [PATCH 030/327] Update src/java.base/share/classes/jdk/internal/lang/StableValue.java Co-authored-by: Chen Liang --- src/java.base/share/classes/jdk/internal/lang/StableValue.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/java.base/share/classes/jdk/internal/lang/StableValue.java b/src/java.base/share/classes/jdk/internal/lang/StableValue.java index c49fdd3d010f4..80a11725eed44 100644 --- a/src/java.base/share/classes/jdk/internal/lang/StableValue.java +++ b/src/java.base/share/classes/jdk/internal/lang/StableValue.java @@ -203,7 +203,7 @@ public sealed interface StableValue * *
 {@code
      * if (stable.isSet()) {
-     *     return stable.getOrThrow();
+     *     return stable.orElseThrow();
      * }
      * T newValue = mapper.apply(input);
      * stable.trySet(newValue);

From c7b6d098d501bd21dc24c9e9c63eb68cba086c4d Mon Sep 17 00:00:00 2001
From: Per-Ake Minborg 
Date: Thu, 13 Jun 2024 10:12:18 +0200
Subject: [PATCH 031/327] Update
 src/java.base/share/classes/jdk/internal/lang/StableValue.java

Co-authored-by: Chen Liang 
---
 src/java.base/share/classes/jdk/internal/lang/StableValue.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/java.base/share/classes/jdk/internal/lang/StableValue.java b/src/java.base/share/classes/jdk/internal/lang/StableValue.java
index 80a11725eed44..715af53a189a1 100644
--- a/src/java.base/share/classes/jdk/internal/lang/StableValue.java
+++ b/src/java.base/share/classes/jdk/internal/lang/StableValue.java
@@ -162,7 +162,7 @@ public sealed interface StableValue
      *
      * 
 {@code
      * if (stable.isSet()) {
-     *     return stable.getOrThrow();
+     *     return stable.orElseThrow();
      * }
      * T newValue = supplier.get();
      * stable.trySet(newValue);

From 052555b677ef2e0fe8eda29bbfbca3fc1d4c4d04 Mon Sep 17 00:00:00 2001
From: Per Minborg 
Date: Thu, 13 Jun 2024 11:19:00 +0200
Subject: [PATCH 032/327] Update after comments

---
 .../jdk/internal/lang/StableValue.java        | 77 ++++++++++------
 .../internal/lang/stable/StableValueImpl.java | 92 +++++++++----------
 2 files changed, 88 insertions(+), 81 deletions(-)

diff --git a/src/java.base/share/classes/jdk/internal/lang/StableValue.java b/src/java.base/share/classes/jdk/internal/lang/StableValue.java
index 715af53a189a1..710cd781de493 100644
--- a/src/java.base/share/classes/jdk/internal/lang/StableValue.java
+++ b/src/java.base/share/classes/jdk/internal/lang/StableValue.java
@@ -78,7 +78,7 @@
  *                                                   IntFunction original) {
  *         List> backing = StableValues.ofList(size);
  *         return i -> backing.get(i)
- *                       .computeIfUnset(() -> original.apply(i));
+ *                       .computeIfUnset(i, original::apply);
  *     }
  * }
  * A memoized Function, for the allowed given {@code input} values and where the
@@ -93,7 +93,7 @@
  *                 throw new IllegalArgumentException("Input not allowed: "+t);
  *             }
  *             return backing.get(t)
- *                         .computeIfUnset(() -> original.apply(t));
+ *                         .computeIfUnset(t, original);
  *         };
  *     }
  * }
@@ -120,6 +120,8 @@ public sealed interface StableValue
     /**
      * {@return {@code true} if the holder value was set to the provided {@code value},
      * otherwise returns {@code false}}
+     * 

+ * When this method returns, a holder value is always set. * * @param value to set (nullable) */ @@ -128,6 +130,7 @@ public sealed interface StableValue /** * {@return the set holder value (nullable) if set, otherwise return the * {@code other} value} + * * @param other to return if the stable holder value is not set */ T orElse(T other); @@ -152,24 +155,27 @@ public sealed interface StableValue * is rethrown, and no holder value is set. The most common usage is to construct a * new object serving as an initial value or memoized result, as in: * - *

 {@code
-     * T t = stable.computeIfUnset(T::new);
-     * }
+ * {@snippet lang = java : + * T t = stable.computeIfUnset(T::new); + * } * * @implSpec * The implementation of this method is equivalent to the following steps for this * {@code stable} and a given non-null {@code supplier}: * - *
 {@code
-     * if (stable.isSet()) {
-     *     return stable.orElseThrow();
+     * {@snippet lang = java :
+     *     if (stable.isSet()) {
+     *         return stable.orElseThrow();
+     *     }
+     *     T newValue = supplier.get();
+     *     stable.trySet(newValue);
+     *     return newValue;
      * }
-     * T newValue = supplier.get();
-     * stable.trySet(newValue);
-     * return newValue;
-     * }
- * Except, the method is atomic and thread-safe with respect to this and all other - * methods that can set this StableValue's holder value. + * Except, the method is atomic, thread-safe and guarded with synchronization + * with respect to this method and all other methods that can set this StableValue's + * holder value. + *

+ * If this method returns without throwing an Exception, a holder value is always set. * * @param supplier the supplier to be used to compute a holder value * @return the current (existing or computed) holder value associated with @@ -186,31 +192,40 @@ public sealed interface StableValue * is rethrown, and no holder value is set. The most common usage is to construct a * new object serving as an initial value or memoized result, as in: * - *

 {@code
-     * Map> map = StableValues.ofMap(...);
-     * K key = ...;
-     * T t = map.get(key)
-     *          .computeIfUnset(key, Foo::valueFromKey);
-     * }
- * + * {@snippet lang = java : + * Map> map = StableValues.ofMap(...); + * K key = ...; + * T t = map.get(key) + * .computeIfUnset(key, Foo::valueFromKey); + * } + *

* The method also allows static Functions/lambdas to be used, for example by * providing `this` as an {@code input} and the static Function/lambda accessing * properties of the `this` input. + *

+ * This method can also be used to emulate a compare-and-exchange idiom for a + * given {@code stable} and {@code candidate} value, as in: + * {@snippet lang = java : + * T t = stable.computeIfUnset(candidate, Function.identity()); + * } * * @implSpec * The implementation of this method is equivalent to the following steps for this * {@code stable} and a given non-null {@code mapper} and {@code inout}: * - *

 {@code
-     * if (stable.isSet()) {
-     *     return stable.orElseThrow();
+     * {@snippet lang = java :
+     *     if (stable.isSet()) {
+     *         return stable.orElseThrow();
+     *     }
+     *     T newValue = mapper.apply(input);
+     *     stable.trySet(newValue);
+     *     return newValue;
      * }
-     * T newValue = mapper.apply(input);
-     * stable.trySet(newValue);
-     * return newValue;
-     * }
- * Except, the method is atomic and thread-safe with respect to this and all other - * methods that can set this StableValue's holder value. + * Except, the method is atomic, thread-safe and guarded with synchronization + * with respect to this method and all other methods that can set this StableValue's + * holder value. + *

+ * If this method returns without throwing an Exception, a holder value is always set. * * @param input to be applied to the {@code mapper} * @param mapper the mapper to be used to compute a holder value @@ -224,6 +239,8 @@ public sealed interface StableValue /** * Sets the holder value to the provided {@code value}, or, if already set, * throws {@linkplain IllegalStateException}} + *

+ * When this method returns (or throws an Exception), a holder value is always set. * * @param value to set (nullable) * @throws IllegalStateException if a holder value is already set 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 c707ef008a262..e485d990b4b70 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 @@ -63,38 +63,41 @@ private StableValueImpl() {} @ForceInline @Override public boolean trySet(T value) { - if (valuePlain() != null) { + if (value() != null) { return false; } synchronized (mutex) { + // Updates to the `value` is always made under `mutex` synchronization + // meaning plain memory semantics is enough here. if (valuePlain() != null) { return false; } - // Prevents reordering of store operations with other store operations. - // This means any stores made to fields in the `value` object prior to this - // point cannot be reordered with the CAS operation of the reference to the - // `value` field. - // In other words, if a reader (using plain memory semantics) can observe a - // `value` reference, any field updates made prior to this fence are - // guaranteed to be seen. - UNSAFE.storeStoreFence(); // Redundant as a volatile put provides a store barrier? - - // We are alone here under the `mutex` - // This upholds the invariant, the `@Stable value` field is written to - // at most once. - UNSAFE.putReferenceVolatile(this, VALUE_OFFSET, wrap(value)); + set0(value); return true; } } + @ForceInline + private void set0(T value) { + // Prevents reordering of store operations with other store operations. + // This means any stores made to fields in the `value` object prior to this + // point cannot be reordered with the CAS operation of the reference to the + // `value` field. + // In other words, if a reader (using plain memory semantics) can observe a + // `value` reference, any field updates made prior to this fence are + // guaranteed to be seen. + UNSAFE.storeStoreFence(); // Redundant as a volatile put provides a store barrier? + + // We are alone here under the `mutex` + // This upholds the invariant, the `@Stable value` field is written to + // at most once. + UNSAFE.putReferenceVolatile(this, VALUE_OFFSET, wrap(value)); + } + @ForceInline @Override public T orElseThrow() { - T t = valuePlain(); - if (t != null) { - return unwrap(t); - } - t = valueVolatile(); + final T t = value(); if (t != null) { return unwrap(t); } @@ -104,11 +107,7 @@ public T orElseThrow() { @ForceInline @Override public T orElse(T other) { - T t = valuePlain(); - if (t != null) { - return unwrap(t); - } - t = valueVolatile(); + final T t = value(); if (t != null) { return unwrap(t); } @@ -118,17 +117,13 @@ public T orElse(T other) { @ForceInline @Override public boolean isSet() { - return valuePlain() != null || valueVolatile() != null; + return value() != null; } @ForceInline @Override public T computeIfUnset(Supplier supplier) { - T t = valuePlain(); - if (t != null) { - return unwrap(t); - } - t = valueVolatile(); + final T t = value(); if (t != null) { return unwrap(t); } @@ -138,11 +133,7 @@ public T computeIfUnset(Supplier supplier) { @ForceInline @Override public T computeIfUnset(I input, Function function) { - T t = valuePlain(); - if (t != null) { - return unwrap(t); - } - t = valueVolatile(); + final T t = value(); if (t != null) { return unwrap(t); } @@ -162,11 +153,10 @@ private T tryCompute(I input, Object provider) { if (provider instanceof Supplier supplier) { t = (T) supplier.get(); } else { - t = ((Function) provider).apply(input);; + t = ((Function) provider).apply(input); } - // Todo: Do not go into sync again... - trySet(t); - return orElseThrow(); + set0(t); + return t; } } @@ -186,19 +176,22 @@ public boolean equals(Object obj) { @Override public String toString() { - return "StableValue" + render(valueVolatile()); + return "StableValue" + render(value()); } + @SuppressWarnings("unchecked") @ForceInline - private T valuePlain() { - // Appears to be faster than `(T) UNSAFE.getReference(this, VALUE_OFFSET)` - return value; + // First, try to read the value using plain memory semantics. + // If not set, fall back to `volatile` memory semantics. + private T value() { + final T t = valuePlain(); + return t != null ? t : (T) UNSAFE.getReferenceVolatile(this, VALUE_OFFSET); } - @SuppressWarnings("unchecked") @ForceInline - private T valueVolatile() { - return (T) UNSAFE.getReferenceVolatile(this, VALUE_OFFSET); + private T valuePlain() { + // Appears to be faster than `(T) UNSAFE.getReference(this, VALUE_OFFSET)` + return value; } // Wraps null values into a sentinel value @@ -209,7 +202,7 @@ private static T wrap(T t) { // Unwraps null sentinel values into null @ForceInline private static T unwrap(T t) { - return t == nullSentinel() ? null : t; + return t != nullSentinel() ? t : null; } @SuppressWarnings("unchecked") @@ -218,10 +211,7 @@ private static T nullSentinel() { } private static String render(T t) { - if (t != null) { - return t == nullSentinel() ? "[null]" : "[" + t + "]"; - } - return ".unset"; + return (t == null) ? ".unset" : "[" + unwrap(t) + "]"; } From 568bb280aa5f77e1b3166db847ae606aca4371d2 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Thu, 13 Jun 2024 12:20:19 +0200 Subject: [PATCH 033/327] Rework pausing in Producer test thread --- .../StableValue/StableValuesSafePublicationTest.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java b/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java index 60d7dd532f419..9c2b332c4ea45 100644 --- a/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java +++ b/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java @@ -38,18 +38,16 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.LockSupport; import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assertions.*; final class StableValuesSafePublicationTest { private static final int SIZE = 100_000; - private static final int THREADS = Runtime.getRuntime().availableProcessors() / 2; + private static final int THREADS = Runtime.getRuntime().availableProcessors(); private static final StableValue[] STABLES = stables(); static StableValue[] stables() { @@ -98,10 +96,14 @@ static final class Producer implements Runnable { @Override public void run() { StableValue s; + long deadlineNs = System.nanoTime(); for (int i = 0; i < SIZE; i++) { s = stables[i]; s.trySet(new Holder()); - LockSupport.parkNanos(1000); + deadlineNs += 1000; + while (System.nanoTime() < deadlineNs) { + Thread.onSpinWait(); + } } } } From badf2cf224564c63aa52c8d87c25890b68e779c5 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Thu, 13 Jun 2024 12:23:22 +0200 Subject: [PATCH 034/327] Improve debug output in a test --- .../java/lang/StableValue/StableValuesSafePublicationTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java b/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java index 9c2b332c4ea45..190d0748905a7 100644 --- a/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java +++ b/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java @@ -165,9 +165,10 @@ static void join(List consumers, Thread... threads) { if (System.currentTimeMillis() > deadline) { long nonNulls = CompletableFuture.supplyAsync(() -> Stream.of(STABLES) + .map(s -> s.orElse(null)) .filter(Objects::nonNull) .count(), Executors.newSingleThreadExecutor()).join(); - fail("Giving up! Non-nulls seen by a new thread: " + nonNulls); + fail("Giving up! Set stables seen by a new thread: " + nonNulls); } } } From 269f3c7353872a4736b18c5639597ad234dbdd43 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Thu, 13 Jun 2024 14:36:10 +0200 Subject: [PATCH 035/327] Update JEP --- .../jdk/internal/lang/StableValue.java | 3 +- test/jdk/java/lang/StableValue/JEP.md | 35 +++++++++++++++---- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/src/java.base/share/classes/jdk/internal/lang/StableValue.java b/src/java.base/share/classes/jdk/internal/lang/StableValue.java index 710cd781de493..3db4d446c9ae3 100644 --- a/src/java.base/share/classes/jdk/internal/lang/StableValue.java +++ b/src/java.base/share/classes/jdk/internal/lang/StableValue.java @@ -227,8 +227,9 @@ public sealed interface StableValue *

* If this method returns without throwing an Exception, a holder value is always set. * - * @param input to be applied to the {@code mapper} + * @param input context to be applied to the {@code mapper} * @param mapper the mapper to be used to compute a holder value + * @param The type of the {@code input} context * @return the current (existing or computed) holder value associated with * this stable value */ diff --git a/test/jdk/java/lang/StableValue/JEP.md b/test/jdk/java/lang/StableValue/JEP.md index 477b1dcb32658..6b272f9d42787 100644 --- a/test/jdk/java/lang/StableValue/JEP.md +++ b/test/jdk/java/lang/StableValue/JEP.md @@ -219,7 +219,7 @@ The Stable Values API resides in the [java.lang](https://cr.openjdk.org/~pminbor ### Stable values A _stable value_ is a holder object that is set at most once whereby it -goes from "unset" to "set". It is expressed as an object of type `java.lang.StableValue`, +goes from "unset" to "set". It is expressed as an object of type `jdk.internal.lang.StableValue`, which, like `Future`, is a holder for some computation that may or may not have occurred yet. Fresh (unset) `StableValue` instances are created via the factory method `StableValue::newInstance`: @@ -258,7 +258,8 @@ method simultaneously if they call the `logger()` method at about the same time. competing threads, there might be applications where it is a requirement, that a supplying method is only called once. -In such cases, it is possible to compute and set an unset value on-demand as shown in this example in which case `StableValue` will uphold the invoke-at-most-once invariant for the provided `Supplier`: +In such cases, it is possible to compute and set an unset value on-demand as shown in this example in which +case `StableValue` will uphold the invoke-at-most-once invariant for the provided `Supplier`: ``` class Bar { @@ -285,7 +286,8 @@ The Stable Values API provides constructs that allow the creation and handling o `List>`. Consequently, each element in the list enjoys the same properties as a `StableValue`. -Like a `StableValue` object, a `List` of stable value elements is created via a factory method by providing the size of the desired `List`: +Like a `StableValue` object, a `List` of stable value elements is created via a factory method by +providing the size of the desired `List`: ``` static List> StableValues.ofList(int size) { ... } @@ -374,8 +376,8 @@ arbitrarily, but pre-specified, keys in a resource-efficient and performant way. high-performance, non-evicting caches may now be easily and reliably realized. It is worth remembering, that the stable collections all promise the function provided at computation -(used to lazily compute elements or values) is invoked at most once per index or key; even -though used from several threads. +(used to lazily compute elements or values) is invoked at most once per index or key (absent any Exceptions); +even though used from several threads. ### Memoized functions @@ -455,7 +457,28 @@ class ErrorMessages { } ``` -The same paradigm can be used for creating a memoized `Supplier` (backed by a single `StableValue` instance) or a memoized `Predicate`(backed by a lazily computed `Map>`). An astute reader will be able to write such constructs in a few lines. +The same paradigm can be used for creating a memoized `Supplier` (backed by a single `StableValue` instance) or +a memoized `Predicate`(backed by a lazily computed `Map>`). An astute reader will be able +to write such constructs in a few lines. + +An advantage with memoized functions, compared to working directly with StableValues, is that the initialization logic +can be centralized and maintained in a single place, usually at the same place where the memoized function is defined. + +The StableValues API offers yet another factory for memoized suppliers that will invoke a provided `original` +supplier at most once (if successful) and also allows an optional `threadFactory` to be provided from which +a new value-computing background thread will be created: + +``` +static final Supplier MEMOIZED = StableValues.memoizedSupplier(original, Thread.ofVirtual().factory()); +``` + +This can provide a best-of-several-worlds situation where the memoized supplier can be quickly defined (as no +computation is made by the defining thread), the holder value is computed in a background thread (thus neither +interfering significantly with the critical startup path nor with future accessing threads), and the threads actually +accessing the holder value can access the holder value with as-if-final performance and without having to compute +a holder value. This is true under the assumption, that the background thread can complete computation before accessing +threads requires a holder value. If this is not the case, at least some reduction of blocking time can be enjoyed as +the background thread has a head start compared to the accessing threads. ## Alternatives From 2e9fdf25e13b9ea9c926b583f1a9459abe4c214e Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Thu, 13 Jun 2024 15:23:43 +0200 Subject: [PATCH 036/327] Add memory consistency section --- .../jdk/internal/lang/StableValue.java | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/java.base/share/classes/jdk/internal/lang/StableValue.java b/src/java.base/share/classes/jdk/internal/lang/StableValue.java index 3db4d446c9ae3..c30754dd124d5 100644 --- a/src/java.base/share/classes/jdk/internal/lang/StableValue.java +++ b/src/java.base/share/classes/jdk/internal/lang/StableValue.java @@ -38,6 +38,9 @@ * A stable value is said to be monotonic because the state of a stable value can only go * from unset to set and consequently, a value can only be set * at most once. + * + * + *

Factories

*

* To create a new fresh (unset) StableValue, use the {@linkplain StableValue#newInstance()} * factory. @@ -100,11 +103,25 @@ *

* The constructs above are eligible for similar JVM optimizations as the StableValue * class itself. - *

+ * + * + *

Blocking

* All methods that can set the stable value's holder value are guarded such that * competing set operations (by other threads) will block if another set operation is * already in progress. - *

+ * + * + *

Memory Consistency Properties

+ * Certain interactions between StableValue operations form + * happens-before + * relationships: + *
    + *
  • Actions in a thread prior to calling a method that sets the holder value + * happen-before any other thread observes a set holder value.
  • + *
+ * + * + *

Nullability

* Except for a StableValue's holder value itself, all method parameters must be * non-null or a {@link NullPointerException} will be thrown. * From da24174b35fed103d8d677923d28b44358d3433f Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 24 Jun 2024 10:36:04 +0200 Subject: [PATCH 037/327] Move javadoc text out of @implSpec tag --- .../share/classes/jdk/internal/lang/StableValue.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/java.base/share/classes/jdk/internal/lang/StableValue.java b/src/java.base/share/classes/jdk/internal/lang/StableValue.java index c30754dd124d5..a7c506bb034ed 100644 --- a/src/java.base/share/classes/jdk/internal/lang/StableValue.java +++ b/src/java.base/share/classes/jdk/internal/lang/StableValue.java @@ -175,6 +175,8 @@ public sealed interface StableValue * {@snippet lang = java : * T t = stable.computeIfUnset(T::new); * } + *

+ * If this method returns without throwing an Exception, a holder value is always set. * * @implSpec * The implementation of this method is equivalent to the following steps for this @@ -191,8 +193,6 @@ public sealed interface StableValue * Except, the method is atomic, thread-safe and guarded with synchronization * with respect to this method and all other methods that can set this StableValue's * holder value. - *

- * If this method returns without throwing an Exception, a holder value is always set. * * @param supplier the supplier to be used to compute a holder value * @return the current (existing or computed) holder value associated with From 666166ec2eb11af2cbde686e5bcae2f9869b74ec Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 24 Jun 2024 10:51:04 +0200 Subject: [PATCH 038/327] Simplify equals/hashcode --- .../jdk/internal/lang/stable/StableValueImpl.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) 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 e485d990b4b70..2a1a97fdf1421 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 @@ -162,16 +162,15 @@ private T tryCompute(I input, Object provider) { @Override public int hashCode() { - return Objects.hashCode(orElse(null)); + return Objects.hashCode(value()); } @Override public boolean equals(Object obj) { return obj instanceof StableValueImpl other && - // Note that the value returned from `orElse()` can never be - // `nullSentinel()` (because it will be unwrapped to `null`). - // Therefore, we can safely use this as a marker for "no value set". - Objects.equals(orElse(nullSentinel()), other.orElse(nullSentinel())); + // Note that the returned value() will be `null` if the holder value + // is unset and `nullSentinel()` if the holder value is `null`. + Objects.equals(value(), other.value()); } @Override From 72fe9416c365fdc57e3a9406ae98e4f7835f16fd Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 24 Jun 2024 11:00:09 +0200 Subject: [PATCH 039/327] Rename method --- .../classes/jdk/internal/lang/StableValue.java | 2 +- .../jdk/internal/lang/stable/StableValueImpl.java | 2 +- test/jdk/java/lang/StableValue/JEP.md | 8 ++++---- test/jdk/java/lang/StableValue/JepTest.java | 8 ++++---- .../jdk/java/lang/StableValue/StableValueTest.java | 14 +++++++------- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/java.base/share/classes/jdk/internal/lang/StableValue.java b/src/java.base/share/classes/jdk/internal/lang/StableValue.java index a7c506bb034ed..744c0031afc7d 100644 --- a/src/java.base/share/classes/jdk/internal/lang/StableValue.java +++ b/src/java.base/share/classes/jdk/internal/lang/StableValue.java @@ -250,7 +250,7 @@ public sealed interface StableValue * @return the current (existing or computed) holder value associated with * this stable value */ - T computeIfUnset(I input, Function mapper); + T mapIfUnset(I input, Function mapper); // Convenience methods 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 2a1a97fdf1421..d178d38d0ab94 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 @@ -132,7 +132,7 @@ public T computeIfUnset(Supplier supplier) { @ForceInline @Override - public T computeIfUnset(I input, Function function) { + public T mapIfUnset(I input, Function function) { final T t = value(); if (t != null) { return unwrap(t); diff --git a/test/jdk/java/lang/StableValue/JEP.md b/test/jdk/java/lang/StableValue/JEP.md index 6b272f9d42787..72631adb838e1 100644 --- a/test/jdk/java/lang/StableValue/JEP.md +++ b/test/jdk/java/lang/StableValue/JEP.md @@ -319,7 +319,7 @@ class ErrorMessages { // 3. Access the stable list element with as-declared-final performance // (evaluation made before the first access) return MESSAGES.get(messageNumber) - .computeIfUnset(messageNumber, ErrorMessages::readFromFile); + .mapIfUnset(messageNumber, ErrorMessages::readFromFile); } } @@ -366,7 +366,7 @@ class MapDemo { // (evaluation made before the first access) static Logger logger(String name) { return LOGGERS.get(name) - .computeIfUnset(name, Logger::getLogger); + .mapIfUnset(name, Logger::getLogger); } } ``` @@ -400,7 +400,7 @@ class Memoized { // 2. Declare a memoized (cached) function backed by the stable map private static final Function LOGGERS = - n -> MAP.get(n).computeIfUnset(n , Logger::getLogger); + n -> MAP.get(n).mapIfUnset(n , Logger::getLogger); ... @@ -431,7 +431,7 @@ class ErrorMessages { // 2. Declare a memoized IntFunction backed by the stable list private static final IntFunction ERROR_FUNCTION = - i -> ERROR_PAGES.get(i).computeIfUnset(i , ErrorMessages::readFromFile); + i -> ERROR_PAGES.get(i).mapIfUnset(i , ErrorMessages::readFromFile); // 3. Define a function that is to be called the first // time a particular message number is referenced diff --git a/test/jdk/java/lang/StableValue/JepTest.java b/test/jdk/java/lang/StableValue/JepTest.java index 14eb1989ed37c..3b473d95b357e 100644 --- a/test/jdk/java/lang/StableValue/JepTest.java +++ b/test/jdk/java/lang/StableValue/JepTest.java @@ -92,7 +92,7 @@ static String errorPage(int messageNumber) { // 3. Access the stable list element with as-declared-final performance // (evaluation made before the first access) return MESSAGES.get(messageNumber) - .computeIfUnset(messageNumber, ErrorMessages::readFromFile); + .mapIfUnset(messageNumber, ErrorMessages::readFromFile); } } @@ -108,7 +108,7 @@ class MapDemo { // (evaluation made before the first access) static Logger logger(String name) { return LOGGERS.get(name) - .computeIfUnset(name, Logger::getLogger); + .mapIfUnset(name, Logger::getLogger); } } @@ -120,7 +120,7 @@ class Memoized { // 2. Declare a memoized (cached) function backed by the stable map private static final Function LOGGERS = - n -> MAP.get(n).computeIfUnset(n , Logger::getLogger); + n -> MAP.get(n).mapIfUnset(n , Logger::getLogger); private static final String NAME = "com.foo.Baz"; @@ -143,7 +143,7 @@ class ErrorMessages { // 2. Declare a memoized IntFunction backed by the stable list private static final IntFunction ERROR_FUNCTION = - i -> ERROR_PAGES.get(i).computeIfUnset(i, ErrorMessages::readFromFile); + i -> ERROR_PAGES.get(i).mapIfUnset(i, ErrorMessages::readFromFile); // 3. Define a function that is to be called the first // time a particular message number is referenced diff --git a/test/jdk/java/lang/StableValue/StableValueTest.java b/test/jdk/java/lang/StableValue/StableValueTest.java index 8471ea04894ad..f1adda60f033d 100644 --- a/test/jdk/java/lang/StableValue/StableValueTest.java +++ b/test/jdk/java/lang/StableValue/StableValueTest.java @@ -116,15 +116,15 @@ void computeIfUnsetException() { } @Test - void computeIfUnset2Arg() { + void mapIfUnset() { StableValue stable = StableValue.newInstance(); StableTestUtil.CountingFunction cntFunction = new StableTestUtil.CountingFunction<>(Function.identity()); StableTestUtil.CountingFunction cntFunction2 = new StableTestUtil.CountingFunction<>(Function.identity()); - assertEquals(42, stable.computeIfUnset(42, cntFunction)); + assertEquals(42, stable.mapIfUnset(42, cntFunction)); assertEquals(1, cntFunction.cnt()); - assertEquals(42, stable.computeIfUnset(42, cntFunction)); + assertEquals(42, stable.mapIfUnset(42, cntFunction)); assertEquals(1, cntFunction.cnt()); - assertEquals(42, stable.computeIfUnset(13, cntFunction2)); + assertEquals(42, stable.mapIfUnset(13, cntFunction2)); assertEquals(0, cntFunction2.cnt()); assertEquals("StableValue[42]", stable.toString()); assertEquals(42, stable.orElse(null)); @@ -134,14 +134,14 @@ void computeIfUnset2Arg() { } @Test - void computeIfUnset2ArgException() { + void mapIfUnsetException() { StableValue stable = StableValue.newInstance(); Function function = _ -> { throw new UnsupportedOperationException("aaa"); }; - var x = assertThrows(UnsupportedOperationException.class, () -> stable.computeIfUnset(42, function)); + var x = assertThrows(UnsupportedOperationException.class, () -> stable.mapIfUnset(42, function)); assertTrue(x.getMessage().contains("aaa")); - assertEquals(42, stable.computeIfUnset(42, Function.identity())); + assertEquals(42, stable.mapIfUnset(42, Function.identity())); assertEquals("StableValue[42]", stable.toString()); assertEquals(42, stable.orElse(13)); assertFalse(stable.trySet(null)); From 318da269f0044d677609d1fa185c61a9d654d381 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 24 Jun 2024 11:06:39 +0200 Subject: [PATCH 040/327] Remove trailing whitespaces --- test/jdk/java/lang/StableValue/JEP.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/jdk/java/lang/StableValue/JEP.md b/test/jdk/java/lang/StableValue/JEP.md index 72631adb838e1..cb83a26a1d2a9 100644 --- a/test/jdk/java/lang/StableValue/JEP.md +++ b/test/jdk/java/lang/StableValue/JEP.md @@ -457,15 +457,15 @@ class ErrorMessages { } ``` -The same paradigm can be used for creating a memoized `Supplier` (backed by a single `StableValue` instance) or +The same paradigm can be used for creating a memoized `Supplier` (backed by a single `StableValue` instance) or a memoized `Predicate`(backed by a lazily computed `Map>`). An astute reader will be able to write such constructs in a few lines. An advantage with memoized functions, compared to working directly with StableValues, is that the initialization logic can be centralized and maintained in a single place, usually at the same place where the memoized function is defined. -The StableValues API offers yet another factory for memoized suppliers that will invoke a provided `original` -supplier at most once (if successful) and also allows an optional `threadFactory` to be provided from which +The StableValues API offers yet another factory for memoized suppliers that will invoke a provided `original` +supplier at most once (if successful) and also allows an optional `threadFactory` to be provided from which a new value-computing background thread will be created: ``` @@ -474,10 +474,10 @@ static final Supplier MEMOIZED = StableValues.memoizedSupplier(original, Thre This can provide a best-of-several-worlds situation where the memoized supplier can be quickly defined (as no computation is made by the defining thread), the holder value is computed in a background thread (thus neither -interfering significantly with the critical startup path nor with future accessing threads), and the threads actually +interfering significantly with the critical startup path nor with future accessing threads), and the threads actually accessing the holder value can access the holder value with as-if-final performance and without having to compute a holder value. This is true under the assumption, that the background thread can complete computation before accessing -threads requires a holder value. If this is not the case, at least some reduction of blocking time can be enjoyed as +threads requires a holder value. If this is not the case, at least some reduction of blocking time can be enjoyed as the background thread has a head start compared to the accessing threads. ## Alternatives From c1ad0950cc6fbf669f43aea26d27deaa67964da2 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 24 Jun 2024 13:08:49 +0200 Subject: [PATCH 041/327] Add two memoized factories --- .../jdk/internal/lang/StableValue.java | 97 ++++++++--- .../jdk/internal/lang/StableValues.java | 164 +++++++++++++----- test/jdk/java/lang/StableValue/JEP.md | 82 ++++----- test/jdk/java/lang/StableValue/JepTest.java | 33 ++-- .../lang/StableValue/StableValuesTest.java | 4 +- 5 files changed, 253 insertions(+), 127 deletions(-) diff --git a/src/java.base/share/classes/jdk/internal/lang/StableValue.java b/src/java.base/share/classes/jdk/internal/lang/StableValue.java index 744c0031afc7d..5d7230d80a129 100644 --- a/src/java.base/share/classes/jdk/internal/lang/StableValue.java +++ b/src/java.base/share/classes/jdk/internal/lang/StableValue.java @@ -27,7 +27,11 @@ import jdk.internal.lang.stable.StableValueImpl; +import java.util.List; +import java.util.Map; import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.Set; import java.util.function.Function; import java.util.function.Supplier; @@ -72,34 +76,26 @@ * Supplier memoized = StableValues.memoizedSupplier(original, Thread.ofVirtual().factory()); * } *

- * A memoized IntFunction, for the allowed given {@code size} values {@code [0, size)} + * A memoized IntFunction, for the allowed given {@code size} input values {@code [0, size)} * and where the given {@code original} IntFunction is guaranteed to be successfully * invoked at most once per inout index even in a multithreaded environment, can be * created like this: - * {@snippet lang = java : - * static IntFunction memoizedIntFunction(int size, - * IntFunction original) { - * List> backing = StableValues.ofList(size); - * return i -> backing.get(i) - * .computeIfUnset(i, original::apply); - * } - * } - * A memoized Function, for the allowed given {@code input} values and where the + * {@snippet lang = java: + * IntFunction memoized = StableValues.memoizedIntFunction(size, original, null); + *} + * Just like a memoized supplier, a thread factory can be provided as a second parameter + * allowing all the values for the allowed input values to be computed by distinct + * background threads. + *

+ * A memoized Function, for the given set of allowed {@code inputs} and where the * given {@code original} function is guaranteed to be successfully invoked at most * once per input value even in a multithreaded environment, can be created like this: * {@snippet lang = java : - * static Function memoizedFunction(Set inputs, - * Function original) { - * Map> backing = StableValues.ofMap(keys); - * return t -> { - * if (!backing.containsKey(t)) { - * throw new IllegalArgumentException("Input not allowed: "+t); - * } - * return backing.get(t) - * .computeIfUnset(t, original); - * }; - * } + * Function memoized = StableValues.memoizedFunction(inputs, original, null); * } + * Just like a memoized supplier, a thread factory can be provided as a second parameter + * allowing all the values for the allowed input values to be computed by distinct + * background threads. *

* The constructs above are eligible for similar JVM optimizations as the StableValue * class itself. @@ -132,6 +128,7 @@ public sealed interface StableValue permits StableValueImpl { + // Principal methods /** @@ -244,7 +241,7 @@ public sealed interface StableValue *

* If this method returns without throwing an Exception, a holder value is always set. * - * @param input context to be applied to the {@code mapper} + * @param input context to be applied to the {@code mapper} (nullable) * @param mapper the mapper to be used to compute a holder value * @param The type of the {@code input} context * @return the current (existing or computed) holder value associated with @@ -270,8 +267,7 @@ default void setOrThrow(T value) { } } - - // Factory + // Factories /** * {@return a fresh stable value with an unset holder value} @@ -282,4 +278,57 @@ static StableValue newInstance() { return StableValueImpl.newInstance(); } + /** + * {@return a shallowly immutable, stable List of distinct fresh stable values} + *

+ * The method is equivalent to the following for a given non-negative {@code size}: + * {@snippet lang = java : + * List> list = Stream.generate(StableValue::newInstance) + * .limit(size) + * .toList(); + * } + * but may be more efficient. + * + * @param size the size of the returned list + * @param the {@code StableValue}s' element type + */ + static List> ofList(int size) { + if (size < 0) { + throw new IllegalArgumentException(); + } + @SuppressWarnings("unchecked") + final var stableValues = (StableValue[]) new StableValue[size]; + for (int i = 0; i < size; i++) { + stableValues[i] = newInstance(); + } + return List.of(stableValues); + } + + /** + * {@return a shallowly immutable, stable Map with the provided {@code keys} + * and associated distinct fresh stable values} + *

+ * The method is equivalent to the following for a given non-null set of {@code keys}: + * {@snippet lang = java : + * Map> map = keys.stream() + * .collect(Collectors.toMap( + * Function.identity(), _ -> StableValue.newInstance())); + * } + * but may be more efficient. + * + * @param keys the keys in the {@code Map} + * @param the {@code Map}'s key type + * @param the StableValue's type for the {@code Map}'s value type + */ + static Map> ofMap(Set keys) { + Objects.requireNonNull(keys); + @SuppressWarnings("unchecked") + final var entries = (Map.Entry>[]) new Map.Entry[keys.size()]; + int i = 0; + for (K key : keys) { + entries[i++] = Map.entry(key, newInstance()); + } + return Map.ofEntries(entries); + } + } \ No newline at end of file diff --git a/src/java.base/share/classes/jdk/internal/lang/StableValues.java b/src/java.base/share/classes/jdk/internal/lang/StableValues.java index e8ed58097c10c..68c5281566e6e 100644 --- a/src/java.base/share/classes/jdk/internal/lang/StableValues.java +++ b/src/java.base/share/classes/jdk/internal/lang/StableValues.java @@ -30,6 +30,8 @@ import java.util.Objects; import java.util.Set; import java.util.concurrent.ThreadFactory; +import java.util.function.Function; +import java.util.function.IntFunction; import java.util.function.Supplier; /** @@ -52,8 +54,8 @@ private StableValues() {} /** * {@return a new thread-safe, stable, lazily computed {@linkplain Supplier supplier} * that records the value of the provided {@code original} supplier upon being first - * accessed via {@linkplain Supplier#get()}, or via a background thread created from - * the provided {@code factory} (if non-null)} + * accessed via {@linkplain Supplier#get()}, or optionally via a background thread + * created from the provided {@code factory} (if non-null)} *

* The provided {@code original} supplier is guaranteed to be successfully invoked * at most once even in a multi-threaded environment. Competing threads invoking the @@ -73,11 +75,11 @@ private StableValues() {} * * @param original supplier used to compute a memoized value * @param factory an optional factory that, if non-null, will be used to create - * a background thread that will compute the memoized value. If the - * factory is {@code null}, no background thread will be started. + * a background thread that will attempt to compute the memoized + * value. If the factory is {@code null}, no background thread will + * be created. * @param the type of results supplied by the returned supplier */ - public static Supplier memoizedSupplier(Supplier original, ThreadFactory factory) { Objects.requireNonNull(original); @@ -103,52 +105,136 @@ record MemoizedSupplier(StableValue stable, } /** - * {@return a shallowly immutable, stable List of distinct fresh stable values} + * {@return a new thread-safe, stable, lazily computed {@linkplain IntFunction} + * that, for each allowed input, records the values of the provided {@code original} + * IntFunction upon being first accessed via {@linkplain IntFunction#apply(int)}, or + * optionally via background threads created from the provided {@code factory} + * (if non-null)} + *

+ * The provided {@code original} IntFunction is guaranteed to be successfully invoked + * at most once per allowed input, even in a multi-threaded environment. Competing + * threads invoking the {@linkplain IntFunction#apply(int)} method when a value is + * already under computation will block until a value is computed or an exception is + * thrown by the computing thread. + *

+ * If the {@code original} IntFunction invokes the returned IntFunction recursively + * for a particular input value, a StackOverflowError will be thrown when the returned + * IntFunction's {@linkplain IntFunction#apply(int)} method is invoked. *

- * The method is equivalent to the following for a given non-negative {@code size}: - * {@snippet lang = java : - * List> list = Stream.generate(StableValue::newInstance) - * .limit(size) - * .toList(); - * } - * @param size the size of the returned list - * @param the {@code StableValue}s' element type + * If the provided {@code original} IntFunction throws an exception, it is relayed + * to the initial caller. If the memoized IntFunction is computed by a background + * thread, exceptions from the provided {@code original} IntFunction will be relayed + * to the background thread's {@linkplain Thread#getUncaughtExceptionHandler() + * uncaught exception handler}. + *

+ * The order in which background threads are started is unspecified. + * + * @param size the size of the allowed inputs in {@code [0, size)} + * @param original IntFunction used to compute a memoized value + * @param factory an optional factory that, if non-null, will be used to create + * {@code size} background threads that will attempt to compute all + * the memoized values. If the provided factory is {@code null}, no + * background threads will be created. + * @param the type of results delivered by the returned IntFunction */ - public static List> ofList(int size) { + public static IntFunction memoizedIntFunction(int size, + IntFunction original, + ThreadFactory factory) { if (size < 0) { throw new IllegalArgumentException(); } - @SuppressWarnings("unchecked") - final var stableValues = (StableValue[]) new StableValue[size]; - for (int i = 0; i < size; i++) { - stableValues[i] = StableValue.newInstance(); + Objects.requireNonNull(original); + // `factory` is nullable + + final List> backing = StableValue.ofList(size); + + // A record provides better debug capabilities than a lambda + record MemoizedIntFunction(List> stables, + IntFunction original) implements IntFunction { + @Override public R apply(int value) { return stables.get(value) + .mapIfUnset(value, original::apply); } + } + + final IntFunction memoized = new MemoizedIntFunction<>(backing, original); + + if (factory != null) { + for (int i = 0; i < size; i++) { + final int input = i; + final Thread thread = factory.newThread(new Runnable() { + @Override public void run() { memoized.apply(input); } + }); + thread.start(); + } } - return List.of(stableValues); + + return memoized; } /** - * {@return a shallowly immutable, stable Map with the provided {@code keys} - * and associated distinct fresh stable values} + * {@return a new thread-safe, stable, lazily computed {@linkplain Function} + * that, for each allowed input in the given set of {@code inputs}, records the + * values of the provided {@code original} Function upon being first accessed via + * {@linkplain Function#apply(Object)}, or optionally via background threads created + * from the provided {@code factory} (if non-null)} *

- * The method is equivalent to the following for a given non-null set of {@code keys}: - * {@snippet lang = java : - * Map> map = keys.stream() - * .collect(Collectors.toMap( - * Function.identity(), _ -> StableValue.newInstance())); - * } - * @param keys the keys in the {@code Map} - * @param the {@code Map}'s key type - * @param the StableValue's type for the {@code Map}'s value type + * The provided {@code original} Function is guaranteed to be successfully invoked + * at most once per allowed input, even in a multi-threaded environment. Competing + * threads invoking the {@linkplain Function#apply(Object)} method when a value is + * already under computation will block until a value is computed or an exception is + * thrown by the computing thread. + *

+ * If the {@code original} Function invokes the returned Function recursively + * for a particular input value, a StackOverflowError will be thrown when the returned + * IntFunction's {@linkplain IntFunction#apply(int)} method is invoked. + *

+ * If the provided {@code original} Function throws an exception, it is relayed + * to the initial caller. If the memoized Function is computed by a background + * thread, exceptions from the provided {@code original} Function will be relayed to + * the background thread's {@linkplain Thread#getUncaughtExceptionHandler() uncaught + * exception handler}. + *

+ * The order in which background threads are started is unspecified. + * + * @param inputs the set of allowed input values + * @param original Function used to compute a memoized value + * @param factory an optional factory that, if non-null, will be used to create + * {@code size} background threads that will attempt to compute the + * memoized values. If the provided factory is {@code null}, no + * background threads will be created. + * @param the type of results delivered by the returned Function */ - public static Map> ofMap(Set keys) { - Objects.requireNonNull(keys); - @SuppressWarnings("unchecked") - final var entries = (Map.Entry>[]) new Map.Entry[keys.size()]; - int i = 0; - for (K key : keys) { - entries[i++] = Map.entry(key, StableValue.newInstance()); + public static Function memoizedFunction(Set inputs, + Function original, + ThreadFactory factory) { + Objects.requireNonNull(inputs); + + final Map> backing = StableValue.ofMap(inputs); + + // A record provides better debug capabilities than a lambda + record MemoizedFunction(Map> stables, + Function original) implements Function { + @Override + public R apply(T value) { + StableValue stable = MemoizedFunction.this.stables.get(value); + if (stable == null) { + throw new IllegalArgumentException("Input not allowed: " + value); + } + return stable.mapIfUnset(value, original); + } + } + + final Function memoized = new MemoizedFunction<>(backing, original); + + if (factory != null) { + for (final T t : inputs) { + final Thread thread = factory.newThread(new Runnable() { + @Override public void run() { memoized.apply(t); } + }); + thread.start(); + } } - return Map.ofEntries(entries); + + return memoized; } } diff --git a/test/jdk/java/lang/StableValue/JEP.md b/test/jdk/java/lang/StableValue/JEP.md index cb83a26a1d2a9..b4a90925e50a9 100644 --- a/test/jdk/java/lang/StableValue/JEP.md +++ b/test/jdk/java/lang/StableValue/JEP.md @@ -16,6 +16,8 @@ _at most once_. Stable Values offer the performance and safety benefits of final - It is not a goal to provide additional language support for expressing lazy computation. This might be the subject of a future JEP. +- It is not a goal to provide lazy collections such as a lazy `List` or a lazy `Map`. +This might be the subject of a future JEP. - It is not a goal to prevent or deprecate existing idioms for expressing lazy initialization. ## Motivation @@ -211,8 +213,8 @@ The Stable Values API defines functions and an interface so that client code in - Define and use stable (scalar) values: - [`StableValue.newInstance()`](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/java/lang/StableValue.html) - Define collections: - - [`StableValues.ofList(int size)`](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/java/lang/StableValues.html#ofList(int)) - - [`StableValues.ofMap(Set keys)`](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/java/lang/StableValues.html#ofMap(java.util.Set)) + - [`StableValue.ofList(int size)`](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/java/lang/StableValue.html#ofList(int)) + - [`StableValue.ofMap(Set keys)`](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/java/lang/StableValue.html#ofMap(java.util.Set)) The Stable Values API resides in the [java.lang](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/java/lang/package-summary.html) package of the [java.base](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/module-summary.html) module. @@ -290,7 +292,7 @@ Like a `StableValue` object, a `List` of stable value elements is created via a providing the size of the desired `List`: ``` -static List> StableValues.ofList(int size) { ... } +static List> StableValue.ofList(int size) { ... } ``` This allows for improving the handling of lists with stable values and enables a much better @@ -303,7 +305,7 @@ class ErrorMessages { private static final int SIZE = 8; // 1. Declare a stable list of default error pages to serve up - private static final List> MESSAGES = StableValues.ofList(SIZE); + private static final List> MESSAGES = StableValue.ofList(SIZE); // 2. Define a function that is to be called the first // time a particular message number is referenced @@ -360,7 +362,7 @@ class MapDemo { // 1. Declare a stable map of loggers with two allowable keys: // "com.foo.Bar" and "com.foo.Baz" static final Map> LOGGERS = - StableValues.ofMap(Set.of("com.foo.Bar", "com.foo.Baz")); + StableValue.ofMap(Set.of("com.foo.Bar", "com.foo.Baz")); // 2. Access the memoized map with as-declared-final performance // (evaluation made before the first access) @@ -394,30 +396,25 @@ reused for subsequent calls with recurring input values. Here is how we could m ``` class Memoized { - // 1. Declare a map with stable values - private static final Map> MAP = - StableValues.ofMap(Set.of("com.foo.Bar", "com.foo.Baz")); - - // 2. Declare a memoized (cached) function backed by the stable map + // 1. Declare a memoized (cached) function backed by a stable map private static final Function LOGGERS = - n -> MAP.get(n).mapIfUnset(n , Logger::getLogger); - - ... + StableValues.memoizedFunction(Set.of("com.foo.Bar", "com.foo.Baz"), + Logger::getLogger, null); private static final String NAME = "com.foo.Baz"; - // 3. Access the memoized value via the function with as-declared-final + // 2. Access the memoized value via the function with as-declared-final // performance (evaluation made before the first access) Logger logger = LOGGERS.apply(NAME); } ``` +Note: the last `null` parameter signifies an optional thread factory that will be explained later on. In the example above, for each key, the function is invoked at most once per loading of the containing class -`MapDemo` (`MapDemo`, in turn, can be loaded at most once into any given `ClassLoader`) as it is backed by a -`Map` with lazily computed values which upholds the invoke-at-most-once-per-key invariant. +`Memoized` (`Memoized`, in turn, can be loaded at most once into any given `ClassLoader`) and it is backed by a +_stable map_ with lazily computed values which upholds the invoke-at-most-once-per-key invariant. -It should be noted that the enumerated collection of keys given at creation time constitutes the only valid -input keys for the memoized function. +It should be noted that the enumerated set of valid inputs given at creation time constitutes the only valid input keys for the memoized function. Similarly to how a `Function` can be memoized using a backing lazily computed map, the same pattern can be used for an `IntFunction` that will record its cached value in a backing _stable list_: @@ -425,29 +422,25 @@ can be used for an `IntFunction` that will record its cached value in a backing ``` class ErrorMessages { - // 1. Declare a stable list of default error pages to serve up - private static final List> ERROR_PAGES = - StableValues.ofList(SIZE); + private static final int SIZE = 8; - // 2. Declare a memoized IntFunction backed by the stable list + // 1. Declare a memoized IntFunction backed by a stable list private static final IntFunction ERROR_FUNCTION = - i -> ERROR_PAGES.get(i).mapIfUnset(i , ErrorMessages::readFromFile); + StableValues.memoizedIntFunction(SIZE, ErrorMessages::readFromFile, null); - // 3. Define a function that is to be called the first + // 2. Define a function that is to be called the first // time a particular message number is referenced private static String readFromFile(int messageNumber) { try { - return Files.readString(Path.of("message-" + messageNumber + ".html")); + return Files.readString(Path.of("message-" + messageNumber + ".html")); } catch (IOException e) { throw new UncheckedIOException(e); } } - ... - - // 4. Access the memoized list element with as-declared-final performance + // 3. Access the memoized list element with as-declared-final performance // (evaluation made before the first access) - String msg = ERROR_FUNCTION.apply(2); + String msg = ERROR_FUNCTION.apply(2); // // @@ -457,28 +450,35 @@ class ErrorMessages { } ``` -The same paradigm can be used for creating a memoized `Supplier` (backed by a single `StableValue` instance) or -a memoized `Predicate`(backed by a lazily computed `Map>`). An astute reader will be able -to write such constructs in a few lines. +The same paradigm can be used for creating a memoized `Supplier` (backed by a single `StableValue` instance) via the `StableValues::memoizedSupplier` factory. Additional memoized functional constructs, such a memoized `Predicate`(backed by a lazily computed `Map>`) can be custom made. An astute reader will be able to write such constructs in a few lines. -An advantage with memoized functions, compared to working directly with StableValues, is that the initialization logic +An advantage with memoized functions, compared to working directly with StableValue instances, is that the initialization logic can be centralized and maintained in a single place, usually at the same place where the memoized function is defined. -The StableValues API offers yet another factory for memoized suppliers that will invoke a provided `original` -supplier at most once (if successful) and also allows an optional `threadFactory` to be provided from which -a new value-computing background thread will be created: +As noted above, the memoized factories in the StableValues API offers an optional tailing thread factory parameter from which new value-computing background threads will be created: ``` -static final Supplier MEMOIZED = StableValues.memoizedSupplier(original, Thread.ofVirtual().factory()); +// 1. Centrally declare a memoized (cached) function backed by a stable map +// computed in the background by two distinct virtual threads. +private static final Function LOGGERS = + StableValues.memoizedFunction(Set.of("com.foo.Bar", "com.foo.Baz"), + Logger::getLogger, + Thread.ofVirtual().factory()); // Create cheap virtual threads for background computation + +private static final String NAME = "com.foo.Baz"; + +// 2. Access the memoized value via the function with as-declared-final +// performance (evaluation already started and perhaps even completed before the first access) +Logger logger = LOGGERS.apply(NAME); ``` -This can provide a best-of-several-worlds situation where the memoized supplier can be quickly defined (as no -computation is made by the defining thread), the holder value is computed in a background thread (thus neither +This can provide a best-of-several-worlds situation where the memoized function can be quickly defined (as no +computation is made by the defining thread), the holder value is computed in background threads (thus neither interfering significantly with the critical startup path nor with future accessing threads), and the threads actually accessing the holder value can access the holder value with as-if-final performance and without having to compute -a holder value. This is true under the assumption, that the background thread can complete computation before accessing +a holder value. This is true under the assumption, that the background threads can complete computation before accessing threads requires a holder value. If this is not the case, at least some reduction of blocking time can be enjoyed as -the background thread has a head start compared to the accessing threads. +the background threads have had a head start compared to the accessing threads. ## Alternatives diff --git a/test/jdk/java/lang/StableValue/JepTest.java b/test/jdk/java/lang/StableValue/JepTest.java index 3b473d95b357e..ad26273378eb5 100644 --- a/test/jdk/java/lang/StableValue/JepTest.java +++ b/test/jdk/java/lang/StableValue/JepTest.java @@ -76,7 +76,7 @@ class ErrorMessages { private static final int SIZE = 8; // 1. Declare a stable list of default error pages to serve up - private static final List> MESSAGES = StableValues.ofList(SIZE); + private static final List> MESSAGES = StableValue.ofList(SIZE); // 2. Define a function that is to be called the first // time a particular message number is referenced @@ -102,7 +102,7 @@ class MapDemo { // 1. Declare a stable map of loggers with two allowable keys: // "com.foo.Bar" and "com.foo.Baz" static final Map> LOGGERS = - StableValues.ofMap(Set.of("com.foo.Bar", "com.foo.Baz")); + StableValue.ofMap(Set.of("com.foo.Bar", "com.foo.Baz")); // 2. Access the memoized map with as-declared-final performance // (evaluation made before the first access) @@ -114,18 +114,14 @@ static Logger logger(String name) { class Memoized { - // 1. Declare a map with stable values - private static final Map> MAP = - StableValues.ofMap(Set.of("com.foo.Bar", "com.foo.Baz")); - - // 2. Declare a memoized (cached) function backed by the stable map + // 1. Declare a memoized (cached) function backed by a stable map private static final Function LOGGERS = - n -> MAP.get(n).mapIfUnset(n , Logger::getLogger); - + StableValues.memoizedFunction(Set.of("com.foo.Bar", "com.foo.Baz"), + Logger::getLogger, null); private static final String NAME = "com.foo.Baz"; - // 3. Access the memoized value via the function with as-declared-final + // 2. Access the memoized value via the function with as-declared-final // performance (evaluation made before the first access) Logger logger = LOGGERS.apply(NAME); } @@ -136,17 +132,12 @@ class A { class ErrorMessages { private static final int SIZE = 8; - - // 1. Declare a stable list of default error pages to serve up - private static final List> ERROR_PAGES = - StableValues.ofList(SIZE); - - // 2. Declare a memoized IntFunction backed by the stable list + // 1. Declare a memoized IntFunction backed by a stable list private static final IntFunction ERROR_FUNCTION = - i -> ERROR_PAGES.get(i).mapIfUnset(i, ErrorMessages::readFromFile); + StableValues.memoizedIntFunction(SIZE, ErrorMessages::readFromFile, null); - // 3. Define a function that is to be called the first -// time a particular message number is referenced + // 2. Define a function that is to be called the first + // time a particular message number is referenced private static String readFromFile(int messageNumber) { try { return Files.readString(Path.of("message-" + messageNumber + ".html")); @@ -155,8 +146,8 @@ private static String readFromFile(int messageNumber) { } } - // 4. Access the memoized list element with as-declared-final performance -// (evaluation made before the first access) + // 3. Access the memoized list element with as-declared-final performance + // (evaluation made before the first access) String msg = ERROR_FUNCTION.apply(2); } } diff --git a/test/jdk/java/lang/StableValue/StableValuesTest.java b/test/jdk/java/lang/StableValue/StableValuesTest.java index b33f701bace78..900264e619b07 100644 --- a/test/jdk/java/lang/StableValue/StableValuesTest.java +++ b/test/jdk/java/lang/StableValue/StableValuesTest.java @@ -77,7 +77,7 @@ public Thread newThread(Runnable r) { @Test void ofList() { - List> list = StableValues.ofList(13); + List> list = StableValue.ofList(13); assertEquals(13, list.size()); // Check, every StableValue is distinct Map, Boolean> idMap = new IdentityHashMap<>(); @@ -87,7 +87,7 @@ void ofList() { @Test void ofMap() { - Map> map = StableValues.ofMap(Set.of(1, 2, 3)); + Map> map = StableValue.ofMap(Set.of(1, 2, 3)); assertEquals(3, map.size()); // Check, every StableValue is distinct Map, Boolean> idMap = new IdentityHashMap<>(); From 17501ccca1e531906c069f79acfbe5f4f1714c47 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 24 Jun 2024 13:28:25 +0200 Subject: [PATCH 042/327] Add tests --- .../java/lang/StableValue/StableTestUtil.java | 17 ++++ .../lang/StableValue/StableValuesTest.java | 84 +++++++++++++++++-- 2 files changed, 93 insertions(+), 8 deletions(-) diff --git a/test/jdk/java/lang/StableValue/StableTestUtil.java b/test/jdk/java/lang/StableValue/StableTestUtil.java index 8809e1d188c3d..a37c63b744680 100644 --- a/test/jdk/java/lang/StableValue/StableTestUtil.java +++ b/test/jdk/java/lang/StableValue/StableTestUtil.java @@ -23,6 +23,7 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; +import java.util.function.IntFunction; import java.util.function.Supplier; final class StableTestUtil { @@ -45,6 +46,22 @@ public T get() { } + public static final class CountingIntFunction + extends AbstractCounting> + implements IntFunction { + + public CountingIntFunction(IntFunction delegate) { + super(delegate); + } + + @Override + public R apply(int value) { + incrementCounter(); + return delegate.apply(value); + } + + } + public static final class CountingFunction extends AbstractCounting> implements Function { diff --git a/test/jdk/java/lang/StableValue/StableValuesTest.java b/test/jdk/java/lang/StableValue/StableValuesTest.java index 900264e619b07..942d26f794bc5 100644 --- a/test/jdk/java/lang/StableValue/StableValuesTest.java +++ b/test/jdk/java/lang/StableValue/StableValuesTest.java @@ -38,25 +38,29 @@ import java.util.Set; import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; +import java.util.function.IntFunction; import java.util.function.Supplier; import static org.junit.jupiter.api.Assertions.*; final class StableValuesTest { + private static final int SIZE = 2; + @Test - void ofMemoized() { + void memoizedSupplier() { StableTestUtil.CountingSupplier cs = new StableTestUtil.CountingSupplier<>(() -> 42); - Supplier memoizeded = StableValues.memoizedSupplier(cs, null); - assertEquals(42, memoizeded.get()); + Supplier memoized = StableValues.memoizedSupplier(cs, null); + assertEquals(42, memoized.get()); assertEquals(1, cs.cnt()); - assertEquals(42, memoizeded.get()); + assertEquals(42, memoized.get()); assertEquals(1, cs.cnt()); - assertEquals("MemoizedSupplier[stable=StableValue[42], original=" + cs + "]", memoizeded.toString()); + assertEquals("MemoizedSupplier[stable=StableValue[42], original=" + cs + "]", memoized.toString()); } @Test - void ofMemoizedBackground() { + void memoizedSupplierBackground() { final AtomicInteger cnt = new AtomicInteger(0); ThreadFactory factory = new ThreadFactory() { @@ -68,11 +72,75 @@ public Thread newThread(Runnable r) { }); } }; - Supplier memoizeded = StableValues.memoizedSupplier(() -> 42, factory); + Supplier memoized = StableValues.memoizedSupplier(() -> 42, factory); while (cnt.get() < 1) { Thread.onSpinWait(); } - assertEquals(42, memoizeded.get()); + assertEquals(42, memoized.get()); + } + + @Test + void memoizedIntFunction() { + StableTestUtil.CountingIntFunction cif = new StableTestUtil.CountingIntFunction<>(i -> i); + IntFunction memoized = StableValues.memoizedIntFunction(SIZE, cif, null); + assertEquals(1, memoized.apply(1)); + assertEquals(1, cif.cnt()); + assertEquals(1, memoized.apply(1)); + assertEquals(1, cif.cnt()); + assertEquals("MemoizedIntFunction[stables=[StableValue.unset, StableValue[1]], original=" + cif + "]", memoized.toString()); + } + + @Test + void memoizedIntFunctionBackground() { + + final AtomicInteger cnt = new AtomicInteger(0); + ThreadFactory factory = new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + return new Thread(() -> { + r.run(); + cnt.incrementAndGet(); + }); + } + }; + IntFunction memoized = StableValues.memoizedIntFunction(SIZE, i -> i, factory); + while (cnt.get() < 2) { + Thread.onSpinWait(); + } + assertEquals(0, memoized.apply(0)); + assertEquals(1, memoized.apply(1)); + } + + @Test + void memoizedFunction() { + StableTestUtil.CountingFunction cif = new StableTestUtil.CountingFunction<>(i -> i); + Function memoized = StableValues.memoizedFunction(Set.of(13, 42), cif, null); + assertEquals(42, memoized.apply(42)); + assertEquals(1, cif.cnt()); + assertEquals(42, memoized.apply(42)); + assertEquals(1, cif.cnt()); + assertEquals("MemoizedFunction[stables={13=StableValue.unset, 42=StableValue[42]}, original=" + cif + "]", memoized.toString()); + } + + @Test + void memoizedFunctionBackground() { + + final AtomicInteger cnt = new AtomicInteger(0); + ThreadFactory factory = new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + return new Thread(() -> { + r.run(); + cnt.incrementAndGet(); + }); + } + }; + Function memoized = StableValues.memoizedFunction(Set.of(13, 42), i -> i, factory); + while (cnt.get() < 2) { + Thread.onSpinWait(); + } + assertEquals(42, memoized.apply(42)); + assertEquals(13, memoized.apply(13)); } @Test From 11d1217022e6c05abd2dd02c45aa52b95326edef Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 24 Jun 2024 14:40:51 +0200 Subject: [PATCH 043/327] Move tests and add multi-dimensional list tests --- .../lang/StableValue/StableValueTest.java | 89 +++++++++++++++++++ .../lang/StableValue/StableValuesTest.java | 26 ++---- 2 files changed, 94 insertions(+), 21 deletions(-) diff --git a/test/jdk/java/lang/StableValue/StableValueTest.java b/test/jdk/java/lang/StableValue/StableValueTest.java index f1adda60f033d..cb521bc64fbab 100644 --- a/test/jdk/java/lang/StableValue/StableValueTest.java +++ b/test/jdk/java/lang/StableValue/StableValueTest.java @@ -32,8 +32,11 @@ import org.junit.jupiter.api.Test; import java.util.BitSet; +import java.util.IdentityHashMap; import java.util.List; +import java.util.Map; import java.util.NoSuchElementException; +import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.LockSupport; @@ -173,6 +176,92 @@ void testEquals() { assertNotEquals(s0, "a"); } + @Test + void ofList() { + List> list = StableValue.ofList(13); + assertEquals(13, list.size()); + // Check, every StableValue is distinct + Map, Boolean> idMap = new IdentityHashMap<>(); + list.forEach(e -> idMap.put(e, true)); + assertEquals(13, idMap.size()); + } + + @Test + void ofList3DimLazy() { + int[] dims = new int[]{2, 3, 4}; + + List>>>>> list3d = StableValue.ofList(dims[0]); + + int[] indices = new int[]{1, 2, 3}; + + int element = from3dLazy(list3d, dims, indices) + .computeIfUnset(() -> 42); + + assertEquals(42, element); + assertEquals(42, from3dLazy(list3d, dims, indices).orElseThrow()); + } + + private StableValue from3dLazy( + List>>>>> list3d, + int[] dims, int[] indices) { + return list3d + .get(indices[0]).mapIfUnset(dims[1], StableValue::ofList) + .get(indices[1]).mapIfUnset(dims[2], StableValue::ofList) + .get(indices[2]); + } + + @Test + void ofList3Dim() { + int[] dims = new int[]{2, 3, 4}; + + List>>>>> list3d = create3d(dims); + + int[] indices = new int[]{1, 2, 3}; + + int element = from3d(list3d, indices) + .computeIfUnset(() -> 42); + + assertEquals(42, element); + assertEquals(42, from3d(list3d, indices).orElseThrow()); + } + + private List>>>>> create3d( + int[] dims) { + + List>>>>> list3d = + StableValue.ofList(dims[0]); + for (int d0 = 0; d0 < dims[0] ; d0++) { + List>>> list2d = StableValue.ofList(dims[1]); + list3d.get(d0).setOrThrow(list2d); + for (int d1 = 0; d1 < dims[1]; d1++) { + List> list = StableValue.ofList(dims[2]); + list2d.get(d1).setOrThrow(list); + } + } + return list3d; + } + + private StableValue from3d( + List>>>>> list3d, int[] indices) { + return list3d + .get(indices[0]).orElseThrow() + .get(indices[1]).orElseThrow() + .get(indices[2]); + } + + + @Test + void ofMap() { + Map> map = StableValue.ofMap(Set.of(1, 2, 3)); + assertEquals(3, map.size()); + // Check, every StableValue is distinct + Map, Boolean> idMap = new IdentityHashMap<>(); + map.forEach((k, v) -> idMap.put(v, true)); + assertEquals(3, idMap.size()); + } + private static final BiPredicate, Integer> TRY_SET = StableValue::trySet; private static final BiPredicate, Integer> SET_OR_THROW = (s, i) -> { try { diff --git a/test/jdk/java/lang/StableValue/StableValuesTest.java b/test/jdk/java/lang/StableValue/StableValuesTest.java index 942d26f794bc5..d04bc17afa47f 100644 --- a/test/jdk/java/lang/StableValue/StableValuesTest.java +++ b/test/jdk/java/lang/StableValue/StableValuesTest.java @@ -119,7 +119,11 @@ void memoizedFunction() { assertEquals(1, cif.cnt()); assertEquals(42, memoized.apply(42)); assertEquals(1, cif.cnt()); - assertEquals("MemoizedFunction[stables={13=StableValue.unset, 42=StableValue[42]}, original=" + cif + "]", memoized.toString()); + assertTrue(memoized.toString().startsWith("MemoizedFunction[stables={")); + // Key order is unspecified + assertTrue(memoized.toString().contains("13=StableValue.unset")); + assertTrue(memoized.toString().contains("42=StableValue[42]")); + assertTrue(memoized.toString().endsWith(", original=" + cif + "]")); } @Test @@ -143,24 +147,4 @@ public Thread newThread(Runnable r) { assertEquals(13, memoized.apply(13)); } - @Test - void ofList() { - List> list = StableValue.ofList(13); - assertEquals(13, list.size()); - // Check, every StableValue is distinct - Map, Boolean> idMap = new IdentityHashMap<>(); - list.forEach(e -> idMap.put(e, true)); - assertEquals(13, idMap.size()); - } - - @Test - void ofMap() { - Map> map = StableValue.ofMap(Set.of(1, 2, 3)); - assertEquals(3, map.size()); - // Check, every StableValue is distinct - Map, Boolean> idMap = new IdentityHashMap<>(); - map.forEach((k, v) -> idMap.put(v, true)); - assertEquals(3, idMap.size()); - } - } From 02bb518b5dfbad9a07c3caef41eaa9dfb1b94706 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 24 Jun 2024 15:35:34 +0200 Subject: [PATCH 044/327] Use an on-demand mutex field --- .../internal/lang/stable/StableValueImpl.java | 54 +++++++++++++++++-- 1 file changed, 49 insertions(+), 5 deletions(-) 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 d178d38d0ab94..fcef96fcb0c61 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 @@ -38,12 +38,21 @@ public final class StableValueImpl implements StableValue { + // Unsafe allows StableValue to be used early in the boot sequence private static final Unsafe UNSAFE = Unsafe.getUnsafe(); + + // Used to indicate a holder value is `null` (see field `value` below) + // A wrapper method `nullSentinel()` is used for generic type conversion. private static final Object NULL_SENTINEL = new Object(); + + // Used to indicate a mutex is never needed anymore. + private static final Object TOMBSTONE = new Object(); + + // Unsafe offsets for direct access private static final long VALUE_OFFSET = UNSAFE.objectFieldOffset(StableValueImpl.class, "value"); - - private final Object mutex = new Object(); + private static final long MUTEX_OFFSET = + UNSAFE.objectFieldOffset(StableValueImpl.class, "mutex"); // Generally, fields annotated with `@Stable` are accessed by the JVM using special // memory semantics rules (see `parse.hpp` and `parse(1|2|3).cpp`). @@ -58,6 +67,12 @@ public final class StableValueImpl implements StableValue { @Stable private T value; + // This field is lazily initialized on demand and when it is no longer needed + // (i.e. when a holder value is set) the TOMBSTONE singleton is stored to allow + // the previous, now-redundant mutex object to be collected. + private volatile Object mutex; + + // Only allow creation via a factory private StableValueImpl() {} @ForceInline @@ -66,15 +81,23 @@ public boolean trySet(T value) { if (value() != null) { return false; } - synchronized (mutex) { + Object m = acquireMutex(); + if (m == TOMBSTONE) { + // A holder value must be set as a holder value store + // happens before a mutex TOMBSTONE store + return false; + } + synchronized (m) { // Updates to the `value` is always made under `mutex` synchronization // meaning plain memory semantics is enough here. if (valuePlain() != null) { return false; } set0(value); - return true; + // The holder value store must happen before the mutex release + releaseMutex(); } + return true; } @ForceInline @@ -143,7 +166,13 @@ public T mapIfUnset(I input, Function function) { @SuppressWarnings("unchecked") @DontInline private T tryCompute(I input, Object provider) { - synchronized (mutex) { + Object m = acquireMutex(); + if (m == TOMBSTONE) { + // A holder value must be set as a holder value store + // happens before a mutex TOMBSTONE store + return unwrap(value()); + } + synchronized (m) { // Updates to the `value` is always made under `mutex` synchronization // meaning plain memory semantics is enough here. T t = valuePlain(); @@ -156,6 +185,8 @@ private T tryCompute(I input, Object provider) { t = ((Function) provider).apply(input); } set0(t); + // The holder value store must happen before the mutex release + releaseMutex(); return t; } } @@ -213,6 +244,19 @@ private static String render(T t) { return (t == null) ? ".unset" : "[" + unwrap(t) + "]"; } + private Object acquireMutex() { + if (mutex != null) { + // We already have a mutex + return mutex; + } + Object newMutex = new Object(); + Object witness = UNSAFE.compareAndExchangeReference(this, MUTEX_OFFSET, null, newMutex); + return witness == null ? newMutex : witness; + } + + private void releaseMutex() { + mutex = TOMBSTONE; + } // Factory public static StableValueImpl newInstance() { From 475d3cf880053bb4ec8a1962bad3ce7cc09541e2 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 25 Jun 2024 11:31:51 +0200 Subject: [PATCH 045/327] Improve comments --- .../internal/lang/stable/StableValueImpl.java | 48 ++++++++++--------- 1 file changed, 26 insertions(+), 22 deletions(-) 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 fcef96fcb0c61..f9ae5c1c74691 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 @@ -45,10 +45,10 @@ public final class StableValueImpl implements StableValue { // A wrapper method `nullSentinel()` is used for generic type conversion. private static final Object NULL_SENTINEL = new Object(); - // Used to indicate a mutex is never needed anymore. + // Used to indicate a mutex is not needed anymore. private static final Object TOMBSTONE = new Object(); - // Unsafe offsets for direct access + // Unsafe offsets for direct object access private static final long VALUE_OFFSET = UNSAFE.objectFieldOffset(StableValueImpl.class, "value"); private static final long MUTEX_OFFSET = @@ -59,20 +59,21 @@ public final class StableValueImpl implements StableValue { // // This field is reflectively accessed via Unsafe using explicit memory semantics. // - // Meaning Value + // Value Meaning // ------- ----- - // Unset: null - // Set(non-null): The set value (!= nullSentinel()) - // Set(null): nullSentinel() + // null Unset + // nullSentinel() Set(null) + // other Set(other) @Stable private T value; - // This field is lazily initialized on demand and when it is no longer needed - // (i.e. when a holder value is set) the TOMBSTONE singleton is stored to allow - // the previous, now-redundant mutex object to be collected. + // This field is initialized on demand to a new distinct mutex object. + // When synchronization is no longer needed (i.e. when a holder value is set), + // the field is set to the `TOMBSTONE` singleton object to allow the previous, + // now-redundant mutex object to be collected. private volatile Object mutex; - // Only allow creation via a factory + // Only allow creation via the factory `StableValueImpl::newInstance` private StableValueImpl() {} @ForceInline @@ -83,13 +84,13 @@ public boolean trySet(T value) { } Object m = acquireMutex(); if (m == TOMBSTONE) { - // A holder value must be set as a holder value store + // A holder value must already be set as a holder value store // happens before a mutex TOMBSTONE store return false; } synchronized (m) { - // Updates to the `value` is always made under `mutex` synchronization - // meaning plain memory semantics is enough here. + // The one-and-only update of the `value` field is always made under + // `mutex` synchronization meaning plain memory semantics is enough here. if (valuePlain() != null) { return false; } @@ -109,7 +110,9 @@ private void set0(T value) { // In other words, if a reader (using plain memory semantics) can observe a // `value` reference, any field updates made prior to this fence are // guaranteed to be seen. - UNSAFE.storeStoreFence(); // Redundant as a volatile put provides a store barrier? + // See https://gee.cs.oswego.edu/dl/html/j9mm.html "Mixed Modes and Specializations", + // Doug Lea, 2018 + UNSAFE.storeStoreFence(); // We are alone here under the `mutex` // This upholds the invariant, the `@Stable value` field is written to @@ -168,13 +171,13 @@ public T mapIfUnset(I input, Function function) { private T tryCompute(I input, Object provider) { Object m = acquireMutex(); if (m == TOMBSTONE) { - // A holder value must be set as a holder value store + // A holder value must already be set as a holder value store // happens before a mutex TOMBSTONE store return unwrap(value()); } synchronized (m) { - // Updates to the `value` is always made under `mutex` synchronization - // meaning plain memory semantics is enough here. + // The one-and-only update of the `value` field is always made under + // `mutex` synchronization meaning plain memory semantics is enough here. T t = valuePlain(); if (t != null) { return unwrap(t); @@ -199,7 +202,7 @@ public int hashCode() { @Override public boolean equals(Object obj) { return obj instanceof StableValueImpl other && - // Note that the returned value() will be `null` if the holder value + // Note that the returned `value()` will be `null` if the holder value // is unset and `nullSentinel()` if the holder value is `null`. Objects.equals(value(), other.value()); } @@ -224,12 +227,12 @@ private T valuePlain() { return value; } - // Wraps null values into a sentinel value + // Wraps `null` values into a sentinel value private static T wrap(T t) { return (t == null) ? nullSentinel() : t; } - // Unwraps null sentinel values into null + // Unwraps null sentinel values into `null` @ForceInline private static T unwrap(T t) { return t != nullSentinel() ? t : null; @@ -250,6 +253,7 @@ private Object acquireMutex() { return mutex; } Object newMutex = new Object(); + // Guarantees, only one distinct mutex object per StableValue is ever exposed. Object witness = UNSAFE.compareAndExchangeReference(this, MUTEX_OFFSET, null, newMutex); return witness == null ? newMutex : witness; } @@ -258,8 +262,8 @@ private void releaseMutex() { mutex = TOMBSTONE; } - // Factory - public static StableValueImpl newInstance() { + // Factory for creating new StableValue instances + public static StableValue newInstance() { return new StableValueImpl<>(); } From 4c9ef39220cb5b6de775229ae3d4b4c138416111 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 25 Jun 2024 19:04:25 +0200 Subject: [PATCH 046/327] Simplify StableValue and refactor dependent constructs --- .../jdk/internal/lang/StableValue.java | 418 +++++++++++------- .../jdk/internal/lang/StableValues.java | 240 ---------- .../internal/lang/stable/CachedFunction.java | 38 ++ .../lang/stable/CachedIntFunction.java | 33 ++ .../internal/lang/stable/CachedSupplier.java | 31 ++ .../jdk/internal/lang/stable/LazyList.java | 56 +++ .../jdk/internal/lang/stable/LazyMap.java | 96 ++++ .../internal/lang/stable/StableValueImpl.java | 97 ++-- test/jdk/java/lang/StableValue/JepTest.java | 55 +-- .../lang/StableValue/StableValueTest.java | 151 +------ .../lang/StableValue/StableValuesTest.java | 22 +- 11 files changed, 565 insertions(+), 672 deletions(-) delete mode 100644 src/java.base/share/classes/jdk/internal/lang/StableValues.java create mode 100644 src/java.base/share/classes/jdk/internal/lang/stable/CachedFunction.java create mode 100644 src/java.base/share/classes/jdk/internal/lang/stable/CachedIntFunction.java create mode 100644 src/java.base/share/classes/jdk/internal/lang/stable/CachedSupplier.java create mode 100644 src/java.base/share/classes/jdk/internal/lang/stable/LazyList.java create mode 100644 src/java.base/share/classes/jdk/internal/lang/stable/LazyMap.java diff --git a/src/java.base/share/classes/jdk/internal/lang/StableValue.java b/src/java.base/share/classes/jdk/internal/lang/StableValue.java index 5d7230d80a129..0478eb2c277a2 100644 --- a/src/java.base/share/classes/jdk/internal/lang/StableValue.java +++ b/src/java.base/share/classes/jdk/internal/lang/StableValue.java @@ -25,6 +25,10 @@ package jdk.internal.lang; +import jdk.internal.lang.stable.CachedFunction; +import jdk.internal.lang.stable.CachedIntFunction; +import jdk.internal.lang.stable.CachedSupplier; +import jdk.internal.lang.stable.LazyList; import jdk.internal.lang.stable.StableValueImpl; import java.util.List; @@ -32,16 +36,21 @@ import java.util.NoSuchElementException; import java.util.Objects; import java.util.Set; +import java.util.concurrent.ThreadFactory; import java.util.function.Function; +import java.util.function.IntFunction; import java.util.function.Supplier; /** - * A thin, atomic, thread-safe, set-at-most-once, stable value holder eligible for - * certain JVM optimizations if set to a value. + * A thin, atomic, non-blocking, thread-safe, set-at-most-once, stable value holder + * eligible for certain JVM optimizations if set to a value. *

* A stable value is said to be monotonic because the state of a stable value can only go * from unset to set and consequently, a value can only be set * at most once. + *

+ * StableValue is mainly intended to be a member of a holding class and is usually neither + * exposed directly via accessors nor passed as a method parameter. * * *

Factories

@@ -51,60 +60,57 @@ *

* The utility class {@linkplain StableValues} contains a number of convenience methods * for creating constructs involving StableValue: - *

- * A List of StableValue elements with a given {@code size} can be created the following way: - * {@snippet lang = java : - * List> list = StableValues.ofList(size); - * } - * The list can be used to model stable one-dimensional arrays. If two- or more - * dimensional arrays are to be modeled, a List of List of ... of StableValue can be used. - *

- * A Map with a given set of {@code keys} associated with StableValue objects can be - * created like this: - * {@snippet lang = java : - * Map> map = StableValues.ofMap(keys); - * } - * A memoized Supplier, where a given {@code original} Supplier is guaranteed to - * be successfully invoked at most once even in a multithreaded environment, can be - * created like this: + * + * A cached (also called "memoized") Supplier, where a given {@code original} + * Supplier is guaranteed to be successfully invoked at most once even in a multithreaded + * environment, can be created like this: * {@snippet lang = java : - * Supplier memoized = StableValues.memoizedSupplier(original, null); + * Supplier cached = StableValue.newCachedSupplier(original, null); * } - * The memoized supplier can also be lazily computed by a fresh background thread if a + * The cached supplier can also be lazily computed by a fresh background thread if a * thread factory is provided as a second parameter as shown here: * {@snippet lang = java : - * Supplier memoized = StableValues.memoizedSupplier(original, Thread.ofVirtual().factory()); + * Supplier cached = StableValue.newCachedSupplier(original, Thread.ofVirtual().factory()); * } *

- * A memoized IntFunction, for the allowed given {@code size} input values {@code [0, size)} - * and where the given {@code original} IntFunction is guaranteed to be successfully - * invoked at most once per inout index even in a multithreaded environment, can be - * created like this: + * A cached (also called "memoized") IntFunction, for the allowed given {@code size} + * input values {@code [0, size)} and where the given {@code original} IntFunction is + * guaranteed to be successfully invoked at most once per inout index even in a + * multithreaded environment, can be created like this: * {@snippet lang = java: - * IntFunction memoized = StableValues.memoizedIntFunction(size, original, null); + * IntFunction cached = StableValue.newCachedIntFunction(size, original, null); *} - * Just like a memoized supplier, a thread factory can be provided as a second parameter + * Just like a cached supplier, a thread factory can be provided as a second parameter * allowing all the values for the allowed input values to be computed by distinct * background threads. *

- * A memoized Function, for the given set of allowed {@code inputs} and where the - * given {@code original} function is guaranteed to be successfully invoked at most - * once per input value even in a multithreaded environment, can be created like this: + * A cached (also called "memoized") Function, for the given set of allowed {@code inputs} + * and where the given {@code original} function is guaranteed to be successfully invoked + * at most once per input value even in a multithreaded environment, can be created like + * this: * {@snippet lang = java : - * Function memoized = StableValues.memoizedFunction(inputs, original, null); + * Function cached = StableValue.newCachedFunction(inputs, original, null); * } - * Just like a memoized supplier, a thread factory can be provided as a second parameter + * Just like a cached supplier, a thread factory can be provided as a second parameter * allowing all the values for the allowed input values to be computed by distinct * background threads. *

- * The constructs above are eligible for similar JVM optimizations as the StableValue - * class itself. - * - * - *

Blocking

- * All methods that can set the stable value's holder value are guarded such that - * competing set operations (by other threads) will block if another set operation is - * already in progress. + * A lazy List of stable elements with a given {@code size} and given {@code mapper} can + * be created the following way: + * {@snippet lang = java : + * List lazyList = StableValue.lazyList(size, mapper); + * } + * The list can be used to model stable one-dimensional arrays. If two- or more + * dimensional arrays are to be modeled, a List of List of ... of E can be used. + *

+ * A Map with a given set of {@code keys} and given (@code mapper) associated with + * stable values can be created like this: + * {@snippet lang = java : + * Map lazyMap = StableValue.lazyMap(keys, mapper); + * } + *

+ * The constructs above are eligible for similar JVM optimizations as StableValue + * instances. * * *

Memory Consistency Properties

@@ -121,6 +127,15 @@ * Except for a StableValue's holder value itself, all method parameters must be * non-null or a {@link NullPointerException} will be thrown. * + * + * Implementations of this interface can be + * value-based + * classes; programmers should treat instances that are + * {@linkplain Object#equals(Object) equal} as interchangeable and should not + * use instances for synchronization, or unpredictable behavior may + * occur. For example, in a future release, synchronization may fail. + * The {@code equals} method should be used for comparisons. + * * @param type of the holder value * * @since 24 @@ -128,7 +143,6 @@ public sealed interface StableValue permits StableValueImpl { - // Principal methods /** @@ -161,94 +175,6 @@ public sealed interface StableValue */ boolean isSet(); - /** - * If the holder value is unset, attempts to compute the holder value using the - * provided {@code supplier} and enters the result into the holder value. - * - *

If the {@code supplier} itself throws an (unchecked) exception, the exception - * is rethrown, and no holder value is set. The most common usage is to construct a - * new object serving as an initial value or memoized result, as in: - * - * {@snippet lang = java : - * T t = stable.computeIfUnset(T::new); - * } - *

- * If this method returns without throwing an Exception, a holder value is always set. - * - * @implSpec - * The implementation of this method is equivalent to the following steps for this - * {@code stable} and a given non-null {@code supplier}: - * - * {@snippet lang = java : - * if (stable.isSet()) { - * return stable.orElseThrow(); - * } - * T newValue = supplier.get(); - * stable.trySet(newValue); - * return newValue; - * } - * Except, the method is atomic, thread-safe and guarded with synchronization - * with respect to this method and all other methods that can set this StableValue's - * holder value. - * - * @param supplier the supplier to be used to compute a holder value - * @return the current (existing or computed) holder value associated with - * this stable value - */ - T computeIfUnset(Supplier supplier); - - /** - * If the holder value is unset, attempts to compute the holder value using the - * provided {@code mapper} applied to the provided {@code input} context and enters - * the result into the holder value. - * - *

If the {@code mapper} itself throws an (unchecked) exception, the exception - * is rethrown, and no holder value is set. The most common usage is to construct a - * new object serving as an initial value or memoized result, as in: - * - * {@snippet lang = java : - * Map> map = StableValues.ofMap(...); - * K key = ...; - * T t = map.get(key) - * .computeIfUnset(key, Foo::valueFromKey); - * } - *

- * The method also allows static Functions/lambdas to be used, for example by - * providing `this` as an {@code input} and the static Function/lambda accessing - * properties of the `this` input. - *

- * This method can also be used to emulate a compare-and-exchange idiom for a - * given {@code stable} and {@code candidate} value, as in: - * {@snippet lang = java : - * T t = stable.computeIfUnset(candidate, Function.identity()); - * } - * - * @implSpec - * The implementation of this method is equivalent to the following steps for this - * {@code stable} and a given non-null {@code mapper} and {@code inout}: - * - * {@snippet lang = java : - * if (stable.isSet()) { - * return stable.orElseThrow(); - * } - * T newValue = mapper.apply(input); - * stable.trySet(newValue); - * return newValue; - * } - * Except, the method is atomic, thread-safe and guarded with synchronization - * with respect to this method and all other methods that can set this StableValue's - * holder value. - *

- * If this method returns without throwing an Exception, a holder value is always set. - * - * @param input context to be applied to the {@code mapper} (nullable) - * @param mapper the mapper to be used to compute a holder value - * @param The type of the {@code input} context - * @return the current (existing or computed) holder value associated with - * this stable value - */ - T mapIfUnset(I input, Function mapper); - // Convenience methods /** @@ -279,56 +205,214 @@ static StableValue newInstance() { } /** - * {@return a shallowly immutable, stable List of distinct fresh stable values} + * {@return a new caching, thread-safe, stable, lazily computed + * {@linkplain Supplier supplier} that records the value of the provided + * {@code original} supplier upon being first accessed via + * {@linkplain Supplier#get()}, or optionally via a background thread created from + * the provided {@code factory} (if non-null)} + *

+ * The provided {@code original} supplier is guaranteed to be successfully invoked + * at most once even in a multi-threaded environment. Competing threads invoking the + * {@linkplain Supplier#get()} method when a value is already under computation + * will block until a value is computed or an exception is thrown by the + * computing thread. + *

+ * If the {@code original} Supplier invokes the returned Supplier recursively, + * a StackOverflowError will be thrown when the returned + * Supplier's {@linkplain Supplier#get()} method is invoked. *

- * The method is equivalent to the following for a given non-negative {@code size}: - * {@snippet lang = java : - * List> list = Stream.generate(StableValue::newInstance) - * .limit(size) - * .toList(); - * } - * but may be more efficient. + * If the provided {@code original} supplier throws an exception, it is relayed + * to the initial caller. If the memoized supplier is computed by a background thread, + * exceptions from the provided {@code original} supplier will be relayed to the + * background thread's {@linkplain Thread#getUncaughtExceptionHandler() uncaught + * exception handler}. * - * @param size the size of the returned list - * @param the {@code StableValue}s' element type + * @param original supplier used to compute a memoized value + * @param factory an optional factory that, if non-null, will be used to create + * a background thread that will attempt to compute the memoized + * value. If the factory is {@code null}, no background thread will + * be created. + * @param the type of results supplied by the returned supplier */ - static List> ofList(int size) { + static Supplier newCachingSupplier(Supplier original, + ThreadFactory factory) { + Objects.requireNonNull(original); + // `factory` is nullable + + final Supplier memoized = CachedSupplier.of(original); + + if (factory != null) { + final Thread thread = factory.newThread(new Runnable() { + @Override + public void run() { + memoized.get(); + } + }); + thread.start(); + } + return memoized; + } + + /** + * {@return a new caching, thread-safe, stable, lazily computed + * {@linkplain IntFunction } that, for each allowed input, records the values of the + * provided {@code original} IntFunction upon being first accessed via + * {@linkplain IntFunction#apply(int)}, or optionally via background threads created + * from the provided {@code factory} (if non-null)} + *

+ * The provided {@code original} IntFunction is guaranteed to be successfully invoked + * at most once per allowed input, even in a multi-threaded environment. Competing + * threads invoking the {@linkplain IntFunction#apply(int)} method when a value is + * already under computation will block until a value is computed or an exception is + * thrown by the computing thread. + *

+ * If the {@code original} IntFunction invokes the returned IntFunction recursively + * for a particular input value, a StackOverflowError will be thrown when the returned + * IntFunction's {@linkplain IntFunction#apply(int)} method is invoked. + *

+ * If the provided {@code original} IntFunction throws an exception, it is relayed + * to the initial caller. If the memoized IntFunction is computed by a background + * thread, exceptions from the provided {@code original} IntFunction will be relayed + * to the background thread's {@linkplain Thread#getUncaughtExceptionHandler() + * uncaught exception handler}. + *

+ * The order in which background threads are started is unspecified. + * + * @param size the size of the allowed inputs in {@code [0, size)} + * @param original IntFunction used to compute a memoized value + * @param factory an optional factory that, if non-null, will be used to create + * {@code size} background threads that will attempt to compute all + * the memoized values. If the provided factory is {@code null}, no + * background threads will be created. + * @param the type of results delivered by the returned IntFunction + */ + static IntFunction newCachingIntFunction(int size, + IntFunction original, + ThreadFactory factory) { + + final IntFunction memoized = CachedIntFunction.of(size, original); + + if (factory != null) { + for (int i = 0; i < size; i++) { + final int input = i; + final Thread thread = factory.newThread(new Runnable() { + @Override public void run() { memoized.apply(input); } + }); + thread.start(); + } + } + return memoized; + } + + /** + * {@return a new caching, thread-safe, stable, lazily computed {@linkplain Function} + * that, for each allowed input in the given set of {@code inputs}, records the + * values of the provided {@code original} Function upon being first accessed via + * {@linkplain Function#apply(Object)}, or optionally via background threads created + * from the provided {@code factory} (if non-null)} + *

+ * The provided {@code original} Function is guaranteed to be successfully invoked + * at most once per allowed input, even in a multi-threaded environment. Competing + * threads invoking the {@linkplain Function#apply(Object)} method when a value is + * already under computation will block until a value is computed or an exception is + * thrown by the computing thread. + *

+ * If the {@code original} Function invokes the returned Function recursively + * for a particular input value, a StackOverflowError will be thrown when the returned + * IntFunction's {@linkplain IntFunction#apply(int)} method is invoked. + *

+ * If the provided {@code original} Function throws an exception, it is relayed + * to the initial caller. If the memoized Function is computed by a background + * thread, exceptions from the provided {@code original} Function will be relayed to + * the background thread's {@linkplain Thread#getUncaughtExceptionHandler() uncaught + * exception handler}. + *

+ * The order in which background threads are started is unspecified. + * + * @param inputs the set of allowed input values + * @param original Function used to compute a memoized value + * @param factory an optional factory that, if non-null, will be used to create + * {@code size} background threads that will attempt to compute the + * memoized values. If the provided factory is {@code null}, no + * background threads will be created. + * @param the type of results delivered by the returned Function + */ + static Function newCachingFunction(Set inputs, + Function original, + ThreadFactory factory) { + + final Function memoized = CachedFunction.of(inputs, original); + + if (factory != null) { + for (final T t : inputs) { + final Thread thread = factory.newThread(new Runnable() { + @Override public void run() { memoized.apply(t); } + }); + thread.start(); + } + } + return memoized; + } + + /** + * {@return a lazy, immutable, stable List of the provided {@code size} where the + * individual elements of the list are lazily computed vio the provided + * {@code mapper} whenever an element is first accessed (directly or indirectly) via + * {@linkplain List#get(int)}} + *

+ * The provided {@code mapper} IntFunction is guaranteed to be successfully invoked + * at most once per list index, even in a multi-threaded environment. Competing + * threads invoking the {@linkplain IntFunction#apply(int)} method when a value is + * already under computation will block until a value is computed or an exception is + * thrown by the computing thread. + *

+ * If the {@code mapper} IntFunction invokes the returned IntFunction recursively + * for a particular input value, a StackOverflowError will be thrown when the returned + * List's {@linkplain List#get(int)} method is invoked. + *

+ * If the provided {@code mapper} IntFunction throws an exception, it is relayed + * to the initial caller. + * + * @param size the size of the returned list + * @param mapper to invoke whenever an element is first accessed + * @param the {@code StableValue}s' element type + */ + static List lazyList(int size, IntFunction mapper) { if (size < 0) { throw new IllegalArgumentException(); } - @SuppressWarnings("unchecked") - final var stableValues = (StableValue[]) new StableValue[size]; - for (int i = 0; i < size; i++) { - stableValues[i] = newInstance(); - } - return List.of(stableValues); + Objects.requireNonNull(mapper); + return LazyList.of(size, mapper); } /** - * {@return a shallowly immutable, stable Map with the provided {@code keys} - * and associated distinct fresh stable values} + * {@return a lazy, immutable, stable Map of the provided {@code keys} where the + * associated values of the maps are lazily computed vio the provided + * {@code mapper} whenever a value is first accessed (directly or indirectly) via + * {@linkplain Map#get(Object)}} + *

+ * The provided {@code mapper} Function is guaranteed to be successfully invoked + * at most once per key, even in a multi-threaded environment. Competing + * threads invoking the {@linkplain Map#get(Object)} method when a value is + * already under computation will block until a value is computed or an exception is + * thrown by the computing thread. *

- * The method is equivalent to the following for a given non-null set of {@code keys}: - * {@snippet lang = java : - * Map> map = keys.stream() - * .collect(Collectors.toMap( - * Function.identity(), _ -> StableValue.newInstance())); - * } - * but may be more efficient. + * If the {@code mapper} Function invokes the returned Map recursively + * for a particular key, a StackOverflowError will be thrown when the returned + * Map's {@linkplain Map#get(Object)}} method is invoked. + *

+ * If the provided {@code mapper} Function throws an exception, it is relayed + * to the initial caller. * - * @param keys the keys in the {@code Map} - * @param the {@code Map}'s key type - * @param the StableValue's type for the {@code Map}'s value type + * @param keys the keys in the returned map + * @param mapper to invoke whenever an associated value is first accessed + * @param the type of keys maintained by the returned map + * @param the type of mapped values */ - static Map> ofMap(Set keys) { + static Map lazyMap(Set keys, Function mapper) { Objects.requireNonNull(keys); - @SuppressWarnings("unchecked") - final var entries = (Map.Entry>[]) new Map.Entry[keys.size()]; - int i = 0; - for (K key : keys) { - entries[i++] = Map.entry(key, newInstance()); - } - return Map.ofEntries(entries); + Objects.requireNonNull(mapper); + throw new UnsupportedOperationException(); } -} \ No newline at end of file +} diff --git a/src/java.base/share/classes/jdk/internal/lang/StableValues.java b/src/java.base/share/classes/jdk/internal/lang/StableValues.java deleted file mode 100644 index 68c5281566e6e..0000000000000 --- a/src/java.base/share/classes/jdk/internal/lang/StableValues.java +++ /dev/null @@ -1,240 +0,0 @@ -/* - * Copyright (c) 2024, 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; - -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.concurrent.ThreadFactory; -import java.util.function.Function; -import java.util.function.IntFunction; -import java.util.function.Supplier; - -/** - * This class consists of static methods returning constructs involving StableValue. - * - *

The methods of this class all throw {@code NullPointerException} - * if provided with {@code null} arguments unless otherwise specified. - *

- * The constructs returned are eligible for similar JVM optimizations as the - * {@linkplain StableValue} itself. - * - * @see StableValue - * @since 24 - */ -public final class StableValues { - - // Suppresses default constructor, ensuring non-instantiability. - private StableValues() {} - - /** - * {@return a new thread-safe, stable, lazily computed {@linkplain Supplier supplier} - * that records the value of the provided {@code original} supplier upon being first - * accessed via {@linkplain Supplier#get()}, or optionally via a background thread - * created from the provided {@code factory} (if non-null)} - *

- * The provided {@code original} supplier is guaranteed to be successfully invoked - * at most once even in a multi-threaded environment. Competing threads invoking the - * {@linkplain Supplier#get()} method when a value is already under computation - * will block until a value is computed or an exception is thrown by the - * computing thread. - *

- * If the {@code original} Supplier invokes the returned Supplier recursively, - * a StackOverflowError will be thrown when the returned - * Supplier's {@linkplain Supplier#get()} method is invoked. - *

- * If the provided {@code original} supplier throws an exception, it is relayed - * to the initial caller. If the memoized supplier is computed by a background thread, - * exceptions from the provided {@code original} supplier will be relayed to the - * background thread's {@linkplain Thread#getUncaughtExceptionHandler() uncaught - * exception handler}. - * - * @param original supplier used to compute a memoized value - * @param factory an optional factory that, if non-null, will be used to create - * a background thread that will attempt to compute the memoized - * value. If the factory is {@code null}, no background thread will - * be created. - * @param the type of results supplied by the returned supplier - */ - public static Supplier memoizedSupplier(Supplier original, - ThreadFactory factory) { - Objects.requireNonNull(original); - // `factory` is nullable - - // The memoized value is backed by a StableValue - final StableValue stable = StableValue.newInstance(); - // A record provides better debug capabilities than a lambda - record MemoizedSupplier(StableValue stable, - Supplier original) implements Supplier { - @Override public T get() { return stable.computeIfUnset(original); } - } - final Supplier memoized = new MemoizedSupplier<>(stable, original); - - if (factory != null) { - final Thread thread = factory.newThread(new Runnable() { - @Override public void run() { memoized.get(); } - }); - thread.start(); - } - - return memoized; - } - - /** - * {@return a new thread-safe, stable, lazily computed {@linkplain IntFunction} - * that, for each allowed input, records the values of the provided {@code original} - * IntFunction upon being first accessed via {@linkplain IntFunction#apply(int)}, or - * optionally via background threads created from the provided {@code factory} - * (if non-null)} - *

- * The provided {@code original} IntFunction is guaranteed to be successfully invoked - * at most once per allowed input, even in a multi-threaded environment. Competing - * threads invoking the {@linkplain IntFunction#apply(int)} method when a value is - * already under computation will block until a value is computed or an exception is - * thrown by the computing thread. - *

- * If the {@code original} IntFunction invokes the returned IntFunction recursively - * for a particular input value, a StackOverflowError will be thrown when the returned - * IntFunction's {@linkplain IntFunction#apply(int)} method is invoked. - *

- * If the provided {@code original} IntFunction throws an exception, it is relayed - * to the initial caller. If the memoized IntFunction is computed by a background - * thread, exceptions from the provided {@code original} IntFunction will be relayed - * to the background thread's {@linkplain Thread#getUncaughtExceptionHandler() - * uncaught exception handler}. - *

- * The order in which background threads are started is unspecified. - * - * @param size the size of the allowed inputs in {@code [0, size)} - * @param original IntFunction used to compute a memoized value - * @param factory an optional factory that, if non-null, will be used to create - * {@code size} background threads that will attempt to compute all - * the memoized values. If the provided factory is {@code null}, no - * background threads will be created. - * @param the type of results delivered by the returned IntFunction - */ - public static IntFunction memoizedIntFunction(int size, - IntFunction original, - ThreadFactory factory) { - if (size < 0) { - throw new IllegalArgumentException(); - } - Objects.requireNonNull(original); - // `factory` is nullable - - final List> backing = StableValue.ofList(size); - - // A record provides better debug capabilities than a lambda - record MemoizedIntFunction(List> stables, - IntFunction original) implements IntFunction { - @Override public R apply(int value) { return stables.get(value) - .mapIfUnset(value, original::apply); } - } - - final IntFunction memoized = new MemoizedIntFunction<>(backing, original); - - if (factory != null) { - for (int i = 0; i < size; i++) { - final int input = i; - final Thread thread = factory.newThread(new Runnable() { - @Override public void run() { memoized.apply(input); } - }); - thread.start(); - } - } - - return memoized; - } - - /** - * {@return a new thread-safe, stable, lazily computed {@linkplain Function} - * that, for each allowed input in the given set of {@code inputs}, records the - * values of the provided {@code original} Function upon being first accessed via - * {@linkplain Function#apply(Object)}, or optionally via background threads created - * from the provided {@code factory} (if non-null)} - *

- * The provided {@code original} Function is guaranteed to be successfully invoked - * at most once per allowed input, even in a multi-threaded environment. Competing - * threads invoking the {@linkplain Function#apply(Object)} method when a value is - * already under computation will block until a value is computed or an exception is - * thrown by the computing thread. - *

- * If the {@code original} Function invokes the returned Function recursively - * for a particular input value, a StackOverflowError will be thrown when the returned - * IntFunction's {@linkplain IntFunction#apply(int)} method is invoked. - *

- * If the provided {@code original} Function throws an exception, it is relayed - * to the initial caller. If the memoized Function is computed by a background - * thread, exceptions from the provided {@code original} Function will be relayed to - * the background thread's {@linkplain Thread#getUncaughtExceptionHandler() uncaught - * exception handler}. - *

- * The order in which background threads are started is unspecified. - * - * @param inputs the set of allowed input values - * @param original Function used to compute a memoized value - * @param factory an optional factory that, if non-null, will be used to create - * {@code size} background threads that will attempt to compute the - * memoized values. If the provided factory is {@code null}, no - * background threads will be created. - * @param the type of results delivered by the returned Function - */ - public static Function memoizedFunction(Set inputs, - Function original, - ThreadFactory factory) { - Objects.requireNonNull(inputs); - - final Map> backing = StableValue.ofMap(inputs); - - // A record provides better debug capabilities than a lambda - record MemoizedFunction(Map> stables, - Function original) implements Function { - @Override - public R apply(T value) { - StableValue stable = MemoizedFunction.this.stables.get(value); - if (stable == null) { - throw new IllegalArgumentException("Input not allowed: " + value); - } - return stable.mapIfUnset(value, original); - } - } - - final Function memoized = new MemoizedFunction<>(backing, original); - - if (factory != null) { - for (final T t : inputs) { - final Thread thread = factory.newThread(new Runnable() { - @Override public void run() { memoized.apply(t); } - }); - thread.start(); - } - } - - return memoized; - } - -} diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/CachedFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/CachedFunction.java new file mode 100644 index 0000000000000..189ff4c0ff402 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/lang/stable/CachedFunction.java @@ -0,0 +1,38 @@ +package jdk.internal.lang.stable; + +import jdk.internal.vm.annotation.ForceInline; + +import java.util.Map; +import java.util.Set; +import java.util.function.Function; + +public record CachedFunction(Map> stables, + Function original) implements Function { + @ForceInline + @Override + public R apply(T value) { + final StableValueImpl stable = stables.get(value); + if (stable == null) { + throw new IllegalArgumentException("Input not allowed: " + value); + } + R r = stable.value(); + if (r != null) { + return StableValueImpl.unwrap(r); + } + synchronized (stable) { + r = stable.value(); + if (r != null) { + return StableValueImpl.unwrap(r); + } + r = original.apply(value); + stable.setOrThrow(r); + } + return r; + } + + public static CachedFunction of(Set inputs, + Function original) { + return new CachedFunction<>(StableValueImpl.ofMap(inputs), original); + } + +} diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/CachedIntFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/CachedIntFunction.java new file mode 100644 index 0000000000000..5863b1ceaefee --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/lang/stable/CachedIntFunction.java @@ -0,0 +1,33 @@ +package jdk.internal.lang.stable; + +import jdk.internal.vm.annotation.ForceInline; + +import java.util.List; +import java.util.function.IntFunction; + +public record CachedIntFunction(List> stables, + IntFunction original) implements IntFunction { + @ForceInline + @Override + public R apply(int value) { + final StableValueImpl stable = stables.get(value); + R r = stable.value(); + if (r != null) { + return StableValueImpl.unwrap(r); + } + synchronized (stable) { + r = stable.value(); + if (r != null) { + return StableValueImpl.unwrap(r); + } + r = original.apply(value); + stable.setOrThrow(r); + } + return r; + } + + public static CachedIntFunction of(int size, IntFunction original) { + return new CachedIntFunction<>(StableValueImpl.ofList(size), original); + } + +} diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/CachedSupplier.java b/src/java.base/share/classes/jdk/internal/lang/stable/CachedSupplier.java new file mode 100644 index 0000000000000..f7be8417671cc --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/lang/stable/CachedSupplier.java @@ -0,0 +1,31 @@ +package jdk.internal.lang.stable; + +import jdk.internal.vm.annotation.ForceInline; + +import java.util.function.Supplier; + +public record CachedSupplier(StableValueImpl stable, + Supplier original) implements Supplier { + @ForceInline + @Override + public T get() { + T t = stable.value(); + if (t != null) { + return StableValueImpl.unwrap(t); + } + synchronized (stable) { + t = stable.value(); + if (t != null) { + return StableValueImpl.unwrap(t); + } + t = original.get(); + stable.setOrThrow(t); + } + return t; + } + + public static CachedSupplier of(Supplier original) { + return new CachedSupplier<>(StableValueImpl.newInstance(), original); + } + +} diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/LazyList.java b/src/java.base/share/classes/jdk/internal/lang/stable/LazyList.java new file mode 100644 index 0000000000000..9992292d188d6 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/lang/stable/LazyList.java @@ -0,0 +1,56 @@ +package jdk.internal.lang.stable; + +import jdk.internal.ValueBased; +import jdk.internal.vm.annotation.ForceInline; +import jdk.internal.vm.annotation.Stable; + +import java.util.AbstractList; +import java.util.List; +import java.util.RandomAccess; +import java.util.function.IntFunction; + +// Todo:: Move this class to ImmutableCollections +@ValueBased +public final class LazyList + extends AbstractList + implements RandomAccess { + + @Stable + private final IntFunction mapper; + @Stable + private final List> backing; + + private LazyList(int size, IntFunction mapper) { + this.mapper = mapper; + backing = StableValueImpl.ofList(size); + } + + @Override + public int size() { + return backing.size(); + } + + @ForceInline + @Override + public E get(int i) { + final StableValueImpl stable = backing.get(i); + E e = stable.value(); + if (e != null) { + return StableValueImpl.unwrap(e); + } + synchronized (stable) { + e = stable.value(); + if (e != null) { + return StableValueImpl.unwrap(e); + } + e = mapper.apply(i); + stable.setOrThrow(e); + } + return e; + } + + public static List of(int size, IntFunction mapper) { + return new LazyList<>(size, mapper); + } + +} diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/LazyMap.java b/src/java.base/share/classes/jdk/internal/lang/stable/LazyMap.java new file mode 100644 index 0000000000000..62eee8be0d045 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/lang/stable/LazyMap.java @@ -0,0 +1,96 @@ +package jdk.internal.lang.stable; + +import jdk.internal.ValueBased; +import jdk.internal.vm.annotation.ForceInline; +import jdk.internal.vm.annotation.Stable; + +import java.util.AbstractList; +import java.util.AbstractMap; +import java.util.AbstractSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.RandomAccess; +import java.util.Set; +import java.util.function.Function; +import java.util.function.IntFunction; + +// Todo:: Move this class to ImmutableCollections +@ValueBased +public final class LazyMap + extends AbstractMap { + + @Stable + private final Function mapper; + @Stable + private final Map> backing; + + private LazyMap(Set keys, Function mapper) { + this.mapper = mapper; + backing = StableValueImpl.ofMap(keys); + } + + @Override + public int size() { + return backing.size(); + } + + @Override + public Set> entrySet() { + return Set.of(); + } + + @Override + public V get(Object key) { + final StableValueImpl stable = backing.get(key); + if (stable == null) { + return null; + } + V v = stable.value(); + if (v != null) { + return StableValueImpl.unwrap(v); + } + synchronized (stable) { + v = stable.value(); + if (v != null) { + return StableValueImpl.unwrap(v); + } + @SuppressWarnings("unchecked") + K k = (K) key; + v = mapper.apply(k); + stable.setOrThrow(v); + } + return v; + } + + @ValueBased + private final class EntrySet extends AbstractSet> { + @Stable + private final Set>> backingEntrySet; + + public EntrySet() { + this.backingEntrySet = backing.entrySet(); + } + + @Override + public Iterator> iterator() { + return null; + } + + @Override + public int size() { + return backingEntrySet.size(); + } + + private final class LazyIterator implements It{ + + } + + } + + + public static Map of(Set keys, Function mapper) { + return new LazyMap<>(keys, mapper); + } + +} 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 f9ae5c1c74691..bf4c5f78f0139 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 @@ -27,14 +27,14 @@ import jdk.internal.lang.StableValue; import jdk.internal.misc.Unsafe; -import jdk.internal.vm.annotation.DontInline; import jdk.internal.vm.annotation.ForceInline; import jdk.internal.vm.annotation.Stable; +import java.util.List; +import java.util.Map; import java.util.NoSuchElementException; import java.util.Objects; -import java.util.function.Function; -import java.util.function.Supplier; +import java.util.Set; public final class StableValueImpl implements StableValue { @@ -45,34 +45,24 @@ public final class StableValueImpl implements StableValue { // A wrapper method `nullSentinel()` is used for generic type conversion. private static final Object NULL_SENTINEL = new Object(); - // Used to indicate a mutex is not needed anymore. - private static final Object TOMBSTONE = new Object(); - // Unsafe offsets for direct object access private static final long VALUE_OFFSET = UNSAFE.objectFieldOffset(StableValueImpl.class, "value"); - private static final long MUTEX_OFFSET = - UNSAFE.objectFieldOffset(StableValueImpl.class, "mutex"); // Generally, fields annotated with `@Stable` are accessed by the JVM using special // memory semantics rules (see `parse.hpp` and `parse(1|2|3).cpp`). // // This field is reflectively accessed via Unsafe using explicit memory semantics. // - // Value Meaning - // ------- ----- - // null Unset - // nullSentinel() Set(null) - // other Set(other) + // | Value | Meaning | + // | -------------- | ------------ | + // | null | Unset | + // | nullSentinel() | Set(null) | + // | other | Set(other) | + // @Stable private T value; - // This field is initialized on demand to a new distinct mutex object. - // When synchronization is no longer needed (i.e. when a holder value is set), - // the field is set to the `TOMBSTONE` singleton object to allow the previous, - // now-redundant mutex object to be collected. - private volatile Object mutex; - // Only allow creation via the factory `StableValueImpl::newInstance` private StableValueImpl() {} @@ -82,42 +72,21 @@ public boolean trySet(T value) { if (value() != null) { return false; } - Object m = acquireMutex(); - if (m == TOMBSTONE) { - // A holder value must already be set as a holder value store - // happens before a mutex TOMBSTONE store - return false; - } - synchronized (m) { - // The one-and-only update of the `value` field is always made under - // `mutex` synchronization meaning plain memory semantics is enough here. - if (valuePlain() != null) { - return false; - } - set0(value); - // The holder value store must happen before the mutex release - releaseMutex(); - } - return true; - } - @ForceInline - private void set0(T value) { // Prevents reordering of store operations with other store operations. // This means any stores made to fields in the `value` object prior to this // point cannot be reordered with the CAS operation of the reference to the // `value` field. - // In other words, if a reader (using plain memory semantics) can observe a + // In other words, if a loader (using plain memory semantics) can observe a // `value` reference, any field updates made prior to this fence are // guaranteed to be seen. // See https://gee.cs.oswego.edu/dl/html/j9mm.html "Mixed Modes and Specializations", // Doug Lea, 2018 UNSAFE.storeStoreFence(); - // We are alone here under the `mutex` // This upholds the invariant, the `@Stable value` field is written to // at most once. - UNSAFE.putReferenceVolatile(this, VALUE_OFFSET, wrap(value)); + return UNSAFE.compareAndSetReference(this, VALUE_OFFSET, null, wrap(value)); } @ForceInline @@ -146,7 +115,7 @@ public boolean isSet() { return value() != null; } - @ForceInline +/* @ForceInline @Override public T computeIfUnset(Supplier supplier) { final T t = value(); @@ -192,7 +161,7 @@ private T tryCompute(I input, Object provider) { releaseMutex(); return t; } - } + }*/ @Override public int hashCode() { @@ -216,7 +185,7 @@ public String toString() { @ForceInline // First, try to read the value using plain memory semantics. // If not set, fall back to `volatile` memory semantics. - private T value() { + public T value() { final T t = valuePlain(); return t != null ? t : (T) UNSAFE.getReferenceVolatile(this, VALUE_OFFSET); } @@ -234,7 +203,7 @@ private static T wrap(T t) { // Unwraps null sentinel values into `null` @ForceInline - private static T unwrap(T t) { + public static T unwrap(T t) { return t != nullSentinel() ? t : null; } @@ -247,24 +216,32 @@ private static String render(T t) { return (t == null) ? ".unset" : "[" + unwrap(t) + "]"; } - private Object acquireMutex() { - if (mutex != null) { - // We already have a mutex - return mutex; - } - Object newMutex = new Object(); - // Guarantees, only one distinct mutex object per StableValue is ever exposed. - Object witness = UNSAFE.compareAndExchangeReference(this, MUTEX_OFFSET, null, newMutex); - return witness == null ? newMutex : witness; + // Factory for creating new StableValue instances + public static StableValueImpl newInstance() { + return new StableValueImpl<>(); } - private void releaseMutex() { - mutex = TOMBSTONE; + public static List> ofList(int size) { + if (size < 0) { + throw new IllegalArgumentException(); + } + @SuppressWarnings("unchecked") + final var stableValues = (StableValueImpl[]) new StableValueImpl[size]; + for (int i = 0; i < size; i++) { + stableValues[i] = newInstance(); + } + return List.of(stableValues); } - // Factory for creating new StableValue instances - public static StableValue newInstance() { - return new StableValueImpl<>(); + public static Map> ofMap(Set keys) { + Objects.requireNonNull(keys); + @SuppressWarnings("unchecked") + final var entries = (Map.Entry>[]) new Map.Entry[keys.size()]; + int i = 0; + for (K key : keys) { + entries[i++] = Map.entry(key, newInstance()); + } + return Map.ofEntries(entries); } } diff --git a/test/jdk/java/lang/StableValue/JepTest.java b/test/jdk/java/lang/StableValue/JepTest.java index ad26273378eb5..c44b2bc044ccf 100644 --- a/test/jdk/java/lang/StableValue/JepTest.java +++ b/test/jdk/java/lang/StableValue/JepTest.java @@ -29,7 +29,6 @@ */ import jdk.internal.lang.StableValue; -import jdk.internal.lang.StableValues; import java.io.IOException; import java.io.UncheckedIOException; @@ -40,6 +39,7 @@ import java.util.Set; import java.util.function.Function; import java.util.function.IntFunction; +import java.util.function.Supplier; import java.util.logging.Logger; final class JepTest { @@ -61,62 +61,25 @@ static Logger logger() { } class Bar2 { - // 1. Declare a stable field - private static final StableValue LOGGER = StableValue.newInstance(); + + // 1. Declare a caching supplier + private static final Supplier LOGGER = + StableValue.newCachingSupplier( () -> Logger.getLogger("com.foo.Bar"), null ); + static Logger logger() { // 2. Access the stable value with as-declared-final performance // (single evaluation made before the first access) - return LOGGER.computeIfUnset( () -> Logger.getLogger("com.foo.Bar") ); - } - } - - class ErrorMessages { - - private static final int SIZE = 8; - - // 1. Declare a stable list of default error pages to serve up - private static final List> MESSAGES = StableValue.ofList(SIZE); - - // 2. Define a function that is to be called the first - // time a particular message number is referenced - private static String readFromFile(int messageNumber) { - try { - return Files.readString(Path.of("message-" + messageNumber + ".html")); - } catch (IOException e) { - throw new UncheckedIOException(e); - } + return LOGGER.get(); } - - static String errorPage(int messageNumber) { - // 3. Access the stable list element with as-declared-final performance - // (evaluation made before the first access) - return MESSAGES.get(messageNumber) - .mapIfUnset(messageNumber, ErrorMessages::readFromFile); - } - } - class MapDemo { - - // 1. Declare a stable map of loggers with two allowable keys: - // "com.foo.Bar" and "com.foo.Baz" - static final Map> LOGGERS = - StableValue.ofMap(Set.of("com.foo.Bar", "com.foo.Baz")); - - // 2. Access the memoized map with as-declared-final performance - // (evaluation made before the first access) - static Logger logger(String name) { - return LOGGERS.get(name) - .mapIfUnset(name, Logger::getLogger); - } - } class Memoized { // 1. Declare a memoized (cached) function backed by a stable map private static final Function LOGGERS = - StableValues.memoizedFunction(Set.of("com.foo.Bar", "com.foo.Baz"), + StableValue.newCachingFunction(Set.of("com.foo.Bar", "com.foo.Baz"), Logger::getLogger, null); private static final String NAME = "com.foo.Baz"; @@ -134,7 +97,7 @@ class ErrorMessages { // 1. Declare a memoized IntFunction backed by a stable list private static final IntFunction ERROR_FUNCTION = - StableValues.memoizedIntFunction(SIZE, ErrorMessages::readFromFile, null); + StableValue.newCachingIntFunction(SIZE, ErrorMessages::readFromFile, null); // 2. Define a function that is to be called the first // time a particular message number is referenced diff --git a/test/jdk/java/lang/StableValue/StableValueTest.java b/test/jdk/java/lang/StableValue/StableValueTest.java index cb521bc64fbab..db7a72fdffa3f 100644 --- a/test/jdk/java/lang/StableValue/StableValueTest.java +++ b/test/jdk/java/lang/StableValue/StableValueTest.java @@ -24,11 +24,13 @@ /* @test * @summary Basic tests for StableValue implementations * @modules java.base/jdk.internal.lang + * @modules java.base/jdk.internal.lang.stable * @compile --enable-preview -source ${jdk.version} StableValueTest.java * @run junit/othervm --enable-preview StableValueTest */ import jdk.internal.lang.StableValue; +import jdk.internal.lang.stable.StableValueImpl; import org.junit.jupiter.api.Test; import java.util.BitSet; @@ -84,74 +86,6 @@ void setNonNull() { assertEquals(42, stable.orElseThrow()); } - @Test - void computeIfUnset() { - StableValue stable = StableValue.newInstance(); - StableTestUtil.CountingSupplier cntSupplier = new StableTestUtil.CountingSupplier<>(() -> 42); - StableTestUtil.CountingSupplier cntSupplier2 = new StableTestUtil.CountingSupplier<>(() -> 13); - assertEquals(42, stable.computeIfUnset(cntSupplier)); - assertEquals(1, cntSupplier.cnt()); - assertEquals(42, stable.computeIfUnset(cntSupplier)); - assertEquals(1, cntSupplier.cnt()); - assertEquals(42, stable.computeIfUnset(cntSupplier2)); - assertEquals(0, cntSupplier2.cnt()); - assertEquals("StableValue[42]", stable.toString()); - assertEquals(42, stable.orElse(null)); - assertFalse(stable.trySet(null)); - assertFalse(stable.trySet(1)); - assertThrows(IllegalStateException.class, () -> stable.setOrThrow(1)); - } - - @Test - void computeIfUnsetException() { - StableValue stable = StableValue.newInstance(); - Supplier supplier = () -> { - throw new UnsupportedOperationException("aaa"); - }; - var x = assertThrows(UnsupportedOperationException.class, () -> stable.computeIfUnset(supplier)); - assertTrue(x.getMessage().contains("aaa")); - assertEquals(42, stable.computeIfUnset(() -> 42)); - assertEquals("StableValue[42]", stable.toString()); - assertEquals(42, stable.orElse(13)); - assertFalse(stable.trySet(null)); - assertFalse(stable.trySet(1)); - assertThrows(IllegalStateException.class, () -> stable.setOrThrow(1)); - } - - @Test - void mapIfUnset() { - StableValue stable = StableValue.newInstance(); - StableTestUtil.CountingFunction cntFunction = new StableTestUtil.CountingFunction<>(Function.identity()); - StableTestUtil.CountingFunction cntFunction2 = new StableTestUtil.CountingFunction<>(Function.identity()); - assertEquals(42, stable.mapIfUnset(42, cntFunction)); - assertEquals(1, cntFunction.cnt()); - assertEquals(42, stable.mapIfUnset(42, cntFunction)); - assertEquals(1, cntFunction.cnt()); - assertEquals(42, stable.mapIfUnset(13, cntFunction2)); - assertEquals(0, cntFunction2.cnt()); - assertEquals("StableValue[42]", stable.toString()); - assertEquals(42, stable.orElse(null)); - assertFalse(stable.trySet(null)); - assertFalse(stable.trySet(1)); - assertThrows(IllegalStateException.class, () -> stable.setOrThrow(1)); - } - - @Test - void mapIfUnsetException() { - StableValue stable = StableValue.newInstance(); - Function function = _ -> { - throw new UnsupportedOperationException("aaa"); - }; - var x = assertThrows(UnsupportedOperationException.class, () -> stable.mapIfUnset(42, function)); - assertTrue(x.getMessage().contains("aaa")); - assertEquals(42, stable.mapIfUnset(42, Function.identity())); - assertEquals("StableValue[42]", stable.toString()); - assertEquals(42, stable.orElse(13)); - assertFalse(stable.trySet(null)); - assertFalse(stable.trySet(1)); - assertThrows(IllegalStateException.class, () -> stable.setOrThrow(1)); - } - @Test void testHashCode() { StableValue s0 = StableValue.newInstance(); @@ -178,7 +112,7 @@ void testEquals() { @Test void ofList() { - List> list = StableValue.ofList(13); + List> list = StableValueImpl.ofList(13); assertEquals(13, list.size()); // Check, every StableValue is distinct Map, Boolean> idMap = new IdentityHashMap<>(); @@ -186,75 +120,9 @@ void ofList() { assertEquals(13, idMap.size()); } - @Test - void ofList3DimLazy() { - int[] dims = new int[]{2, 3, 4}; - - List>>>>> list3d = StableValue.ofList(dims[0]); - - int[] indices = new int[]{1, 2, 3}; - - int element = from3dLazy(list3d, dims, indices) - .computeIfUnset(() -> 42); - - assertEquals(42, element); - assertEquals(42, from3dLazy(list3d, dims, indices).orElseThrow()); - } - - private StableValue from3dLazy( - List>>>>> list3d, - int[] dims, int[] indices) { - return list3d - .get(indices[0]).mapIfUnset(dims[1], StableValue::ofList) - .get(indices[1]).mapIfUnset(dims[2], StableValue::ofList) - .get(indices[2]); - } - - @Test - void ofList3Dim() { - int[] dims = new int[]{2, 3, 4}; - - List>>>>> list3d = create3d(dims); - - int[] indices = new int[]{1, 2, 3}; - - int element = from3d(list3d, indices) - .computeIfUnset(() -> 42); - - assertEquals(42, element); - assertEquals(42, from3d(list3d, indices).orElseThrow()); - } - - private List>>>>> create3d( - int[] dims) { - - List>>>>> list3d = - StableValue.ofList(dims[0]); - for (int d0 = 0; d0 < dims[0] ; d0++) { - List>>> list2d = StableValue.ofList(dims[1]); - list3d.get(d0).setOrThrow(list2d); - for (int d1 = 0; d1 < dims[1]; d1++) { - List> list = StableValue.ofList(dims[2]); - list2d.get(d1).setOrThrow(list); - } - } - return list3d; - } - - private StableValue from3d( - List>>>>> list3d, int[] indices) { - return list3d - .get(indices[0]).orElseThrow() - .get(indices[1]).orElseThrow() - .get(indices[2]); - } - - @Test void ofMap() { - Map> map = StableValue.ofMap(Set.of(1, 2, 3)); + Map> map = StableValueImpl.ofMap(Set.of(1, 2, 3)); assertEquals(3, map.size()); // Check, every StableValue is distinct Map, Boolean> idMap = new IdentityHashMap<>(); @@ -271,10 +139,6 @@ void ofMap() { return false; } }; - private static final BiPredicate, Integer> COMPUTE_IF_UNSET = (s, i) -> { - int r = s.computeIfUnset(() -> i); - return r == i; - }; @Test void raceTrySet() { @@ -286,17 +150,12 @@ void raceSetOrThrow() { race(SET_OR_THROW); } - @Test - void raceComputeIfUnset() { - race(COMPUTE_IF_UNSET); - } @Test void raceMixed() { - race((s, i) -> switch (i % 3) { + race((s, i) -> switch (i % 2) { case 0 -> TRY_SET.test(s, i); case 1 -> SET_OR_THROW.test(s, i); - case 2 -> COMPUTE_IF_UNSET.test(s, i); default -> fail("should not reach here"); }); } diff --git a/test/jdk/java/lang/StableValue/StableValuesTest.java b/test/jdk/java/lang/StableValue/StableValuesTest.java index d04bc17afa47f..75844157e250d 100644 --- a/test/jdk/java/lang/StableValue/StableValuesTest.java +++ b/test/jdk/java/lang/StableValue/StableValuesTest.java @@ -29,12 +29,8 @@ */ import jdk.internal.lang.StableValue; -import jdk.internal.lang.StableValues; import org.junit.jupiter.api.Test; -import java.util.IdentityHashMap; -import java.util.List; -import java.util.Map; import java.util.Set; import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicInteger; @@ -51,12 +47,12 @@ final class StableValuesTest { @Test void memoizedSupplier() { StableTestUtil.CountingSupplier cs = new StableTestUtil.CountingSupplier<>(() -> 42); - Supplier memoized = StableValues.memoizedSupplier(cs, null); + Supplier memoized = StableValue.newCachingSupplier(cs, null); assertEquals(42, memoized.get()); assertEquals(1, cs.cnt()); assertEquals(42, memoized.get()); assertEquals(1, cs.cnt()); - assertEquals("MemoizedSupplier[stable=StableValue[42], original=" + cs + "]", memoized.toString()); + assertEquals("CachedSupplier[stable=StableValue[42], original=" + cs + "]", memoized.toString()); } @Test @@ -72,7 +68,7 @@ public Thread newThread(Runnable r) { }); } }; - Supplier memoized = StableValues.memoizedSupplier(() -> 42, factory); + Supplier memoized = StableValue.newCachingSupplier(() -> 42, factory); while (cnt.get() < 1) { Thread.onSpinWait(); } @@ -82,12 +78,12 @@ public Thread newThread(Runnable r) { @Test void memoizedIntFunction() { StableTestUtil.CountingIntFunction cif = new StableTestUtil.CountingIntFunction<>(i -> i); - IntFunction memoized = StableValues.memoizedIntFunction(SIZE, cif, null); + IntFunction memoized = StableValue.newCachingIntFunction(SIZE, cif, null); assertEquals(1, memoized.apply(1)); assertEquals(1, cif.cnt()); assertEquals(1, memoized.apply(1)); assertEquals(1, cif.cnt()); - assertEquals("MemoizedIntFunction[stables=[StableValue.unset, StableValue[1]], original=" + cif + "]", memoized.toString()); + assertEquals("CachedIntFunction[stables=[StableValue.unset, StableValue[1]], original=" + cif + "]", memoized.toString()); } @Test @@ -103,7 +99,7 @@ public Thread newThread(Runnable r) { }); } }; - IntFunction memoized = StableValues.memoizedIntFunction(SIZE, i -> i, factory); + IntFunction memoized = StableValue.newCachingIntFunction(SIZE, i -> i, factory); while (cnt.get() < 2) { Thread.onSpinWait(); } @@ -114,12 +110,12 @@ public Thread newThread(Runnable r) { @Test void memoizedFunction() { StableTestUtil.CountingFunction cif = new StableTestUtil.CountingFunction<>(i -> i); - Function memoized = StableValues.memoizedFunction(Set.of(13, 42), cif, null); + Function memoized = StableValue.newCachingFunction(Set.of(13, 42), cif, null); assertEquals(42, memoized.apply(42)); assertEquals(1, cif.cnt()); assertEquals(42, memoized.apply(42)); assertEquals(1, cif.cnt()); - assertTrue(memoized.toString().startsWith("MemoizedFunction[stables={")); + assertTrue(memoized.toString().startsWith("CachedFunction[stables={")); // Key order is unspecified assertTrue(memoized.toString().contains("13=StableValue.unset")); assertTrue(memoized.toString().contains("42=StableValue[42]")); @@ -139,7 +135,7 @@ public Thread newThread(Runnable r) { }); } }; - Function memoized = StableValues.memoizedFunction(Set.of(13, 42), i -> i, factory); + Function memoized = StableValue.newCachingFunction(Set.of(13, 42), i -> i, factory); while (cnt.get() < 2) { Thread.onSpinWait(); } From 3c1f03ff7cd62832b59dc89cbf9eef786320bb11 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 26 Jun 2024 11:16:46 +0200 Subject: [PATCH 047/327] Add LazyList and LazyMap --- .../java/util/ImmutableCollections.java | 206 ++++++++++++++++++ .../access/JavaUtilCollectionAccess.java | 6 + .../jdk/internal/lang/StableValue.java | 35 +-- .../jdk/internal/lang/stable/LazyList.java | 56 ----- .../jdk/internal/lang/stable/LazyMap.java | 96 -------- .../internal/lang/stable/StableValueImpl.java | 51 +---- 6 files changed, 233 insertions(+), 217 deletions(-) delete mode 100644 src/java.base/share/classes/jdk/internal/lang/stable/LazyList.java delete mode 100644 src/java.base/share/classes/jdk/internal/lang/stable/LazyMap.java diff --git a/src/java.base/share/classes/java/util/ImmutableCollections.java b/src/java.base/share/classes/java/util/ImmutableCollections.java index 726c7bb923b25..e094b32068479 100644 --- a/src/java.base/share/classes/java/util/ImmutableCollections.java +++ b/src/java.base/share/classes/java/util/ImmutableCollections.java @@ -33,12 +33,17 @@ import java.io.Serializable; import java.lang.reflect.Array; import java.util.function.BiFunction; +import java.util.function.Consumer; import java.util.function.Function; +import java.util.function.IntFunction; import java.util.function.Predicate; import java.util.function.UnaryOperator; + import jdk.internal.access.JavaUtilCollectionAccess; import jdk.internal.access.SharedSecrets; +import jdk.internal.lang.stable.StableValueImpl; import jdk.internal.misc.CDS; +import jdk.internal.vm.annotation.ForceInline; import jdk.internal.vm.annotation.Stable; /** @@ -126,6 +131,12 @@ public List listFromTrustedArray(Object[] array) { public List listFromTrustedArrayNullsAllowed(Object[] array) { return ImmutableCollections.listFromTrustedArrayNullsAllowed(array); } + public List lazyList(int size, IntFunction mapper) { + return ImmutableCollections.lazyList(size, mapper); + } + public Map lazyMap(Set keys, Function mapper) { + return new LazyMap<>(keys, mapper); + } }); } } @@ -248,6 +259,11 @@ static List listFromTrustedArrayNullsAllowed(Object... input) { } } + static List lazyList(int size, IntFunction mapper) { + // A lazy list is not Serializable so, we cannot return `List.of()` if size == 0 + return new LazyList<>(size, mapper); + } + // ---------- List Implementations ---------- @jdk.internal.ValueBased @@ -748,6 +764,92 @@ public int lastIndexOf(Object o) { } } + @jdk.internal.ValueBased + static final class LazyList extends AbstractImmutableList { + + @Stable + private final IntFunction mapper; + @Stable + private final List> backing; + + LazyList(int size, IntFunction mapper) { + this.mapper = mapper; + this.backing = StableValueImpl.ofList(size); + } + + @Override public boolean isEmpty() { + return backing.isEmpty(); + } + @Override public int size() { return backing.size(); } + @Override public Object[] toArray() { return copyInto(new Object[size()]); } + + @ForceInline + @Override + public E get(int i) { + final StableValueImpl stable = backing.get(i); + E e = stable.value(); + if (e != null) { + return StableValueImpl.unwrap(e); + } + synchronized (stable) { + e = stable.value(); + if (e != null) { + return StableValueImpl.unwrap(e); + } + e = mapper.apply(i); + stable.setOrThrow(e); + } + return e; + } + + @Override + @SuppressWarnings("unchecked") + public T[] toArray(T[] a) { + final int size = backing.size(); + if (a.length < size) { + // Make a new array of a's runtime type, but my contents: + T[] n = (T[])Array.newInstance(a.getClass().getComponentType(), size); + return copyInto(n); + } + copyInto(a); + if (a.length > size) { + a[size] = null; // null-terminate + } + return a; + } + + @Override + public int indexOf(Object o) { + final int size = size(); + for (int i = 0; i < size; i++) { + if (Objects.equals(0, get(i))) { + return i; + } + } + return -1; + } + + @Override + public int lastIndexOf(Object o) { + for (int i = size() - 1; i >= 0; i--) { + if (Objects.equals(0, get(i))) { + return i; + } + } + return -1; + } + + @SuppressWarnings("unchecked") + private T[] copyInto(Object[] a) { + final int len = backing.size(); + for (int i = 0; i < len; i++) { + a[i] = get(i); + } + return (T[]) a; + } + + } + // ---------- Set Implementations ---------- @jdk.internal.ValueBased @@ -1360,6 +1462,110 @@ private Object writeReplace() { return new CollSer(CollSer.IMM_MAP, array); } } + + static final class LazyMap + extends AbstractImmutableMap { + + @Stable + private final Function mapper; + @Stable + private final Map> backing; + + LazyMap(Set keys, Function mapper) { + this.mapper = mapper; + this.backing = StableValueImpl.ofMap(keys); + } + + @Override public boolean containsKey(Object o) { return backing.containsKey(o); } + @Override public int size() { return backing.size(); } + @Override public Set> entrySet() { return new LazyMapEntrySet(); } + + @ForceInline + @Override + public V get(Object key) { + final StableValueImpl stable = backing.get(key); + if (stable == null) { + return null; + } + @SuppressWarnings("unchecked") + final K k = (K) key; + return computeIfUnset(k, stable); + } + + @ForceInline + V computeIfUnset(K key, StableValueImpl stable) { + V v = stable.value(); + if (v != null) { + return StableValueImpl.unwrap(v); + } + synchronized (stable) { + v = stable.value(); + if (v != null) { + return StableValueImpl.unwrap(v); + } + v = mapper.apply(key); + stable.setOrThrow(v); + } + return v; + } + + @jdk.internal.ValueBased + final class LazyMapEntrySet extends AbstractImmutableSet> { + + @Stable + private final Set>> delegateEntrySet; + + public LazyMapEntrySet() { + this.delegateEntrySet = backing.entrySet(); + } + + @Override public Iterator> iterator() { return new LazyMapIterator(); } + @Override public int size() { return delegateEntrySet.size(); } + + @Override + public int hashCode() { + int h = 0; + for (Map.Entry e : this) { + h += e.hashCode(); + } + return h; + } + + @jdk.internal.ValueBased + private final class LazyMapIterator implements Iterator> { + + @Stable + private final Iterator>> delegateIterator; + + private LazyMapIterator() { + this.delegateIterator = delegateEntrySet.iterator(); + } + + @Override public boolean hasNext() { return delegateIterator.hasNext(); } + + @Override + public Entry next() { + final Map.Entry> inner = delegateIterator.next(); + final K key = delegateIterator.next().getKey(); + return new KeyValueHolder<>(key, computeIfUnset(key, inner.getValue())); + } + + @Override + public void forEachRemaining(Consumer> action) { + final Consumer>> innerAction = + new Consumer<>() { + @Override + public void accept(Entry> inner) { + final K key = inner.getKey(); + action.accept(new KeyValueHolder<>(key, LazyMap.this.computeIfUnset(key, inner.getValue()))); + } + }; + delegateIterator.forEachRemaining(innerAction); + } + } + } + } + } // ---------- Serialization Proxy ---------- diff --git a/src/java.base/share/classes/jdk/internal/access/JavaUtilCollectionAccess.java b/src/java.base/share/classes/jdk/internal/access/JavaUtilCollectionAccess.java index f88d57521ac5a..3d09b893fdf85 100644 --- a/src/java.base/share/classes/jdk/internal/access/JavaUtilCollectionAccess.java +++ b/src/java.base/share/classes/jdk/internal/access/JavaUtilCollectionAccess.java @@ -26,8 +26,14 @@ package jdk.internal.access; import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.function.IntFunction; public interface JavaUtilCollectionAccess { List listFromTrustedArray(Object[] array); List listFromTrustedArrayNullsAllowed(Object[] array); + List lazyList(int size, IntFunction mapper); + Map lazyMap(Set keys, Function mapper); } diff --git a/src/java.base/share/classes/jdk/internal/lang/StableValue.java b/src/java.base/share/classes/jdk/internal/lang/StableValue.java index 0478eb2c277a2..fcfdff9de6ccc 100644 --- a/src/java.base/share/classes/jdk/internal/lang/StableValue.java +++ b/src/java.base/share/classes/jdk/internal/lang/StableValue.java @@ -25,12 +25,13 @@ package jdk.internal.lang; +import jdk.internal.access.SharedSecrets; import jdk.internal.lang.stable.CachedFunction; import jdk.internal.lang.stable.CachedIntFunction; import jdk.internal.lang.stable.CachedSupplier; -import jdk.internal.lang.stable.LazyList; import jdk.internal.lang.stable.StableValueImpl; +import java.io.Serializable; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; @@ -357,21 +358,22 @@ static Function newCachingFunction(Set inputs, /** * {@return a lazy, immutable, stable List of the provided {@code size} where the * individual elements of the list are lazily computed vio the provided - * {@code mapper} whenever an element is first accessed (directly or indirectly) via - * {@linkplain List#get(int)}} + * {@code mapper} whenever an element is first accessed (directly or indirectly), + * for example via {@linkplain List#get(int)}} *

* The provided {@code mapper} IntFunction is guaranteed to be successfully invoked * at most once per list index, even in a multi-threaded environment. Competing - * threads invoking the {@linkplain IntFunction#apply(int)} method when a value is - * already under computation will block until a value is computed or an exception is - * thrown by the computing thread. + * threads accessing an element already under computation will block until an element + * is computed or an exception is thrown by the computing thread. *

* If the {@code mapper} IntFunction invokes the returned IntFunction recursively - * for a particular input value, a StackOverflowError will be thrown when the returned + * for a particular index, a StackOverflowError will be thrown when the returned * List's {@linkplain List#get(int)} method is invoked. *

* If the provided {@code mapper} IntFunction throws an exception, it is relayed - * to the initial caller. + * to the initial caller and no element is computed. + *

+ * The returned List is not {@linkplain Serializable} * * @param size the size of the returned list * @param mapper to invoke whenever an element is first accessed @@ -382,27 +384,28 @@ static List lazyList(int size, IntFunction mapper) { throw new IllegalArgumentException(); } Objects.requireNonNull(mapper); - return LazyList.of(size, mapper); + return SharedSecrets.getJavaUtilCollectionAccess().lazyList(size, mapper); } /** * {@return a lazy, immutable, stable Map of the provided {@code keys} where the * associated values of the maps are lazily computed vio the provided - * {@code mapper} whenever a value is first accessed (directly or indirectly) via - * {@linkplain Map#get(Object)}} + * {@code mapper} whenever a value is first accessed (directly or indirectly), for + * example via {@linkplain Map#get(Object)}} *

* The provided {@code mapper} Function is guaranteed to be successfully invoked * at most once per key, even in a multi-threaded environment. Competing - * threads invoking the {@linkplain Map#get(Object)} method when a value is - * already under computation will block until a value is computed or an exception is - * thrown by the computing thread. + * threads accessing an associated value already under computation will block until + * an associated value is computed or an exception is thrown by the computing thread. *

* If the {@code mapper} Function invokes the returned Map recursively * for a particular key, a StackOverflowError will be thrown when the returned * Map's {@linkplain Map#get(Object)}} method is invoked. *

* If the provided {@code mapper} Function throws an exception, it is relayed - * to the initial caller. + * to the initial caller and no value is computed. + *

+ * The returned Map is not {@linkplain Serializable} * * @param keys the keys in the returned map * @param mapper to invoke whenever an associated value is first accessed @@ -412,7 +415,7 @@ static List lazyList(int size, IntFunction mapper) { static Map lazyMap(Set keys, Function mapper) { Objects.requireNonNull(keys); Objects.requireNonNull(mapper); - throw new UnsupportedOperationException(); + return SharedSecrets.getJavaUtilCollectionAccess().lazyMap(keys, mapper); } } diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/LazyList.java b/src/java.base/share/classes/jdk/internal/lang/stable/LazyList.java deleted file mode 100644 index 9992292d188d6..0000000000000 --- a/src/java.base/share/classes/jdk/internal/lang/stable/LazyList.java +++ /dev/null @@ -1,56 +0,0 @@ -package jdk.internal.lang.stable; - -import jdk.internal.ValueBased; -import jdk.internal.vm.annotation.ForceInline; -import jdk.internal.vm.annotation.Stable; - -import java.util.AbstractList; -import java.util.List; -import java.util.RandomAccess; -import java.util.function.IntFunction; - -// Todo:: Move this class to ImmutableCollections -@ValueBased -public final class LazyList - extends AbstractList - implements RandomAccess { - - @Stable - private final IntFunction mapper; - @Stable - private final List> backing; - - private LazyList(int size, IntFunction mapper) { - this.mapper = mapper; - backing = StableValueImpl.ofList(size); - } - - @Override - public int size() { - return backing.size(); - } - - @ForceInline - @Override - public E get(int i) { - final StableValueImpl stable = backing.get(i); - E e = stable.value(); - if (e != null) { - return StableValueImpl.unwrap(e); - } - synchronized (stable) { - e = stable.value(); - if (e != null) { - return StableValueImpl.unwrap(e); - } - e = mapper.apply(i); - stable.setOrThrow(e); - } - return e; - } - - public static List of(int size, IntFunction mapper) { - return new LazyList<>(size, mapper); - } - -} diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/LazyMap.java b/src/java.base/share/classes/jdk/internal/lang/stable/LazyMap.java deleted file mode 100644 index 62eee8be0d045..0000000000000 --- a/src/java.base/share/classes/jdk/internal/lang/stable/LazyMap.java +++ /dev/null @@ -1,96 +0,0 @@ -package jdk.internal.lang.stable; - -import jdk.internal.ValueBased; -import jdk.internal.vm.annotation.ForceInline; -import jdk.internal.vm.annotation.Stable; - -import java.util.AbstractList; -import java.util.AbstractMap; -import java.util.AbstractSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.RandomAccess; -import java.util.Set; -import java.util.function.Function; -import java.util.function.IntFunction; - -// Todo:: Move this class to ImmutableCollections -@ValueBased -public final class LazyMap - extends AbstractMap { - - @Stable - private final Function mapper; - @Stable - private final Map> backing; - - private LazyMap(Set keys, Function mapper) { - this.mapper = mapper; - backing = StableValueImpl.ofMap(keys); - } - - @Override - public int size() { - return backing.size(); - } - - @Override - public Set> entrySet() { - return Set.of(); - } - - @Override - public V get(Object key) { - final StableValueImpl stable = backing.get(key); - if (stable == null) { - return null; - } - V v = stable.value(); - if (v != null) { - return StableValueImpl.unwrap(v); - } - synchronized (stable) { - v = stable.value(); - if (v != null) { - return StableValueImpl.unwrap(v); - } - @SuppressWarnings("unchecked") - K k = (K) key; - v = mapper.apply(k); - stable.setOrThrow(v); - } - return v; - } - - @ValueBased - private final class EntrySet extends AbstractSet> { - @Stable - private final Set>> backingEntrySet; - - public EntrySet() { - this.backingEntrySet = backing.entrySet(); - } - - @Override - public Iterator> iterator() { - return null; - } - - @Override - public int size() { - return backingEntrySet.size(); - } - - private final class LazyIterator implements It{ - - } - - } - - - public static Map of(Set keys, Function mapper) { - return new LazyMap<>(keys, mapper); - } - -} 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 bf4c5f78f0139..e1aff636cf661 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 @@ -115,54 +115,6 @@ public boolean isSet() { return value() != null; } -/* @ForceInline - @Override - public T computeIfUnset(Supplier supplier) { - final T t = value(); - if (t != null) { - return unwrap(t); - } - return tryCompute(null, supplier); - } - - @ForceInline - @Override - public T mapIfUnset(I input, Function function) { - final T t = value(); - if (t != null) { - return unwrap(t); - } - return tryCompute(input, function); - } - - @SuppressWarnings("unchecked") - @DontInline - private T tryCompute(I input, Object provider) { - Object m = acquireMutex(); - if (m == TOMBSTONE) { - // A holder value must already be set as a holder value store - // happens before a mutex TOMBSTONE store - return unwrap(value()); - } - synchronized (m) { - // The one-and-only update of the `value` field is always made under - // `mutex` synchronization meaning plain memory semantics is enough here. - T t = valuePlain(); - if (t != null) { - return unwrap(t); - } - if (provider instanceof Supplier supplier) { - t = (T) supplier.get(); - } else { - t = ((Function) provider).apply(input); - } - set0(t); - // The holder value store must happen before the mutex release - releaseMutex(); - return t; - } - }*/ - @Override public int hashCode() { return Objects.hashCode(value()); @@ -216,7 +168,8 @@ private static String render(T t) { return (t == null) ? ".unset" : "[" + unwrap(t) + "]"; } - // Factory for creating new StableValue instances + // Factories + public static StableValueImpl newInstance() { return new StableValueImpl<>(); } From 1b1a7bf206c34045550a1bfa075cda2e3bb91093 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 26 Jun 2024 11:44:49 +0200 Subject: [PATCH 048/327] Improve cached construct tests --- .../lang/StableValue/CachingFunctionTest.java | 96 ++++++++++++ .../StableValue/CachingIntFunctionTest.java | 89 +++++++++++ .../lang/StableValue/CachingSupplierTest.java | 84 ++++++++++ .../lang/StableValue/StableValuesTest.java | 146 ------------------ 4 files changed, 269 insertions(+), 146 deletions(-) create mode 100644 test/jdk/java/lang/StableValue/CachingFunctionTest.java create mode 100644 test/jdk/java/lang/StableValue/CachingIntFunctionTest.java create mode 100644 test/jdk/java/lang/StableValue/CachingSupplierTest.java delete mode 100644 test/jdk/java/lang/StableValue/StableValuesTest.java diff --git a/test/jdk/java/lang/StableValue/CachingFunctionTest.java b/test/jdk/java/lang/StableValue/CachingFunctionTest.java new file mode 100644 index 0000000000000..2816bd221d129 --- /dev/null +++ b/test/jdk/java/lang/StableValue/CachingFunctionTest.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2024, 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 CachingFunction methods + * @modules java.base/jdk.internal.lang + * @compile --enable-preview -source ${jdk.version} CachingFunctionTest.java + * @run junit/othervm --enable-preview CachingFunctionTest + */ + +import jdk.internal.lang.StableValue; +import org.junit.jupiter.api.Test; + +import java.util.Set; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.jupiter.api.Assertions.*; + +final class CachingFunctionTest { + + @Test + void basic() { + StableTestUtil.CountingFunction cif = new StableTestUtil.CountingFunction<>(i -> i); + var cached = StableValue.newCachingFunction(Set.of(13, 42), cif, null); + assertEquals(42, cached.apply(42)); + assertEquals(1, cif.cnt()); + assertEquals(42, cached.apply(42)); + assertEquals(1, cif.cnt()); + assertTrue(cached.toString().startsWith("CachedFunction[stables={")); + // Key order is unspecified + assertTrue(cached.toString().contains("13=StableValue.unset")); + assertTrue(cached.toString().contains("42=StableValue[42]")); + assertTrue(cached.toString().endsWith(", original=" + cif + "]")); + var x = assertThrows(IllegalArgumentException.class, () -> cached.apply(-1)); + assertTrue(x.getMessage().contains("-1")); + } + + @Test + void background() { + final AtomicInteger cnt = new AtomicInteger(0); + ThreadFactory factory = new ThreadFactory() { + @java.lang.Override + public Thread newThread(Runnable r) { + return new Thread(() -> { + r.run(); + cnt.incrementAndGet(); + }); + } + }; + var cached = StableValue.newCachingFunction(Set.of(13, 42), i -> i, factory); + while (cnt.get() < 2) { + Thread.onSpinWait(); + } + assertEquals(42, cached.apply(42)); + assertEquals(13, cached.apply(13)); + } + + @Test + void exception() { + StableTestUtil.CountingFunction cif = new StableTestUtil.CountingFunction<>(_ -> { + throw new UnsupportedOperationException(); + }); + var cached = StableValue.newCachingFunction(Set.of(13, 42), cif, null); + assertThrows(UnsupportedOperationException.class, () -> cached.apply(42)); + assertEquals(1, cif.cnt()); + assertThrows(UnsupportedOperationException.class, () -> cached.apply(42)); + assertEquals(2, cif.cnt()); + assertTrue(cached.toString().startsWith("CachedFunction[stables={")); + // Key order is unspecified + assertTrue(cached.toString().contains("13=StableValue.unset")); + assertTrue(cached.toString().contains("42=StableValue.unset")); + assertTrue(cached.toString().endsWith(", original=" + cif + "]")); + } + +} diff --git a/test/jdk/java/lang/StableValue/CachingIntFunctionTest.java b/test/jdk/java/lang/StableValue/CachingIntFunctionTest.java new file mode 100644 index 0000000000000..9f25c4a4a4528 --- /dev/null +++ b/test/jdk/java/lang/StableValue/CachingIntFunctionTest.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2024, 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 CachingIntFunction methods + * @modules java.base/jdk.internal.lang + * @compile --enable-preview -source ${jdk.version} CachingIntFunctionTest.java + * @run junit/othervm --enable-preview CachingIntFunctionTest + */ + +import jdk.internal.lang.StableValue; +import org.junit.jupiter.api.Test; + +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +final class CachingIntFunctionTest { + + private static final int SIZE = 2; + + @Test + void basic() { + StableTestUtil.CountingIntFunction cif = new StableTestUtil.CountingIntFunction<>(i -> i); + var cached = StableValue.newCachingIntFunction(SIZE, cif, null); + assertEquals(1, cached.apply(1)); + assertEquals(1, cif.cnt()); + assertEquals(1, cached.apply(1)); + assertEquals(1, cif.cnt()); + assertEquals("CachedIntFunction[stables=[StableValue.unset, StableValue[1]], original=" + cif + "]", cached.toString()); + assertThrows(IndexOutOfBoundsException.class, () -> cached.apply(SIZE + 1)); + } + + @Test + void background() { + final AtomicInteger cnt = new AtomicInteger(0); + ThreadFactory factory = new ThreadFactory() { + @java.lang.Override + public Thread newThread(Runnable r) { + return new Thread(() -> { + r.run(); + cnt.incrementAndGet(); + }); + } + }; + var cached = StableValue.newCachingIntFunction(SIZE, i -> i, factory); + while (cnt.get() < 2) { + Thread.onSpinWait(); + } + assertEquals(0, cached.apply(0)); + assertEquals(1, cached.apply(1)); + } + + @Test + void exception() { + StableTestUtil.CountingIntFunction cif = new StableTestUtil.CountingIntFunction<>(_ -> { + throw new UnsupportedOperationException(); + }); + var cached = StableValue.newCachingIntFunction(SIZE, cif, null); + assertThrows(UnsupportedOperationException.class, () -> cached.apply(1)); + assertEquals(1, cif.cnt()); + assertThrows(UnsupportedOperationException.class, () -> cached.apply(1)); + assertEquals(2, cif.cnt()); + assertEquals("CachedIntFunction[stables=[StableValue.unset, StableValue.unset], original=" + cif + "]", cached.toString()); + } + +} diff --git a/test/jdk/java/lang/StableValue/CachingSupplierTest.java b/test/jdk/java/lang/StableValue/CachingSupplierTest.java new file mode 100644 index 0000000000000..b5747d68ca502 --- /dev/null +++ b/test/jdk/java/lang/StableValue/CachingSupplierTest.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2024, 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 CachingSupplier methods + * @modules java.base/jdk.internal.lang + * @compile --enable-preview -source ${jdk.version} CachingSupplierTest.java + * @run junit/othervm --enable-preview CachingSupplierTest + */ + +import jdk.internal.lang.StableValue; +import org.junit.jupiter.api.Test; + +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.jupiter.api.Assertions.*; + +final class CachingSupplierTest { + + @Test + void basic() { + StableTestUtil.CountingSupplier cs = new StableTestUtil.CountingSupplier<>(() -> 42); + var cached = StableValue.newCachingSupplier(cs, null); + assertEquals(42, cached.get()); + assertEquals(1, cs.cnt()); + assertEquals(42, cached.get()); + assertEquals(1, cs.cnt()); + assertEquals("CachedSupplier[stable=StableValue[42], original=" + cs + "]", cached.toString()); + } + + @Test + void background() { + final AtomicInteger cnt = new AtomicInteger(0); + ThreadFactory factory = new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + return new Thread(() -> { + r.run(); + cnt.incrementAndGet(); + }); + } + }; + var cached = StableValue.newCachingSupplier(() -> 42, factory); + while (cnt.get() < 1) { + Thread.onSpinWait(); + } + assertEquals(42, cached.get()); + } + + @Test + void exception() { + StableTestUtil.CountingSupplier cs = new StableTestUtil.CountingSupplier<>(() -> { + throw new UnsupportedOperationException(); + }); + var cached = StableValue.newCachingSupplier(cs, null); + assertThrows(UnsupportedOperationException.class, cached::get); + assertEquals(1, cs.cnt()); + assertThrows(UnsupportedOperationException.class, cached::get); + assertEquals(2, cs.cnt()); + assertEquals("CachedSupplier[stable=StableValue.unset, original=" + cs + "]", cached.toString()); + } + +} diff --git a/test/jdk/java/lang/StableValue/StableValuesTest.java b/test/jdk/java/lang/StableValue/StableValuesTest.java deleted file mode 100644 index 75844157e250d..0000000000000 --- a/test/jdk/java/lang/StableValue/StableValuesTest.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright (c) 2024, 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 StableValues methods - * @modules java.base/jdk.internal.lang - * @compile --enable-preview -source ${jdk.version} StableValuesTest.java - * @run junit/othervm --enable-preview StableValuesTest - */ - -import jdk.internal.lang.StableValue; -import org.junit.jupiter.api.Test; - -import java.util.Set; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Function; -import java.util.function.IntFunction; -import java.util.function.Supplier; - -import static org.junit.jupiter.api.Assertions.*; - -final class StableValuesTest { - - private static final int SIZE = 2; - - @Test - void memoizedSupplier() { - StableTestUtil.CountingSupplier cs = new StableTestUtil.CountingSupplier<>(() -> 42); - Supplier memoized = StableValue.newCachingSupplier(cs, null); - assertEquals(42, memoized.get()); - assertEquals(1, cs.cnt()); - assertEquals(42, memoized.get()); - assertEquals(1, cs.cnt()); - assertEquals("CachedSupplier[stable=StableValue[42], original=" + cs + "]", memoized.toString()); - } - - @Test - void memoizedSupplierBackground() { - - final AtomicInteger cnt = new AtomicInteger(0); - ThreadFactory factory = new ThreadFactory() { - @Override - public Thread newThread(Runnable r) { - return new Thread(() -> { - r.run(); - cnt.incrementAndGet(); - }); - } - }; - Supplier memoized = StableValue.newCachingSupplier(() -> 42, factory); - while (cnt.get() < 1) { - Thread.onSpinWait(); - } - assertEquals(42, memoized.get()); - } - - @Test - void memoizedIntFunction() { - StableTestUtil.CountingIntFunction cif = new StableTestUtil.CountingIntFunction<>(i -> i); - IntFunction memoized = StableValue.newCachingIntFunction(SIZE, cif, null); - assertEquals(1, memoized.apply(1)); - assertEquals(1, cif.cnt()); - assertEquals(1, memoized.apply(1)); - assertEquals(1, cif.cnt()); - assertEquals("CachedIntFunction[stables=[StableValue.unset, StableValue[1]], original=" + cif + "]", memoized.toString()); - } - - @Test - void memoizedIntFunctionBackground() { - - final AtomicInteger cnt = new AtomicInteger(0); - ThreadFactory factory = new ThreadFactory() { - @Override - public Thread newThread(Runnable r) { - return new Thread(() -> { - r.run(); - cnt.incrementAndGet(); - }); - } - }; - IntFunction memoized = StableValue.newCachingIntFunction(SIZE, i -> i, factory); - while (cnt.get() < 2) { - Thread.onSpinWait(); - } - assertEquals(0, memoized.apply(0)); - assertEquals(1, memoized.apply(1)); - } - - @Test - void memoizedFunction() { - StableTestUtil.CountingFunction cif = new StableTestUtil.CountingFunction<>(i -> i); - Function memoized = StableValue.newCachingFunction(Set.of(13, 42), cif, null); - assertEquals(42, memoized.apply(42)); - assertEquals(1, cif.cnt()); - assertEquals(42, memoized.apply(42)); - assertEquals(1, cif.cnt()); - assertTrue(memoized.toString().startsWith("CachedFunction[stables={")); - // Key order is unspecified - assertTrue(memoized.toString().contains("13=StableValue.unset")); - assertTrue(memoized.toString().contains("42=StableValue[42]")); - assertTrue(memoized.toString().endsWith(", original=" + cif + "]")); - } - - @Test - void memoizedFunctionBackground() { - - final AtomicInteger cnt = new AtomicInteger(0); - ThreadFactory factory = new ThreadFactory() { - @Override - public Thread newThread(Runnable r) { - return new Thread(() -> { - r.run(); - cnt.incrementAndGet(); - }); - } - }; - Function memoized = StableValue.newCachingFunction(Set.of(13, 42), i -> i, factory); - while (cnt.get() < 2) { - Thread.onSpinWait(); - } - assertEquals(42, memoized.apply(42)); - assertEquals(13, memoized.apply(13)); - } - -} From 6d595baaa8dad363d2138721c211fd250c78b2e1 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 26 Jun 2024 11:47:45 +0200 Subject: [PATCH 049/327] Supress warning --- test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java b/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java index d1a2253882528..0f85f4938508f 100644 --- a/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java +++ b/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java @@ -59,6 +59,7 @@ final class HolderNonFinal { assertDoesNotThrow(() -> valueNonFinal.setAccessible(true)); } + @SuppressWarnings("removal") @Test void sunMiscUnsafe() throws NoSuchFieldException, IllegalAccessException { Field unsafeField = sun.misc.Unsafe.class.getDeclaredField("theUnsafe"); From a38672f878427bbf7c03b3779d4d9f8d9c4d2c53 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 26 Jun 2024 14:54:01 +0200 Subject: [PATCH 050/327] Add LazyList test --- .../java/util/ImmutableCollections.java | 9 +- .../StableValue/CachingIntFunctionTest.java | 3 +- .../java/lang/StableValue/LazyListTest.java | 298 ++++++++++++++++++ 3 files changed, 304 insertions(+), 6 deletions(-) create mode 100644 test/jdk/java/lang/StableValue/LazyListTest.java diff --git a/src/java.base/share/classes/java/util/ImmutableCollections.java b/src/java.base/share/classes/java/util/ImmutableCollections.java index e094b32068479..a12904510cd34 100644 --- a/src/java.base/share/classes/java/util/ImmutableCollections.java +++ b/src/java.base/share/classes/java/util/ImmutableCollections.java @@ -462,7 +462,7 @@ static final class SubList extends AbstractImmutableList private final int size; private SubList(AbstractImmutableList root, int offset, int size) { - assert root instanceof List12 || root instanceof ListN; + assert root instanceof List12 || root instanceof ListN || root instanceof LazyList; this.root = root; this.offset = offset; this.size = size; @@ -513,7 +513,8 @@ private void rangeCheck(int index) { } private boolean allowNulls() { - return root instanceof ListN && ((ListN)root).allowNulls; + return root instanceof ListN listN && listN.allowNulls + || root instanceof LazyList; } @Override @@ -822,7 +823,7 @@ public T[] toArray(T[] a) { public int indexOf(Object o) { final int size = size(); for (int i = 0; i < size; i++) { - if (Objects.equals(0, get(i))) { + if (Objects.equals(o, get(i))) { return i; } } @@ -832,7 +833,7 @@ public int indexOf(Object o) { @Override public int lastIndexOf(Object o) { for (int i = size() - 1; i >= 0; i--) { - if (Objects.equals(0, get(i))) { + if (Objects.equals(o, get(i))) { return i; } } diff --git a/test/jdk/java/lang/StableValue/CachingIntFunctionTest.java b/test/jdk/java/lang/StableValue/CachingIntFunctionTest.java index 9f25c4a4a4528..b945ca5a6574e 100644 --- a/test/jdk/java/lang/StableValue/CachingIntFunctionTest.java +++ b/test/jdk/java/lang/StableValue/CachingIntFunctionTest.java @@ -34,8 +34,7 @@ import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicInteger; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.*; final class CachingIntFunctionTest { diff --git a/test/jdk/java/lang/StableValue/LazyListTest.java b/test/jdk/java/lang/StableValue/LazyListTest.java new file mode 100644 index 0000000000000..d06a4176abdd0 --- /dev/null +++ b/test/jdk/java/lang/StableValue/LazyListTest.java @@ -0,0 +1,298 @@ +/* + * Copyright (c) 2024, 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 LazyList methods + * @modules java.base/jdk.internal.lang + * @compile --enable-preview -source ${jdk.version} LazyListTest.java + * @run junit/othervm --enable-preview LazyListTest + */ + +import jdk.internal.lang.StableValue; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import java.io.Serializable; +import java.util.Comparator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.RandomAccess; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; +import java.util.function.IntFunction; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.*; + +final class LazyListTest { + + private static final int ZERO = 0; + private static final int INDEX = 7; + private static final int SIZE = 31; + private static final IntFunction IDENTITY = i -> i; + + @Test + void factoryInvariants() { + assertThrows(NullPointerException.class, () -> StableValue.lazyList(SIZE, null)); + assertThrows(IllegalArgumentException.class, () -> StableValue.lazyList(-1, IDENTITY)); + } + + @Test + void isEmpty() { + assertFalse(StableValue.lazyList(SIZE, IDENTITY).isEmpty()); + assertTrue(StableValue.lazyList(ZERO, IDENTITY).isEmpty()); + } + + @Test + void size() { + assertEquals(SIZE, StableValue.lazyList(SIZE, IDENTITY).size()); + assertEquals(ZERO, StableValue.lazyList(ZERO, IDENTITY).size()); + } + + @Test + void get() { + StableTestUtil.CountingIntFunction cif = new StableTestUtil.CountingIntFunction<>(IDENTITY); + var lazy = StableValue.lazyList(SIZE, cif); + for (int i = 0; i < SIZE; i++) { + assertEquals(i, lazy.get(i)); + assertEquals(i + 1, cif.cnt()); + assertEquals(i, lazy.get(i)); + assertEquals(i + 1, cif.cnt()); + } + } + + @Test + void getException() { + StableTestUtil.CountingIntFunction cif = new StableTestUtil.CountingIntFunction<>(_ -> { + throw new UnsupportedOperationException(); + }); + var lazy = StableValue.lazyList(SIZE, cif); + assertThrows(UnsupportedOperationException.class, () -> lazy.get(INDEX)); + assertEquals(1, cif.cnt()); + assertThrows(UnsupportedOperationException.class, () -> lazy.get(INDEX)); + assertEquals(2, cif.cnt()); + } + + @Test + void toArray() { + assertArrayEquals(new Object[ZERO], StableValue.lazyList(ZERO, IDENTITY).toArray()); + assertArrayEquals(IntStream.range(0, SIZE).boxed().toList().toArray(), StableValue.lazyList(SIZE, IDENTITY).toArray()); + } + + @Test + void toArrayWithArrayLarger() { + Integer[] arr = new Integer[SIZE]; + arr[INDEX] = 1; + assertSame(arr, StableValue.lazyList(INDEX, IDENTITY).toArray(arr)); + assertNull(arr[INDEX]); + } + + @Test + void toArrayWithArraySmaller() { + Integer[] arr = new Integer[INDEX]; + Integer[] actual = StableValue.lazyList(SIZE, IDENTITY).toArray(arr); + assertNotSame(arr, actual); + Integer[] expected = IntStream.range(0, SIZE).boxed().toList().toArray(new Integer[0]); + assertArrayEquals(expected, actual); + } + + @Test + void toArrayWithGenerator() { + Integer[] expected = IntStream.range(0, SIZE).boxed().toList().toArray(Integer[]::new); + Integer[] actual = StableValue.lazyList(SIZE, IDENTITY).toArray(Integer[]::new); + assertArrayEquals(expected, actual); + } + + @Test + void firstIndex() { + var lazy = StableValue.lazyList(SIZE, IDENTITY); + for (int i = INDEX; i < SIZE; i++) { + assertEquals(i, StableValue.lazyList(SIZE, IDENTITY).indexOf(i)); + } + assertEquals(-1, lazy.indexOf(SIZE + 1)); + } + + @Test + void lastIndex() { + var lazy = StableValue.lazyList(SIZE, IDENTITY); + for (int i = INDEX; i < SIZE; i++) { + assertEquals(i, lazy.lastIndexOf(i)); + } + assertEquals(-1, lazy.lastIndexOf(SIZE + 1)); + } + + @Test + void toStringTest() { + assertEquals("[]", StableValue.lazyList(ZERO, IDENTITY).toString()); + assertEquals("[0, 1]", StableValue.lazyList(2, IDENTITY).toString()); + assertEquals(IntStream.range(0, SIZE).boxed().toList().toString(), StableValue.lazyList(SIZE, IDENTITY).toString()); + } + + @Test + void hashCodeTest() { + assertEquals(List.of().hashCode(), StableValue.lazyList(ZERO, IDENTITY).hashCode()); + assertEquals(IntStream.range(0, SIZE).boxed().toList().hashCode(), StableValue.lazyList(SIZE, IDENTITY).hashCode()); + } + + @Test + void equalsTest() { + assertEquals(List.of(), StableValue.lazyList(ZERO, IDENTITY)); + assertEquals(IntStream.range(0, SIZE).boxed().toList(), StableValue.lazyList(SIZE, IDENTITY)); + assertFalse(StableValue.lazyList(SIZE, IDENTITY).equals("A")); + } + + @Test + void iteratorTotal() { + var iterator = StableValue.lazyList(SIZE, IDENTITY).iterator(); + for (int i = 0; i < SIZE; i++) { + assertTrue(iterator.hasNext()); + assertTrue(iterator.hasNext()); + assertEquals(i, iterator.next()); + } + assertFalse(iterator.hasNext()); + assertThrows(NoSuchElementException.class, iterator::next); + AtomicInteger cnt = new AtomicInteger(); + iterator.forEachRemaining(_ -> cnt.incrementAndGet()); + assertEquals(0, cnt.get()); + } + + @Test + void iteratorPartial() { + var iterator = StableValue.lazyList(SIZE, IDENTITY).iterator(); + for (int i = 0; i < INDEX; i++) { + assertTrue(iterator.hasNext()); + assertTrue(iterator.hasNext()); + assertEquals(i, iterator.next()); + } + assertTrue(iterator.hasNext()); + AtomicInteger cnt = new AtomicInteger(); + iterator.forEachRemaining(_ -> cnt.incrementAndGet()); + assertEquals(SIZE - INDEX, cnt.get()); + assertFalse(iterator.hasNext()); + assertThrows(NoSuchElementException.class, iterator::next); + } + + // Immutability + + @ParameterizedTest + @MethodSource("unsupportedOperations") + void unsupported(Operation operation) { + assertThrowsForOperation(UnsupportedOperationException.class, operation); + } + + // Method parameter invariant checking + + @ParameterizedTest + @MethodSource("nullAverseOperations") + void nullAverse(Operation operation) { + assertThrowsForOperation(NullPointerException.class, operation); + } + + @ParameterizedTest + @MethodSource("outOfBoundsOperations") + void outOfBounds(Operation operation) { + assertThrowsForOperation(IndexOutOfBoundsException.class, operation); + } + + static void assertThrowsForOperation(Class expectedType, Operation operation) { + var lazy = StableValue.lazyList(SIZE, IDENTITY); + assertThrows(expectedType, () -> operation.accept(lazy)); + var sub = lazy.subList(1, SIZE / 2); + assertThrows(expectedType, () -> operation.accept(sub)); + var subSub = sub.subList(1, sub.size() / 2); + assertThrows(expectedType, () -> operation.accept(subSub)); + } + + // Implementing interfaces + + @Test + void serializable() { + assertFalse(StableValue.lazyList(SIZE, IDENTITY) instanceof Serializable); + assertFalse(StableValue.lazyList(ZERO, IDENTITY) instanceof Serializable); + assertFalse(StableValue.lazyList(SIZE, IDENTITY).subList(1, INDEX) instanceof Serializable); + } + + @Test + void randomAccess() { + assertInstanceOf(RandomAccess.class, StableValue.lazyList(SIZE, IDENTITY)); + assertInstanceOf(RandomAccess.class, StableValue.lazyList(ZERO, IDENTITY)); + assertInstanceOf(RandomAccess.class, StableValue.lazyList(SIZE, IDENTITY).subList(1, INDEX)); + } + + // Support constructs + + record Operation(String name, + Consumer> consumer) implements Consumer> { + @Override public void accept(List list) { consumer.accept(list); } + @Override public String toString() { return name; } + } + + static Stream nullAverseOperations() { + return Stream.of( + new Operation("forEach", l -> l.forEach(null)), + new Operation("containsAll", l -> l.containsAll(null)), + new Operation("toArray", l -> l.toArray((Integer[]) null)), + new Operation("toArray", l -> l.toArray((IntFunction) null)) + ); + } + + static Stream outOfBoundsOperations() { + return Stream.of( + new Operation("get(-1)", l -> l.get(-1)), + new Operation("get(size)", l -> l.get(l.size())), + new Operation("sublist(-1,)", l -> l.subList(-1, INDEX)), + new Operation("sublist(,size)", l -> l.subList(0, l.size() + 1)), + new Operation("listIter(-1)", l -> l.listIterator(-1)), + new Operation("listIter(size)", l -> l.listIterator(l.size() + 1)) + ); + } + + static Stream unsupportedOperations() { + final Set SET = Set.of(0, 1); + return Stream.of( + new Operation("add(0)", l -> l.add(0)), + new Operation("add(0, 1)", l -> l.add(0, 1)), + new Operation("addAll(col)", l -> l.addAll(SET)), + new Operation("addAll(1, coll)", l -> l.addAll(1, SET)), + new Operation("addFirst(0)", l -> l.addFirst(0)), + new Operation("addLast(0)", l -> l.addLast(0)), + new Operation("clear", List::clear), + new Operation("remove(Obj)", l -> l.remove((Object)1)), + new Operation("remove(1)", l -> l.remove(1)), + new Operation("removeAll", l -> l.removeAll(SET)), + new Operation("removeFirst", List::removeFirst), + new Operation("removeLast", List::removeLast), + new Operation("removeIf", l -> l.removeIf(i -> i % 2 == 0)), + new Operation("replaceAll", l -> l.replaceAll(i -> i + 1)), + new Operation("sort", l -> l.sort(Comparator.naturalOrder())), + new Operation("iterator().remove", l -> l.iterator().remove()), + new Operation("listIter().remove", l -> l.listIterator().remove()), + new Operation("listIter().add", l -> l.listIterator().add(1)), + new Operation("listIter().set", l -> l.listIterator().set(1)) + ); + } + +} From bba7cc0deeca5c02558ea11e85227d56676232d2 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 26 Jun 2024 20:03:35 +0200 Subject: [PATCH 051/327] Fix Serialized issue and improve test --- .../java/util/ImmutableCollections.java | 6 +- .../java/lang/StableValue/LazyListTest.java | 72 +++++++++++-------- 2 files changed, 46 insertions(+), 32 deletions(-) diff --git a/src/java.base/share/classes/java/util/ImmutableCollections.java b/src/java.base/share/classes/java/util/ImmutableCollections.java index a12904510cd34..b6df7a73452ce 100644 --- a/src/java.base/share/classes/java/util/ImmutableCollections.java +++ b/src/java.base/share/classes/java/util/ImmutableCollections.java @@ -1175,7 +1175,7 @@ public T[] toArray(T[] a) { // ---------- Map Implementations ---------- // Not a jdk.internal.ValueBased class; disqualified by fields in superclass AbstractMap - abstract static class AbstractImmutableMap extends AbstractMap implements Serializable { + abstract static class AbstractImmutableMap extends AbstractMap { @Override public void clear() { throw uoe(); } @Override public V compute(K key, BiFunction rf) { throw uoe(); } @Override public V computeIfAbsent(K key, Function mf) { throw uoe(); } @@ -1206,7 +1206,7 @@ public V getOrDefault(Object key, V defaultValue) { } // Not a jdk.internal.ValueBased class; disqualified by fields in superclass AbstractMap - static final class Map1 extends AbstractImmutableMap { + static final class Map1 extends AbstractImmutableMap implements Serializable { @Stable private final K k0; @Stable @@ -1273,7 +1273,7 @@ public int hashCode() { * @param the value type */ // Not a jdk.internal.ValueBased class; disqualified by fields in superclass AbstractMap - static final class MapN extends AbstractImmutableMap { + static final class MapN extends AbstractImmutableMap implements Serializable { @Stable final Object[] table; // pairs of key, value diff --git a/test/jdk/java/lang/StableValue/LazyListTest.java b/test/jdk/java/lang/StableValue/LazyListTest.java index d06a4176abdd0..ea32eb4b3e137 100644 --- a/test/jdk/java/lang/StableValue/LazyListTest.java +++ b/test/jdk/java/lang/StableValue/LazyListTest.java @@ -62,14 +62,14 @@ void factoryInvariants() { @Test void isEmpty() { - assertFalse(StableValue.lazyList(SIZE, IDENTITY).isEmpty()); - assertTrue(StableValue.lazyList(ZERO, IDENTITY).isEmpty()); + assertFalse(newList().isEmpty()); + assertTrue(newEmptyList().isEmpty()); } @Test void size() { - assertEquals(SIZE, StableValue.lazyList(SIZE, IDENTITY).size()); - assertEquals(ZERO, StableValue.lazyList(ZERO, IDENTITY).size()); + assertEquals(SIZE, newList().size()); + assertEquals(ZERO, newEmptyList().size()); } @Test @@ -98,8 +98,8 @@ void getException() { @Test void toArray() { - assertArrayEquals(new Object[ZERO], StableValue.lazyList(ZERO, IDENTITY).toArray()); - assertArrayEquals(IntStream.range(0, SIZE).boxed().toList().toArray(), StableValue.lazyList(SIZE, IDENTITY).toArray()); + assertArrayEquals(new Object[ZERO], newEmptyList().toArray()); + assertArrayEquals(newRegularList().toArray(), newList().toArray()); } @Test @@ -113,31 +113,31 @@ void toArrayWithArrayLarger() { @Test void toArrayWithArraySmaller() { Integer[] arr = new Integer[INDEX]; - Integer[] actual = StableValue.lazyList(SIZE, IDENTITY).toArray(arr); + Integer[] actual = newList().toArray(arr); assertNotSame(arr, actual); - Integer[] expected = IntStream.range(0, SIZE).boxed().toList().toArray(new Integer[0]); + Integer[] expected = newRegularList().toArray(new Integer[0]); assertArrayEquals(expected, actual); } @Test void toArrayWithGenerator() { - Integer[] expected = IntStream.range(0, SIZE).boxed().toList().toArray(Integer[]::new); - Integer[] actual = StableValue.lazyList(SIZE, IDENTITY).toArray(Integer[]::new); + Integer[] expected = newRegularList().toArray(Integer[]::new); + Integer[] actual = newList().toArray(Integer[]::new); assertArrayEquals(expected, actual); } @Test void firstIndex() { - var lazy = StableValue.lazyList(SIZE, IDENTITY); + var lazy = newList(); for (int i = INDEX; i < SIZE; i++) { - assertEquals(i, StableValue.lazyList(SIZE, IDENTITY).indexOf(i)); + assertEquals(i, lazy.indexOf(i)); } assertEquals(-1, lazy.indexOf(SIZE + 1)); } @Test void lastIndex() { - var lazy = StableValue.lazyList(SIZE, IDENTITY); + var lazy = newList(); for (int i = INDEX; i < SIZE; i++) { assertEquals(i, lazy.lastIndexOf(i)); } @@ -146,27 +146,29 @@ void lastIndex() { @Test void toStringTest() { - assertEquals("[]", StableValue.lazyList(ZERO, IDENTITY).toString()); + assertEquals("[]", newEmptyList().toString()); assertEquals("[0, 1]", StableValue.lazyList(2, IDENTITY).toString()); - assertEquals(IntStream.range(0, SIZE).boxed().toList().toString(), StableValue.lazyList(SIZE, IDENTITY).toString()); + assertEquals(newRegularList().toString(), newList().toString()); } @Test void hashCodeTest() { - assertEquals(List.of().hashCode(), StableValue.lazyList(ZERO, IDENTITY).hashCode()); - assertEquals(IntStream.range(0, SIZE).boxed().toList().hashCode(), StableValue.lazyList(SIZE, IDENTITY).hashCode()); + assertEquals(List.of().hashCode(), newEmptyList().hashCode()); + assertEquals(newRegularList().hashCode(), newList().hashCode()); } @Test void equalsTest() { - assertEquals(List.of(), StableValue.lazyList(ZERO, IDENTITY)); - assertEquals(IntStream.range(0, SIZE).boxed().toList(), StableValue.lazyList(SIZE, IDENTITY)); - assertFalse(StableValue.lazyList(SIZE, IDENTITY).equals("A")); + assertTrue(newEmptyList().equals(List.of())); + assertTrue(List.of().equals(newEmptyList())); + assertTrue(newList().equals(newRegularList())); + assertTrue(newRegularList().equals(newList())); + assertFalse(newList().equals("A")); } @Test void iteratorTotal() { - var iterator = StableValue.lazyList(SIZE, IDENTITY).iterator(); + var iterator = newList().iterator(); for (int i = 0; i < SIZE; i++) { assertTrue(iterator.hasNext()); assertTrue(iterator.hasNext()); @@ -181,7 +183,7 @@ void iteratorTotal() { @Test void iteratorPartial() { - var iterator = StableValue.lazyList(SIZE, IDENTITY).iterator(); + var iterator = newList().iterator(); for (int i = 0; i < INDEX; i++) { assertTrue(iterator.hasNext()); assertTrue(iterator.hasNext()); @@ -218,7 +220,7 @@ void outOfBounds(Operation operation) { } static void assertThrowsForOperation(Class expectedType, Operation operation) { - var lazy = StableValue.lazyList(SIZE, IDENTITY); + var lazy = newList(); assertThrows(expectedType, () -> operation.accept(lazy)); var sub = lazy.subList(1, SIZE / 2); assertThrows(expectedType, () -> operation.accept(sub)); @@ -230,16 +232,16 @@ static void assertThrowsForOperation(Class expectedType @Test void serializable() { - assertFalse(StableValue.lazyList(SIZE, IDENTITY) instanceof Serializable); - assertFalse(StableValue.lazyList(ZERO, IDENTITY) instanceof Serializable); - assertFalse(StableValue.lazyList(SIZE, IDENTITY).subList(1, INDEX) instanceof Serializable); + assertFalse(newList() instanceof Serializable); + assertFalse(newEmptyList() instanceof Serializable); + assertFalse(newList().subList(1, INDEX) instanceof Serializable); } @Test void randomAccess() { - assertInstanceOf(RandomAccess.class, StableValue.lazyList(SIZE, IDENTITY)); - assertInstanceOf(RandomAccess.class, StableValue.lazyList(ZERO, IDENTITY)); - assertInstanceOf(RandomAccess.class, StableValue.lazyList(SIZE, IDENTITY).subList(1, INDEX)); + assertInstanceOf(RandomAccess.class, newList()); + assertInstanceOf(RandomAccess.class, newEmptyList()); + assertInstanceOf(RandomAccess.class, newList().subList(1, INDEX)); } // Support constructs @@ -295,4 +297,16 @@ static Stream unsupportedOperations() { ); } + static List newList() { + return StableValue.lazyList(SIZE, IDENTITY); + } + + static List newEmptyList() { + return StableValue.lazyList(ZERO, IDENTITY); + } + + static List newRegularList() { + return IntStream.range(0, SIZE).boxed().toList(); + } + } From c13dbadefedcc2c2319a4d180cabea467a6e9f80 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Thu, 27 Jun 2024 15:00:34 +0200 Subject: [PATCH 052/327] Add test for LazyMap --- .../java/util/ImmutableCollections.java | 20 +- .../java/lang/StableValue/LazyMapTest.java | 247 ++++++++++++++++++ 2 files changed, 257 insertions(+), 10 deletions(-) create mode 100644 test/jdk/java/lang/StableValue/LazyMapTest.java diff --git a/src/java.base/share/classes/java/util/ImmutableCollections.java b/src/java.base/share/classes/java/util/ImmutableCollections.java index b6df7a73452ce..466c5179fe3c4 100644 --- a/src/java.base/share/classes/java/util/ImmutableCollections.java +++ b/src/java.base/share/classes/java/util/ImmutableCollections.java @@ -1470,21 +1470,21 @@ static final class LazyMap @Stable private final Function mapper; @Stable - private final Map> backing; + private final Map> delegate; LazyMap(Set keys, Function mapper) { this.mapper = mapper; - this.backing = StableValueImpl.ofMap(keys); + this.delegate = StableValueImpl.ofMap(keys); } - @Override public boolean containsKey(Object o) { return backing.containsKey(o); } - @Override public int size() { return backing.size(); } + @Override public boolean containsKey(Object o) { return delegate.containsKey(o); } + @Override public int size() { return delegate.size(); } @Override public Set> entrySet() { return new LazyMapEntrySet(); } @ForceInline @Override public V get(Object key) { - final StableValueImpl stable = backing.get(key); + final StableValueImpl stable = delegate.get(key); if (stable == null) { return null; } @@ -1516,8 +1516,8 @@ final class LazyMapEntrySet extends AbstractImmutableSet> { @Stable private final Set>> delegateEntrySet; - public LazyMapEntrySet() { - this.delegateEntrySet = backing.entrySet(); + LazyMapEntrySet() { + this.delegateEntrySet = delegate.entrySet(); } @Override public Iterator> iterator() { return new LazyMapIterator(); } @@ -1533,12 +1533,12 @@ public int hashCode() { } @jdk.internal.ValueBased - private final class LazyMapIterator implements Iterator> { + final class LazyMapIterator implements Iterator> { @Stable private final Iterator>> delegateIterator; - private LazyMapIterator() { + LazyMapIterator() { this.delegateIterator = delegateEntrySet.iterator(); } @@ -1547,7 +1547,7 @@ private LazyMapIterator() { @Override public Entry next() { final Map.Entry> inner = delegateIterator.next(); - final K key = delegateIterator.next().getKey(); + final K key = inner.getKey(); return new KeyValueHolder<>(key, computeIfUnset(key, inner.getValue())); } diff --git a/test/jdk/java/lang/StableValue/LazyMapTest.java b/test/jdk/java/lang/StableValue/LazyMapTest.java new file mode 100644 index 0000000000000..dc8547178ad8c --- /dev/null +++ b/test/jdk/java/lang/StableValue/LazyMapTest.java @@ -0,0 +1,247 @@ +/* + * Copyright (c) 2024, 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 LazyMap methods + * @modules java.base/jdk.internal.lang + * @compile --enable-preview -source ${jdk.version} LazyMapTest.java + * @run junit/othervm --enable-preview LazyMapTest + */ + +import jdk.internal.lang.StableValue; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import java.io.Serializable; +import java.util.AbstractMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.*; + +final class LazyMapTest { + + private static final int NOT_PRESENT = 147; + private static final int KEY = 7; + private static final Set KEYS = Set.of(0, KEY, 13); + private static final Set EMPTY = Set.of(); + private static final Function IDENTITY = Function.identity(); + + @Test + void factoryInvariants() { + assertThrows(NullPointerException.class, () -> StableValue.lazyMap(KEYS, null)); + assertThrows(NullPointerException.class, () -> StableValue.lazyMap(null, IDENTITY)); + } + + @Test + void isEmpty() { + assertFalse(newMap().isEmpty()); + assertTrue(newEmptyMap().isEmpty()); + } + + @Test + void size() { + assertEquals(KEYS.size(), newMap().size()); + assertEquals(EMPTY.size(), newEmptyMap().size()); + } + + @Test + void get() { + StableTestUtil.CountingFunction cf = new StableTestUtil.CountingFunction<>(IDENTITY); + var lazy = StableValue.lazyMap(KEYS, cf); + int cnt = 1; + for (int i : KEYS) { + assertEquals(i, lazy.get(i)); + assertEquals(cnt, cf.cnt()); + assertEquals(i, lazy.get(i)); + assertEquals(cnt++, cf.cnt()); + } + assertNull(lazy.get(NOT_PRESENT)); + } + + @Test + void getException() { + StableTestUtil.CountingFunction cf = new StableTestUtil.CountingFunction<>(_ -> { + throw new UnsupportedOperationException(); + }); + var lazy = StableValue.lazyMap(KEYS, cf); + assertThrows(UnsupportedOperationException.class, () -> lazy.get(KEY)); + assertEquals(1, cf.cnt()); + assertThrows(UnsupportedOperationException.class, () -> lazy.get(KEY)); + assertEquals(2, cf.cnt()); + } + + @Test + void containsKey() { + var lazy = newMap(); + for (int i : KEYS) { + assertTrue(lazy.containsKey(i)); + } + assertFalse(lazy.containsKey(NOT_PRESENT)); + } + + @Test + void containsValue() { + var lazy = newMap(); + for (int i : KEYS) { + assertTrue(lazy.containsValue(i)); + } + assertFalse(lazy.containsValue(NOT_PRESENT)); + } + + @Test + void forEach() { + var lazy = newMap(); + Set> expected = KEYS.stream() + .map(i -> new AbstractMap.SimpleImmutableEntry<>(i , i)) + .collect(Collectors.toSet()); + Set> actual = new HashSet<>(); + lazy.forEach((k, v) -> actual.add(new AbstractMap.SimpleImmutableEntry<>(k , v))); + assertEquals(expected, actual); + } + + @Test + void toStringTest() { + assertEquals("{}", newEmptyMap().toString()); + assertEquals("{" + KEY + "=" + KEY + "}", StableValue.lazyMap(Set.of(KEY), IDENTITY).toString()); + String actual = newMap().toString(); + assertTrue(actual.startsWith("{")); + for (int key:KEYS) { + assertTrue(actual.contains(key + "=" + key)); + } + assertTrue(actual.endsWith("}")); + } + + @Test + void hashCodeTest() { + assertEquals(Map.of().hashCode(), newEmptyMap().hashCode()); + assertEquals(newRegularMap().hashCode(), newMap().hashCode()); + } + + @Test + void equalsTest() { + assertTrue(newEmptyMap().equals(Map.of())); + assertTrue(Map.of().equals(newEmptyMap())); + assertTrue(newMap().equals(newRegularMap())); + assertTrue(newRegularMap().equals(newMap())); + assertFalse(newMap().equals("A")); + } + + @Test + void entrySet() { + var regular = newRegularMap().entrySet(); + var actual = newMap().entrySet(); + assertTrue(regular.equals(actual)); + assertTrue(actual.equals(regular)); + assertTrue(regular.equals(actual)); + } + + @Test + void iterator() { + System.out.println("ITERATOR"); + var iterator = newMap().entrySet().iterator(); + while (iterator.hasNext()) { + System.out.println("iterator.next() = " + iterator.next()); + } + } + + // Immutability + @ParameterizedTest + @MethodSource("unsupportedOperations") + void unsupported(Operation operation) { + assertThrowsForOperation(UnsupportedOperationException.class, operation); + } + + // Method parameter invariant checking + + @ParameterizedTest + @MethodSource("nullAverseOperations") + void nullAverse(Operation operation) { + assertThrowsForOperation(NullPointerException.class, operation); + } + + static void assertThrowsForOperation(Class expectedType, Operation operation) { + var lazy = newMap(); + assertThrows(expectedType, () -> operation.accept(lazy)); + } + + // Implementing interfaces + + @Test + void serializable() { + assertFalse(newMap() instanceof Serializable); + assertFalse(newEmptyMap() instanceof Serializable); + } + + // Support constructs + + record Operation(String name, + Consumer> consumer) implements Consumer> { + @java.lang.Override + public void accept(Map map) { consumer.accept(map); } + @java.lang.Override + public String toString() { return name; } + } + + static Stream nullAverseOperations() { + return Stream.of( + new Operation("forEach", m -> m.forEach(null)) + ); + } + + static Stream unsupportedOperations() { + return Stream.of( + new Operation("clear", Map::clear), + new Operation("compute", m -> m.compute(KEY, (_, _) -> 1)), + new Operation("computeIfAbsent", m -> m.computeIfAbsent(KEY, _ -> 1)), + new Operation("computeIfPresent", m -> m.computeIfPresent(KEY, (_, _) -> 1)), + new Operation("merge", m -> m.merge(KEY, KEY, (a, _) -> a)), + new Operation("put", m -> m.put(0, 0)), + new Operation("putAll", m -> m.putAll(Map.of())), + new Operation("remove1", m -> m.remove(KEY)), + new Operation("remove2", m -> m.remove(KEY, KEY)), + new Operation("replace2", m -> m.replace(KEY, 1)), + new Operation("replace3", m -> m.replace(KEY, KEY, 1)), + new Operation("replaceAll", m -> m.replaceAll((a, _) -> a)) + ); + } + + static Map newMap() { + return StableValue.lazyMap(KEYS, IDENTITY); + } + + static Map newEmptyMap() { + return StableValue.lazyMap(EMPTY, IDENTITY); + } + + static Map newRegularMap() { + return KEYS.stream().collect(Collectors.toMap(IDENTITY, IDENTITY)); + } + +} From fbec4c8d0511a32899a82dac88094d6c9b5d0f60 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Fri, 28 Jun 2024 11:29:09 +0200 Subject: [PATCH 053/327] Updat JEP --- .../jdk/internal/lang/StableValue.java | 4 +- .../internal/lang/stable/CachedFunction.java | 3 + .../lang/stable/CachedIntFunction.java | 11 +- .../StableValue/CachingIntFunctionTest.java | 2 +- test/jdk/java/lang/StableValue/JEP.md | 430 +++++++++--------- test/jdk/java/lang/StableValue/JepTest.java | 57 ++- 6 files changed, 278 insertions(+), 229 deletions(-) diff --git a/src/java.base/share/classes/jdk/internal/lang/StableValue.java b/src/java.base/share/classes/jdk/internal/lang/StableValue.java index fcfdff9de6ccc..446fef2e0348d 100644 --- a/src/java.base/share/classes/jdk/internal/lang/StableValue.java +++ b/src/java.base/share/classes/jdk/internal/lang/StableValue.java @@ -59,8 +59,8 @@ * To create a new fresh (unset) StableValue, use the {@linkplain StableValue#newInstance()} * factory. *

- * The utility class {@linkplain StableValues} contains a number of convenience methods - * for creating constructs involving StableValue: + * This class contains a number of convenience methods for creating constructs + * involving StableValue: * * A cached (also called "memoized") Supplier, where a given {@code original} * Supplier is guaranteed to be successfully invoked at most once even in a multithreaded diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/CachedFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/CachedFunction.java index 189ff4c0ff402..50f5c2e1f8370 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/CachedFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/CachedFunction.java @@ -6,6 +6,9 @@ import java.util.Set; import java.util.function.Function; +// Note: It would be possible to just use `LazyMap::get` with some additional logic +// instead of this class but explicitly providing a class like this provides better +// debug capability, exception handling, and may provide better performance. public record CachedFunction(Map> stables, Function original) implements Function { @ForceInline diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/CachedIntFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/CachedIntFunction.java index 5863b1ceaefee..b9bec6272b8bf 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/CachedIntFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/CachedIntFunction.java @@ -5,12 +5,21 @@ import java.util.List; import java.util.function.IntFunction; +// Note: It would be possible to just use `LazyList::get` instead of this +// class but explicitly providing a class like this provides better +// debug capability, exception handling, and may provide better performance. public record CachedIntFunction(List> stables, IntFunction original) implements IntFunction { @ForceInline @Override public R apply(int value) { - final StableValueImpl stable = stables.get(value); + final StableValueImpl stable; + try { + // Todo: Will the exception handling here impair performance? + stable = stables.get(value); + } catch (IndexOutOfBoundsException e) { + throw new IllegalArgumentException(e); + } R r = stable.value(); if (r != null) { return StableValueImpl.unwrap(r); diff --git a/test/jdk/java/lang/StableValue/CachingIntFunctionTest.java b/test/jdk/java/lang/StableValue/CachingIntFunctionTest.java index b945ca5a6574e..ac00c54c5cdbf 100644 --- a/test/jdk/java/lang/StableValue/CachingIntFunctionTest.java +++ b/test/jdk/java/lang/StableValue/CachingIntFunctionTest.java @@ -49,7 +49,7 @@ void basic() { assertEquals(1, cached.apply(1)); assertEquals(1, cif.cnt()); assertEquals("CachedIntFunction[stables=[StableValue.unset, StableValue[1]], original=" + cif + "]", cached.toString()); - assertThrows(IndexOutOfBoundsException.class, () -> cached.apply(SIZE + 1)); + assertThrows(IllegalArgumentException.class, () -> cached.apply(SIZE + 1)); } @Test diff --git a/test/jdk/java/lang/StableValue/JEP.md b/test/jdk/java/lang/StableValue/JEP.md index b4a90925e50a9..cce5f681324ae 100644 --- a/test/jdk/java/lang/StableValue/JEP.md +++ b/test/jdk/java/lang/StableValue/JEP.md @@ -1,9 +1,10 @@ -# Stable Values (Preview) +# Stable Values & Collections (Preview) ## Summary -Introduce a _Stable Values_ API, which provides immutable value holders where elements are initialized -_at most once_. Stable Values offer the performance and safety benefits of final fields, while offering greater flexibility as to the timing of initialization. This is a [preview API](https://openjdk.org/jeps/12). +Introduce a _Stable Values & Collections API_, which provides performant immutable value holders where elements +are initialized _at most once_. Stable Values & Collections offer the performance and safety benefits of +final fields, while offering greater flexibility as to the timing of initialization. This is a [preview API](https://openjdk.org/jeps/12). ## Goals @@ -16,23 +17,40 @@ _at most once_. Stable Values offer the performance and safety benefits of final - It is not a goal to provide additional language support for expressing lazy computation. This might be the subject of a future JEP. -- It is not a goal to provide lazy collections such as a lazy `List` or a lazy `Map`. -This might be the subject of a future JEP. - It is not a goal to prevent or deprecate existing idioms for expressing lazy initialization. ## Motivation +Some internal JDK classes are relying heavily on the annotation `jdk.internal.vm.annotation.@Stable` +to mark scalar and array fields whose values or elements will change *at most once*, thereby providing +crucial performance, energy efficiency, and flexibility benefits. + +Unfortunately, the powerful `@Stable` annotation cannot be used directly by client code thereby severely +restricting its applicability. The Stable Values & Collections API rectifies this imbalance +between internal and client code by providing safe wrappers around the `@Stable` annotation. Hence, all _the +important benefits of `@Stable` are now made available to regular Java developers and third-party +library developers_. + +One of the benefits with `@Stable` is it makes a marked field eligible for [constant-folding](https://en.wikipedia.org/wiki/Constant_folding). +Publicly exposing `@Stable` without a safe API, like the Stable Values & Collections API, would have +rendered it unsafe as further updating a `@Stable` field after its initial update will result +in undefined behavior, as the JIT compiler might have *already* constant-folded the (now overwritten) +field value. + +### Existing solutions + Most Java developers have heard the advice "prefer immutability" (Effective Java, Third Edition, Item 17, by Joshua Bloch). Immutability confers many advantages including: * an immutable object can only be in one state * the invariants of an immutable object can be enforced by its constructor * immutable objects can be freely shared across threads -* immutability enables all manner of runtime optimizations. +* immutability enables all manner of runtime optimizations Java's main tool for managing immutability is `final` fields (and more recently, `record` classes). Unfortunately, `final` fields come with restrictions. Final instance fields must be set by the end of -the constructor, and `static final` fields during class initialization. Moreover, the order in which `final` field initializers are executed is determined by the [textual order](https://docs.oracle.com/javase/specs/jls/se7/html/jls-13.html#jls-12.4.1) +the constructor, and `static final` fields during class initialization. Moreover, the order in which +`final` field initializers are executed is determined by the [textual order](https://docs.oracle.com/javase/specs/jls/se7/html/jls-13.html#jls-12.4.1) and is then made explicit in the resulting class file. As such, the initialization of a `final` field is fixed in time; it cannot be arbitrarily moved forward. In other words, developers cannot cause specific constants to be initialized after the class or object is initialized. @@ -42,7 +60,7 @@ devised several strategies to ameliorate this imbalance, but none are ideal. For instance, monolithic class initializers can be broken up by leveraging the -laziness already built into class loading. Often referred to as the +laziness already built into class loading. Often referred to as the [_class-holder idiom_](https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom), this technique moves lazily initialized state into a helper class which is then loaded on-demand, so its initialization is only performed when the data is @@ -50,7 +68,7 @@ actually needed, rather than unconditionally initializing constants when a class is first referenced: ``` // ordinary static initialization -private static final Logger LOGGER = Logger.getLogger("com.foo.Bar"); +private static final Logger LOGGER = Logger.getLogger("com.company.Foo"); ... LOGGER.log(Level.DEBUG, ...); ``` @@ -59,7 +77,7 @@ we can defer initialization until we actually need it: // Initialization-on-demand holder idiom Logger logger() { class Holder { - static final Logger LOGGER = Logger.getLogger("com.foo.Bar"); + static final Logger LOGGER = Logger.getLogger("com.company.Foo"); } return Holder.LOGGER; } @@ -69,10 +87,10 @@ LOGGER.log(Level.DEBUG, ...); The code above ensures that the `Logger` object is created only when actually required. The (possibly expensive) initializer for the logger lives in the nested `Holder` class, which will only be initialized when the `logger` method -accesses the `LOGGER` field. While this idiom works well, its reliance on the -class loading process comes with significant drawbacks. First, each constant +accesses the `LOGGER` field. While this idiom works well, its reliance on the +class loading process comes with significant drawbacks. First, each constant whose computation needs to be deferred generally requires its own holder -class, thus introducing a significant static footprint cost. Second, this idiom +class, thus introducing a significant static footprint cost. Second, this idiom is only really applicable if the field initialization is suitably isolated, not relying on any other parts of the object state. @@ -99,7 +117,7 @@ class Foo { synchronized (this) { v = logger; if (v == null) { - logger = v = Logger.getLogger("com.foo.Bar"); + logger = v = Logger.getLogger("com.company.Foo"); } } } @@ -123,7 +141,8 @@ similar optimizations in existing Java implementations is when a `MethodHandle` in a `static final` field, allowing the runtime to generate machine code that is competitive with direct invocation of the corresponding method. -Furthermore, the idiom shown above needs to be modified to properly handle `null` values, for example using a [sentinel](https://en.wikipedia.org/wiki/Sentinel_value) value. +Furthermore, the idiom shown above needs to be modified to properly handle `null` values, for example +using a [sentinel](https://en.wikipedia.org/wiki/Sentinel_value) value. The situation is even worse when clients need to operate on a _collection_ of immutable values. @@ -173,60 +192,45 @@ String errorPage = ErrorMessages.errorPage(2); ``` Unfortunately, this approach provides a plethora of challenges. First, retrieving the values -from a static array is slow, as said values cannot be [constant-folded](https://en.wikipedia.org/wiki/Constant_folding). Even worse, access to the array is guarded by synchronization that is -not only slow but will block access to the array for all elements whenever one of the elements is -under computation. Furthermore, the class holder idiom (see above) is undoubtedly insufficient in -this case, as the number of required holder classes is *statically unbounded* - it -depends on the value of the parameter `SIZE` which may change in future variants of the code. +from a static array is slow, as said values cannot be constant-folded. Even worse, access to the +array is guarded by synchronization that is not only slow but will block access to the array for +all elements whenever one of the elements is under computation. Furthermore, the class holder idiom +(see above) is undoubtedly insufficient in this case, as the number of required holder classes is +*statically unbounded* - it depends on the value of the parameter `SIZE` which may change in future +variants of the code. What we are missing -- in all cases -- is a way to *promise* that a constant will be initialized by the time it is used, with a value that is computed at most once. Such a mechanism would give -the Java runtime maximum opportunity to stage and optimize its computation, thus avoiding the penalties (static footprint, loss of runtime optimizations) that plague the workarounds shown above. Moreover, such a mechanism should gracefully scale to handle collections of constant values, while retaining efficient computer resource management. - -The attentive reader might have noticed the similarity between what is sought after here and the JDK -internal annotation`jdk.internal.vm.annotation.@Stable`. This annotation is used by *JDK code* to -mark scalar and array variables whose values or elements will change *at most once*. This annotation -is powerful and often crucial to achieving optimal performance, but it is also easy to misuse: -further updating a `@Stable` field after its initial update will result in undefined behavior, as the -JIT compiler might have *already* constant-folded the (now overwritten) field value. In other words, -what we are after is a *safe* and *efficient* wrapper around the `@Stable` mechanism - in the form of -a new Java SE API that might be enjoyed by _all_ client and 3rd-party Java code (and not the JDK -alone). +the Java runtime maximum opportunity to stage and optimize its computation, thus avoiding the penalties +(static footprint, loss of runtime optimizations) that plague the workarounds shown above. Moreover, such +a mechanism should gracefully scale to handle collections of constant values, while retaining efficient +computer resource management. ## Description -### Preview feature - -Stable Values is a [preview API](https://openjdk.org/jeps/12), disabled by default. -To use the Stable Value APIs, the JVM flag `--enable-preview` must be passed in, as follows: - -- Compile the program with `javac --release 24 --enable-preview Main.java` and run it with `java --enable-preview Main`; or, - -- When using the source code launcher, run the program with `java --source 24 --enable-preview Main.java`; or, - -- When using `jshell`, start it with `jshell --enable-preview`. - -### Outline - -The Stable Values API defines functions and an interface so that client code in libraries and applications can +The Stable Values & Collections API defines an interface so that client code in libraries and applications can - Define and use stable (scalar) values: - [`StableValue.newInstance()`](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/java/lang/StableValue.html) -- Define collections: - - [`StableValue.ofList(int size)`](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/java/lang/StableValue.html#ofList(int)) - - [`StableValue.ofMap(Set keys)`](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/java/lang/StableValue.html#ofMap(java.util.Set)) +- Define various _cached_ functions: + - [`StableValue.newCachedSupplier()`](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/java/lang/StableValue.html#newCachedSupplier(Supplier)) + - [`StableValue.newCachedIntFunction()`](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/java/lang/StableValue.html#newCachedIntFunction(int,IntFunction)) + - [`StableValue.newCachedFunction()`](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/java/lang/StableValue.html#newCachedFunction(Set,Function)) +- Define _lazy_ collections: + - [`StableValue.lazyList(int size)`](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/java/lang/StableValue.html#ofList(int)) + - [`StableValue.lazyMap(Set keys)`](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/java/lang/StableValue.html#ofMap(java.util.Set)) -The Stable Values API resides in the [java.lang](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/java/lang/package-summary.html) package of the [java.base](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/module-summary.html) module. +The Stable Values & Collections API resides in the [java.lang](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/java/lang/package-summary.html) package of the [java.base](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/module-summary.html) module. ### Stable values -A _stable value_ is a holder object that is set at most once whereby it -goes from "unset" to "set". It is expressed as an object of type `jdk.internal.lang.StableValue`, -which, like `Future`, is a holder for some computation that may or may not have occurred yet. -Fresh (unset) `StableValue` instances are created via the factory method `StableValue::newInstance`: +A _stable value_ is a thin, atomic, non-blocking, thread-safe, set-at-most-once, stable value holder +eligible for certain JVM optimizations if set to a value. It is expressed as an object of type +`jdk.lang.StableValue`, which, like `Future`, is a holder for some computation that may or may not have +occurred yet. Fresh (unset) `StableValue` instances are created via the factory method `StableValue::newInstance`: ``` -class Bar { +class Foo { // 1. Declare a Stable field private static final StableValue LOGGER = StableValue.newInstance(); @@ -234,7 +238,7 @@ class Bar { if (!LOGGER.isSet()) { // 2. Set the stable value _after_ the field was declared - LOGGER.trySet(Logger.getLogger("com.foo.Bar")); + LOGGER.trySet(Logger.getLogger("com.company.Foo")); } // 3. Access the stable value with as-declared-final performance @@ -242,7 +246,8 @@ class Bar { } } ``` -Setting a stable value is an atomic, thread-safe operation, i.e. `StableValue::trySet`, + +Setting a stable value is an atomic, non-blocking, thread-safe operation, i.e. `StableValue::trySet`, either results in successfully initializing the `StableValue` to a value, or retaining an already set value. This is true regardless of whether the stable value is accessed by a single thread, or concurrently, by multiple threads. @@ -250,62 +255,76 @@ thread, or concurrently, by multiple threads. A stable value may be set to `null` which then will be considered its set value. Null-averse applications can also use `StableValue>`. -In many ways, this is similar to the holder-class idiom in the sense it offers the same -performance and constant-folding characteristics. It also incurs a lower static footprint -since no additional class is required. +When retrieving values, `StableValue` instances holding reference values can be faster +than reference values managed via double-checked-idiom constructs as stable values rely +on explicit memory barriers rather than performing volatile access on each retrieval +operation. + +In addition, stable values are eligible for constant folding optimizations by the JVM. In many +ways, this is similar to the holder-class idiom in the sense it offers the same +performance and constant-folding characteristics but, `StableValue` incurs a lower static +footprint since no additional class is required. -However, there is _an important distinction_; several threads may invoke the `Logger::getLogger` +Looking at the basic example above, it becomes evident, several threads may invoke the `Logger::getLogger` method simultaneously if they call the `logger()` method at about the same time. Even though `StableValue` will guarantee, that only one of these results will ever be exposed to the many competing threads, there might be applications where it is a requirement, that a supplying method is -only called once. +called *only once*. This brings us to the introduction of _cached functions_. + +### Cached functions + +So far, we have talked about the fundamental features of StableValue as securely +wrapped `@Stable` value holders. However, it has become apparent, stable primitives are amenable +to composition with other constructs in order to create more high-level and powerful features. -In such cases, it is possible to compute and set an unset value on-demand as shown in this example in which -case `StableValue` will uphold the invoke-at-most-once invariant for the provided `Supplier`: +[Cached (or Memoized) functions](https://en.wikipedia.org/wiki/Memoization) are functions where the output for a +particular input value is computed only once and is remembered such that remembered outputs can be +reused for subsequent calls with recurring input values. + +In cases where the invoke-at-most-once property of a `Supplier` is important, the +Stable Values & Collections API offers a _cached supplier_ which is a caching, thread-safe, stable, +lazily computed `Supplier` that records the value of an _original_ `Supplier` upon being first +accessed via its `Supplier::get` method. In a multi-threaded scenario, competing threads will block +until the first thread has computed a cached value. Unsurprisingly, the cached value is +stored internally in a stable value. + +Here is how the code in the previous example can be improved using a cached supplier: ``` -class Bar { - // 1. Declare a stable field - private static final StableValue LOGGER = StableValue.newInstance(); +class Foo { + + // 1. Centrally declare a caching supplier and define how it should be computed + private static final Supplier LOGGER = + StableValue.newCachingSupplier( () -> Logger.getLogger("com.company.Foo"), null); static Logger logger() { - // 2. Access the stable value with as-declared-final performance + // 2. Access the cached value with as-declared-final performance // (single evaluation made before the first access) - return LOGGER.computeIfUnset( () -> Logger.getLogger("com.foo.Bar") ); + return LOGGER.get(); } } ``` -When retrieving values, `StableValue` instances holding reference values can be faster -than reference values managed via double-checked-idiom constructs as stable values rely -on explicit memory barriers rather than performing volatile access on each retrieval -operation. In addition, stable values are eligible for constant folding optimizations by the JVM. - -### Stable collections +Note: the last `null` parameter signifies an optional thread factory that will be explained at the end +of this chapter. -The Stable Values API provides constructs that allow the creation and handling of a -*`List` of stable value elements*. Lists of lazily computed values are objects of type -`List>`. Consequently, each element in the list enjoys the same properties as a -`StableValue`. +In the example above, the original `Supplier` provided is invoked at most once per loading of the containing +class `Foo` (`Foo`, in turn, can be loaded at most once into any given `ClassLoader`) and it is backed +by a lazily computed stable value upholding the invoke-at-most-once invariant. -Like a `StableValue` object, a `List` of stable value elements is created via a factory method by -providing the size of the desired `List`: - -``` -static List> StableValue.ofList(int size) { ... } -``` - -This allows for improving the handling of lists with stable values and enables a much better -implementation of the `ErrorMessages` class mentioned earlier. Here is an improved version -of the class which is now using the newly proposed API: +Similarly to how a `Supplier` can be cached using a backing stable value, a similar pattern +can be used for an `IntFunction` that will record its cached values in a backing list of +stable value elements. Here is how the error message example above can be improved using +a caching `IntFunction`: ``` class ErrorMessages { private static final int SIZE = 8; - - // 1. Declare a stable list of default error pages to serve up - private static final List> MESSAGES = StableValue.ofList(SIZE); + + // 1. Centrally declare a cached IntFunction backed by a list of StableValue elements + private static final IntFunction ERROR_FUNCTION = + StableValue.newCachingIntFunction(SIZE, ErrorMessages::readFromFile, null); // 2. Define a function that is to be called the first // time a particular message number is referenced @@ -316,22 +335,12 @@ class ErrorMessages { throw new UncheckedIOException(e); } } - - static String errorPage(int messageNumber) { - // 3. Access the stable list element with as-declared-final performance - // (evaluation made before the first access) - return MESSAGES.get(messageNumber) - .mapIfUnset(messageNumber, ErrorMessages::readFromFile); - } - + + // 3. Access the cached element with as-declared-final performance + // (evaluation made before the first access) + String msg = ERROR_FUNCTION.apply(2); } -``` - -Just like before, we can perform retrieval of error pages like this: - -``` -String errorPage = ErrorMessages.errorPage(2); - + // // // @@ -339,146 +348,143 @@ String errorPage = ErrorMessages.errorPage(2); // ``` -Note how there's only one field of type `List>` to initialize even though every -computation is performed independently of the other element of the list when accessed (i.e. no -blocking will occur across threads computing distinct elements simultaneously). Also, the -`IntSupplier` provided at computation is only invoked at most once for each distinct index. The -Stable Values API allows modeling this cleanly, while still preserving good constant-folding -guarantees and integrity of updates in the case of multi-threaded access. - -It should be noted that even though a lazily computed list of stable elements might mutate its -internal state upon external access, it is _still shallowly immutable_ because _no first-level -change can ever be observed by an external observer_. This is similar to other immutable classes, -such as `String` (which internally caches its `hash` value), where they might rely on mutable -internal states that are carefully kept internally and that never shine through to the outside world. +Finally, the most general cached function variant provided is a cached `Function` which, for example, +can make sure `Logger::getLogger` in one of the first examples above is invoked at most once +per input value (provided it executes successfully) in a multi-threaded environment. Such a cached +`Function` is almost always faster and more resource efficient than a `ConcurrentHashMap`. -Just as a `List` can be lazily computed, a `Map` of lazily computed stable values can also be defined -and used similarly. In the example below, we lazily compute a map's stable values for an enumerated -collection of pre-defined keys: +Here is what a caching `Function` lazily holding two loggers could look like: ``` -class MapDemo { +class Cahced { + + // 1. Centrally declare a cached function backed by a map of stable values + private static final Function LOGGERS = + StableValue.newCachingFunction(Set.of("com.company.Foo", "com.company.Bar"), + Logger::getLogger, null); - // 1. Declare a stable map of loggers with two allowable keys: - // "com.foo.Bar" and "com.foo.Baz" - static final Map> LOGGERS = - StableValue.ofMap(Set.of("com.foo.Bar", "com.foo.Baz")); + private static final String NAME = "com.company.Foo"; - // 2. Access the memoized map with as-declared-final performance - // (evaluation made before the first access) - static Logger logger(String name) { - return LOGGERS.get(name) - .mapIfUnset(name, Logger::getLogger); - } + // 2. Access the cached value via the function with as-declared-final + // performance (evaluation made before the first access) + Logger logger = LOGGERS.apply(NAME); } ``` -This concept allows declaring a large number of stable values which can be easily retrieved using -arbitrarily, but pre-specified, keys in a resource-efficient and performant way. For example, -high-performance, non-evicting caches may now be easily and reliably realized. +It should be noted that the enumerated set of valid inputs given at creation time constitutes the only valid +input keys for the cached function. Providing a non-valid input for a cached `Function` (or a cached `IntFunction`) +would incur an `IllegalArgumentException`. -It is worth remembering, that the stable collections all promise the function provided at computation -(used to lazily compute elements or values) is invoked at most once per index or key (absent any Exceptions); -even though used from several threads. +An advantage with cached functions, compared to working directly with `StableValue` instances, is that the +initialization logic can be centralized and maintained in a single place, usually at the same place where +the cached function is defined. -### Memoized functions +Additional cached function types, such a cached `Predicate` (backed by a lazily computed +`Map>`) or a cached `BiFunction` can be custom-made. An astute reader will be able +to write such constructs in a few lines. -So far, we have talked about the fundamental features of Stable Values as securely -wrapped `@Stable` value holders. However, it has become apparent, stable primitives are amenable -to composition with other constructs in order to create more high-level and powerful features. +#### Background threads -[Memoized functions](https://en.wikipedia.org/wiki/Memoization) are functions where the output for a -particular input value is computed only once and is remembered such that remembered outputs can be -reused for subsequent calls with recurring input values. Here is how we could make sure -`Logger.getLogger("com.foo.Bar")` in one of the first examples above is invoked at most once -(provided it executes successfully) in a multi-threaded environment: +As noted above, the cached-returning factories in the Stable Values & Collections API offers an optional +tailing thread factory parameter from which new value-computing background threads will be created: ``` -class Memoized { - - // 1. Declare a memoized (cached) function backed by a stable map - private static final Function LOGGERS = - StableValues.memoizedFunction(Set.of("com.foo.Bar", "com.foo.Baz"), - Logger::getLogger, null); +// 1. Centrally declare a cached function backed by a map of stable values +// computed in the background by two distinct virtual threads. +private static final Function LOGGERS = + StableValue.newCachingFunction(Set.of("com.company.Foo", "com.company.Bar"), + Logger::getLogger, + // Create cheap virtual threads for background computation + Thread.ofVirtual().factory()); + +... Somewhere else in the code - private static final String NAME = "com.foo.Baz"; +private static final String NAME = "com.company.Foo"; - // 2. Access the memoized value via the function with as-declared-final - // performance (evaluation made before the first access) - Logger logger = LOGGERS.apply(NAME); -} +// 2. Access the cached value via the function with as-declared-final +// performance (evaluation made before the first access) +Logger logger = LOGGERS.apply(NAME); ``` -Note: the last `null` parameter signifies an optional thread factory that will be explained later on. -In the example above, for each key, the function is invoked at most once per loading of the containing class -`Memoized` (`Memoized`, in turn, can be loaded at most once into any given `ClassLoader`) and it is backed by a -_stable map_ with lazily computed values which upholds the invoke-at-most-once-per-key invariant. +This can provide a best-of-several-worlds situation where the cached function can be quickly defined (as no +computation is made by the defining thread), the holder value is computed in background threads (thus neither +interfering significantly with the critical startup path nor with future accessing threads), and the threads actually +accessing the holder value can access the holder value with as-if-final performance and without having to compute +a holder value. This is true under the assumption, that the background threads can complete computation before accessing +threads requires a holder value. If this is not the case, at least some reduction of blocking time can be enjoyed as +the background threads have had a head start compared to the accessing threads. -It should be noted that the enumerated set of valid inputs given at creation time constitutes the only valid input keys for the memoized function. +### Stable collections -Similarly to how a `Function` can be memoized using a backing lazily computed map, the same pattern -can be used for an `IntFunction` that will record its cached value in a backing _stable list_: +The Stable Values & Collections API also provides factories that allow the creation of new +collection variants that are lazy, shallowly immutable, and stable: -``` -class ErrorMessages { +- `List` of stable elements +- `Map` of stable values - private static final int SIZE = 8; +Lists of lazily computed stable elements are objects of type `List` where each +element in such a list enjoys the same properties as a `StableValue`. - // 1. Declare a memoized IntFunction backed by a stable list - private static final IntFunction ERROR_FUNCTION = - StableValues.memoizedIntFunction(SIZE, ErrorMessages::readFromFile, null); +Like a `StableValue` object, a lazy `List` of stable value elements is created via a factory +method while additionally providing the `size` of the desired `List` and a mapper of type +`IntFunction` to be used when computing the lazy elements on demand: - // 2. Define a function that is to be called the first - // time a particular message number is referenced - private static String readFromFile(int messageNumber) { - try { - return Files.readString(Path.of("message-" + messageNumber + ".html")); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } +``` +List lazyList = StableValue.lazyList(size, mapper); +``` - // 3. Access the memoized list element with as-declared-final performance - // (evaluation made before the first access) - String msg = ERROR_FUNCTION.apply(2); +Note how there's only one field of type `List` to initialize even though every +computation is performed independently of the other element of the list when accessed (i.e. no +blocking will occur across threads computing distinct elements simultaneously). Also, the +`IntSupplier` mapper provided at creation is only invoked at most once for each distinct input +value. The Stable Values & Collections API allows modeling this cleanly, while still preserving +good constant-folding guarantees and integrity of updates in the case of multi-threaded access. - // - // - // - // Payment was denied: Insufficient funds. - // +It should be noted that even though a lazily computed list of stable elements might mutate its +internal state upon external access, it is _still shallowly immutable_ because _no first-level +change can ever be observed by an external observer_. This is similar to other immutable classes, +such as `String` (which internally caches its `hash` value), where they might rely on mutable +internal states that are carefully kept internally and that never shine through to the outside world. + +Just as a lazy `List` can be created, a `Map` of lazily computed stable values can also be defined +and used similarly: + +``` +// 1. Declare a lazy stable map of loggers with two allowable keys: +// "com.company.Foo" and "com.company.Bar" +static final Map LOGGERS = + StableValue.lazyMap(Set.of("com.company.Foo", "com.company.Bar"), Logger::getLogger); + +// 2. Access the lazy map with as-declared-final performance +// (evaluation made before the first access) +static Logger logger(String name) { + return LOGGERS.get(name); } ``` -The same paradigm can be used for creating a memoized `Supplier` (backed by a single `StableValue` instance) via the `StableValues::memoizedSupplier` factory. Additional memoized functional constructs, such a memoized `Predicate`(backed by a lazily computed `Map>`) can be custom made. An astute reader will be able to write such constructs in a few lines. +In the example above, only two input values were used. However, this concept allows declaring a +large number of stable values which can be easily retrieved using arbitrarily, but pre-specified, +keys in a resource-efficient and performant way. For example, high-performance, non-evicting caches +may now be easily and reliably realized. -An advantage with memoized functions, compared to working directly with StableValue instances, is that the initialization logic -can be centralized and maintained in a single place, usually at the same place where the memoized function is defined. +Analogue to a lazy list, the lazy map guarantees the function provided at map creation +(used to lazily compute the map values) is invoked at most once per key (absent any Exceptions), +even though used from several threads. -As noted above, the memoized factories in the StableValues API offers an optional tailing thread factory parameter from which new value-computing background threads will be created: +Even though a cached `IntFunction` may sometimes replace a lazy `List` and a cached `Function` may be +used in place of a lazy `Map`, a lazy `List` or `Map` oftentimes provides better interoperability with +existing libraries. For example, if provided as a method parameter. -``` -// 1. Centrally declare a memoized (cached) function backed by a stable map -// computed in the background by two distinct virtual threads. -private static final Function LOGGERS = - StableValues.memoizedFunction(Set.of("com.foo.Bar", "com.foo.Baz"), - Logger::getLogger, - Thread.ofVirtual().factory()); // Create cheap virtual threads for background computation - -private static final String NAME = "com.foo.Baz"; +### Preview feature -// 2. Access the memoized value via the function with as-declared-final -// performance (evaluation already started and perhaps even completed before the first access) -Logger logger = LOGGERS.apply(NAME); -``` +The Stable Values & Collections is a [preview API](https://openjdk.org/jeps/12), disabled by default. +To use the Stable Value & Collections APIs, the JVM flag `--enable-preview` must be passed in, as follows: -This can provide a best-of-several-worlds situation where the memoized function can be quickly defined (as no -computation is made by the defining thread), the holder value is computed in background threads (thus neither -interfering significantly with the critical startup path nor with future accessing threads), and the threads actually -accessing the holder value can access the holder value with as-if-final performance and without having to compute -a holder value. This is true under the assumption, that the background threads can complete computation before accessing -threads requires a holder value. If this is not the case, at least some reduction of blocking time can be enjoyed as -the background threads have had a head start compared to the accessing threads. +- Compile the program with `javac --release 24 --enable-preview Main.java` and run it with `java --enable-preview Main`; or, + +- When using the source code launcher, run the program with `java --source 24 --enable-preview Main.java`; or, + +- When using `jshell`, start it with `jshell --enable-preview`. ## Alternatives diff --git a/test/jdk/java/lang/StableValue/JepTest.java b/test/jdk/java/lang/StableValue/JepTest.java index c44b2bc044ccf..c1f801533b99c 100644 --- a/test/jdk/java/lang/StableValue/JepTest.java +++ b/test/jdk/java/lang/StableValue/JepTest.java @@ -44,7 +44,7 @@ final class JepTest { - class Bar { + class Foo { // 1. Declare a Stable field private static final StableValue LOGGER = StableValue.newInstance(); @@ -52,7 +52,7 @@ static Logger logger() { if (!LOGGER.isSet()) { // 2. Set the stable value _after_ the field was declared - LOGGER.trySet(Logger.getLogger("com.foo.Bar")); + LOGGER.trySet(Logger.getLogger("com.company.Foo")); } // 3. Access the stable value with as-declared-final performance @@ -60,31 +60,62 @@ static Logger logger() { } } - class Bar2 { + class Foo2 { - // 1. Declare a caching supplier + // 1. Centrally declare a caching supplier and define how it should be computed private static final Supplier LOGGER = - StableValue.newCachingSupplier( () -> Logger.getLogger("com.foo.Bar"), null ); + StableValue.newCachingSupplier( () -> Logger.getLogger("com.company.Foo"), null ); static Logger logger() { - // 2. Access the stable value with as-declared-final performance + // 2. Access the cached value with as-declared-final performance // (single evaluation made before the first access) return LOGGER.get(); } } + class MapDemo { - class Memoized { + // 1. Declare a lazy stable map of loggers with two allowable keys: + // "com.company.Bar" and "com.company.Baz" + static final Map LOGGERS = + StableValue.lazyMap(Set.of("com.company.Foo", "com.company.Bar"), Logger::getLogger); - // 1. Declare a memoized (cached) function backed by a stable map + // 2. Access the lazy map with as-declared-final performance + // (evaluation made before the first access) + static Logger logger(String name) { + return LOGGERS.get(name); + } + } + + + class Cached { + + // 1. Centrally declare a cached function backed by a map of stable values private static final Function LOGGERS = - StableValue.newCachingFunction(Set.of("com.foo.Bar", "com.foo.Baz"), + StableValue.newCachingFunction(Set.of("com.company.Foo", "com.company.Bar"), Logger::getLogger, null); - private static final String NAME = "com.foo.Baz"; + private static final String NAME = "com.company.Foo"; + + // 2. Access the cached value via the function with as-declared-final + // performance (evaluation made before the first access) + Logger logger = LOGGERS.apply(NAME); + } + + class CachedBackground { + + // 1. Centrally declare a cached function backed by a map of stable values + // computed in the background by two distinct virtual threads. + private static final Function LOGGERS = + StableValue.newCachingFunction(Set.of("com.company.Foo", "com.company.Bar"), + Logger::getLogger, + // Create cheap virtual threads for background computation + Thread.ofVirtual().factory()); + + private static final String NAME = "com.company.Foo"; - // 2. Access the memoized value via the function with as-declared-final + // 2. Access the cached value via the function with as-declared-final // performance (evaluation made before the first access) Logger logger = LOGGERS.apply(NAME); } @@ -95,7 +126,7 @@ class A { class ErrorMessages { private static final int SIZE = 8; - // 1. Declare a memoized IntFunction backed by a stable list + // 1. Centrally declare a cached IntFunction backed by a list of StableValue elements private static final IntFunction ERROR_FUNCTION = StableValue.newCachingIntFunction(SIZE, ErrorMessages::readFromFile, null); @@ -109,7 +140,7 @@ private static String readFromFile(int messageNumber) { } } - // 3. Access the memoized list element with as-declared-final performance + // 3. Access the cached element with as-declared-final performance // (evaluation made before the first access) String msg = ERROR_FUNCTION.apply(2); } From 8d03594cb1731e714be4dc0db4479b6dbdfb8cfc Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Thu, 25 Jul 2024 12:02:53 +0200 Subject: [PATCH 054/327] Use NullableKeyValueHolder --- .../share/classes/java/util/ImmutableCollections.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/java.base/share/classes/java/util/ImmutableCollections.java b/src/java.base/share/classes/java/util/ImmutableCollections.java index 466c5179fe3c4..cc7532cf9c09c 100644 --- a/src/java.base/share/classes/java/util/ImmutableCollections.java +++ b/src/java.base/share/classes/java/util/ImmutableCollections.java @@ -43,6 +43,7 @@ import jdk.internal.access.SharedSecrets; import jdk.internal.lang.stable.StableValueImpl; import jdk.internal.misc.CDS; +import jdk.internal.util.NullableKeyValueHolder; import jdk.internal.vm.annotation.ForceInline; import jdk.internal.vm.annotation.Stable; @@ -1548,7 +1549,7 @@ final class LazyMapIterator implements Iterator> { public Entry next() { final Map.Entry> inner = delegateIterator.next(); final K key = inner.getKey(); - return new KeyValueHolder<>(key, computeIfUnset(key, inner.getValue())); + return new NullableKeyValueHolder<>(key, computeIfUnset(key, inner.getValue())); } @Override @@ -1558,7 +1559,7 @@ public void forEachRemaining(Consumer> action) { @Override public void accept(Entry> inner) { final K key = inner.getKey(); - action.accept(new KeyValueHolder<>(key, LazyMap.this.computeIfUnset(key, inner.getValue()))); + action.accept(new NullableKeyValueHolder<>(key, LazyMap.this.computeIfUnset(key, inner.getValue()))); } }; delegateIterator.forEachRemaining(innerAction); From a0ccdaf707c5064a9e73f8b1fddb27789b96b851 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Thu, 25 Jul 2024 12:11:42 +0200 Subject: [PATCH 055/327] Remove trailing spaces --- test/jdk/java/lang/StableValue/JEP.md | 50 +++++++++++++-------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/test/jdk/java/lang/StableValue/JEP.md b/test/jdk/java/lang/StableValue/JEP.md index cce5f681324ae..21b2c1bc993e9 100644 --- a/test/jdk/java/lang/StableValue/JEP.md +++ b/test/jdk/java/lang/StableValue/JEP.md @@ -23,15 +23,15 @@ This might be the subject of a future JEP. Some internal JDK classes are relying heavily on the annotation `jdk.internal.vm.annotation.@Stable` to mark scalar and array fields whose values or elements will change *at most once*, thereby providing -crucial performance, energy efficiency, and flexibility benefits. +crucial performance, energy efficiency, and flexibility benefits. Unfortunately, the powerful `@Stable` annotation cannot be used directly by client code thereby severely restricting its applicability. The Stable Values & Collections API rectifies this imbalance between internal and client code by providing safe wrappers around the `@Stable` annotation. Hence, all _the -important benefits of `@Stable` are now made available to regular Java developers and third-party +important benefits of `@Stable` are now made available to regular Java developers and third-party library developers_. -One of the benefits with `@Stable` is it makes a marked field eligible for [constant-folding](https://en.wikipedia.org/wiki/Constant_folding). +One of the benefits with `@Stable` is it makes a marked field eligible for [constant-folding](https://en.wikipedia.org/wiki/Constant_folding). Publicly exposing `@Stable` without a safe API, like the Stable Values & Collections API, would have rendered it unsafe as further updating a `@Stable` field after its initial update will result in undefined behavior, as the JIT compiler might have *already* constant-folded the (now overwritten) @@ -195,7 +195,7 @@ Unfortunately, this approach provides a plethora of challenges. First, retrievin from a static array is slow, as said values cannot be constant-folded. Even worse, access to the array is guarded by synchronization that is not only slow but will block access to the array for all elements whenever one of the elements is under computation. Furthermore, the class holder idiom -(see above) is undoubtedly insufficient in this case, as the number of required holder classes is +(see above) is undoubtedly insufficient in this case, as the number of required holder classes is *statically unbounded* - it depends on the value of the parameter `SIZE` which may change in future variants of the code. @@ -225,7 +225,7 @@ The Stable Values & Collections API resides in the [java.lang](https://cr.openjd ### Stable values A _stable value_ is a thin, atomic, non-blocking, thread-safe, set-at-most-once, stable value holder -eligible for certain JVM optimizations if set to a value. It is expressed as an object of type +eligible for certain JVM optimizations if set to a value. It is expressed as an object of type `jdk.lang.StableValue`, which, like `Future`, is a holder for some computation that may or may not have occurred yet. Fresh (unset) `StableValue` instances are created via the factory method `StableValue::newInstance`: @@ -258,7 +258,7 @@ Null-averse applications can also use `StableValue>`. When retrieving values, `StableValue` instances holding reference values can be faster than reference values managed via double-checked-idiom constructs as stable values rely on explicit memory barriers rather than performing volatile access on each retrieval -operation. +operation. In addition, stable values are eligible for constant folding optimizations by the JVM. In many ways, this is similar to the holder-class idiom in the sense it offers the same @@ -269,7 +269,7 @@ Looking at the basic example above, it becomes evident, several threads may invo method simultaneously if they call the `logger()` method at about the same time. Even though `StableValue` will guarantee, that only one of these results will ever be exposed to the many competing threads, there might be applications where it is a requirement, that a supplying method is -called *only once*. This brings us to the introduction of _cached functions_. +called *only once*. This brings us to the introduction of _cached functions_. ### Cached functions @@ -281,7 +281,7 @@ to composition with other constructs in order to create more high-level and powe particular input value is computed only once and is remembered such that remembered outputs can be reused for subsequent calls with recurring input values. -In cases where the invoke-at-most-once property of a `Supplier` is important, the +In cases where the invoke-at-most-once property of a `Supplier` is important, the Stable Values & Collections API offers a _cached supplier_ which is a caching, thread-safe, stable, lazily computed `Supplier` that records the value of an _original_ `Supplier` upon being first accessed via its `Supplier::get` method. In a multi-threaded scenario, competing threads will block @@ -313,7 +313,7 @@ class `Foo` (`Foo`, in turn, can be loaded at most once into any given `ClassLoa by a lazily computed stable value upholding the invoke-at-most-once invariant. Similarly to how a `Supplier` can be cached using a backing stable value, a similar pattern -can be used for an `IntFunction` that will record its cached values in a backing list of +can be used for an `IntFunction` that will record its cached values in a backing list of stable value elements. Here is how the error message example above can be improved using a caching `IntFunction`: @@ -321,7 +321,7 @@ a caching `IntFunction`: class ErrorMessages { private static final int SIZE = 8; - + // 1. Centrally declare a cached IntFunction backed by a list of StableValue elements private static final IntFunction ERROR_FUNCTION = StableValue.newCachingIntFunction(SIZE, ErrorMessages::readFromFile, null); @@ -335,12 +335,12 @@ class ErrorMessages { throw new UncheckedIOException(e); } } - + // 3. Access the cached element with as-declared-final performance // (evaluation made before the first access) String msg = ERROR_FUNCTION.apply(2); } - + // // // @@ -372,14 +372,14 @@ class Cahced { ``` It should be noted that the enumerated set of valid inputs given at creation time constitutes the only valid -input keys for the cached function. Providing a non-valid input for a cached `Function` (or a cached `IntFunction`) -would incur an `IllegalArgumentException`. +input keys for the cached function. Providing a non-valid input for a cached `Function` (or a cached `IntFunction`) +would incur an `IllegalArgumentException`. An advantage with cached functions, compared to working directly with `StableValue` instances, is that the initialization logic can be centralized and maintained in a single place, usually at the same place where the cached function is defined. -Additional cached function types, such a cached `Predicate` (backed by a lazily computed +Additional cached function types, such a cached `Predicate` (backed by a lazily computed `Map>`) or a cached `BiFunction` can be custom-made. An astute reader will be able to write such constructs in a few lines. @@ -395,15 +395,15 @@ private static final Function LOGGERS = StableValue.newCachingFunction(Set.of("com.company.Foo", "com.company.Bar"), Logger::getLogger, // Create cheap virtual threads for background computation - Thread.ofVirtual().factory()); - -... Somewhere else in the code + Thread.ofVirtual().factory()); + +... Somewhere else in the code private static final String NAME = "com.company.Foo"; // 2. Access the cached value via the function with as-declared-final // performance (evaluation made before the first access) -Logger logger = LOGGERS.apply(NAME); +Logger logger = LOGGERS.apply(NAME); ``` This can provide a best-of-several-worlds situation where the cached function can be quickly defined (as no @@ -419,14 +419,14 @@ the background threads have had a head start compared to the accessing threads. The Stable Values & Collections API also provides factories that allow the creation of new collection variants that are lazy, shallowly immutable, and stable: -- `List` of stable elements -- `Map` of stable values +- `List` of stable elements +- `Map` of stable values Lists of lazily computed stable elements are objects of type `List` where each element in such a list enjoys the same properties as a `StableValue`. -Like a `StableValue` object, a lazy `List` of stable value elements is created via a factory -method while additionally providing the `size` of the desired `List` and a mapper of type +Like a `StableValue` object, a lazy `List` of stable value elements is created via a factory +method while additionally providing the `size` of the desired `List` and a mapper of type `IntFunction` to be used when computing the lazy elements on demand: ``` @@ -437,7 +437,7 @@ Note how there's only one field of type `List` to initialize even though ever computation is performed independently of the other element of the list when accessed (i.e. no blocking will occur across threads computing distinct elements simultaneously). Also, the `IntSupplier` mapper provided at creation is only invoked at most once for each distinct input -value. The Stable Values & Collections API allows modeling this cleanly, while still preserving +value. The Stable Values & Collections API allows modeling this cleanly, while still preserving good constant-folding guarantees and integrity of updates in the case of multi-threaded access. It should be noted that even though a lazily computed list of stable elements might mutate its @@ -462,7 +462,7 @@ static Logger logger(String name) { } ``` -In the example above, only two input values were used. However, this concept allows declaring a +In the example above, only two input values were used. However, this concept allows declaring a large number of stable values which can be easily retrieved using arbitrarily, but pre-specified, keys in a resource-efficient and performant way. For example, high-performance, non-evicting caches may now be easily and reliably realized. From 3bf777c1291029c69ca61d31a8c5eeb44d913de5 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Thu, 25 Jul 2024 13:54:32 +0200 Subject: [PATCH 056/327] Move to public --- .../internal => java}/lang/StableValue.java | 16 ++++++++------ .../classes/java/lang/reflect/Field.java | 2 +- .../jdk/internal/javac/PreviewFeature.java | 2 ++ .../internal/lang/stable/StableValueImpl.java | 2 +- .../share/classes/sun/misc/Unsafe.java | 2 +- .../lang/StableValue/CachingFunctionTest.java | 2 +- .../StableValue/CachingIntFunctionTest.java | 2 +- .../lang/StableValue/CachingSupplierTest.java | 2 +- test/jdk/java/lang/StableValue/JEP.md | 22 +++++++++++-------- test/jdk/java/lang/StableValue/JepTest.java | 3 +-- .../java/lang/StableValue/LazyListTest.java | 2 +- .../java/lang/StableValue/LazyMapTest.java | 2 +- .../lang/StableValue/StableValueTest.java | 4 +--- .../StableValuesSafePublicationTest.java | 2 +- .../StableValue/TrustedFieldTypeTest.java | 2 +- .../lang/stable/StableValueBenchmark.java | 2 +- 16 files changed, 37 insertions(+), 32 deletions(-) rename src/java.base/share/classes/{jdk/internal => java}/lang/StableValue.java (97%) diff --git a/src/java.base/share/classes/jdk/internal/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java similarity index 97% rename from src/java.base/share/classes/jdk/internal/lang/StableValue.java rename to src/java.base/share/classes/java/lang/StableValue.java index 446fef2e0348d..48373cc8da8b5 100644 --- a/src/java.base/share/classes/jdk/internal/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -23,9 +23,10 @@ * questions. */ -package jdk.internal.lang; +package java.lang; import jdk.internal.access.SharedSecrets; +import jdk.internal.javac.PreviewFeature; import jdk.internal.lang.stable.CachedFunction; import jdk.internal.lang.stable.CachedIntFunction; import jdk.internal.lang.stable.CachedSupplier; @@ -115,13 +116,12 @@ * * *

Memory Consistency Properties

- * Certain interactions between StableValue operations form + * Actions on a presumptive holder value in a thread prior to calling a method that sets + * the holder value are seen by any other thread that first observes a set holder value. + * + * It should be noted that this does not form a proper * happens-before - * relationships: - *
    - *
  • Actions in a thread prior to calling a method that sets the holder value - * happen-before any other thread observes a set holder value.
  • - *
+ * relation because the stable value set/observe relation is not transitive. * * *

Nullability

@@ -141,6 +141,7 @@ * * @since 24 */ +@PreviewFeature(feature = PreviewFeature.Feature.STABLE_VALUES) public sealed interface StableValue permits StableValueImpl { @@ -336,6 +337,7 @@ static IntFunction newCachingIntFunction(int size, * {@code size} background threads that will attempt to compute the * memoized values. If the provided factory is {@code null}, no * background threads will be created. + * @param the type of the input to the returned Function * @param the type of results delivered by the returned Function */ static Function newCachingFunction(Set inputs, diff --git a/src/java.base/share/classes/java/lang/reflect/Field.java b/src/java.base/share/classes/java/lang/reflect/Field.java index d2f95149f4e62..c46054cf0770e 100644 --- a/src/java.base/share/classes/java/lang/reflect/Field.java +++ b/src/java.base/share/classes/java/lang/reflect/Field.java @@ -26,7 +26,7 @@ package java.lang.reflect; import jdk.internal.access.SharedSecrets; -import jdk.internal.lang.StableValue; +import java.lang.StableValue; import jdk.internal.reflect.CallerSensitive; import jdk.internal.reflect.FieldAccessor; import jdk.internal.reflect.Reflection; diff --git a/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java b/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java index 05049effef8c4..87b64d7c633bc 100644 --- a/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java +++ b/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java @@ -82,6 +82,8 @@ public enum Feature { @JEP(number=476, title="Module Import Declarations", status="Preview") MODULE_IMPORTS, LANGUAGE_MODEL, + @JEP(number = 8330465, title = "Stable Values & Collections", status = "Preview") + STABLE_VALUES, /** * A key for testing. */ 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 e1aff636cf661..28eea51c8ebb0 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 @@ -25,7 +25,7 @@ package jdk.internal.lang.stable; -import jdk.internal.lang.StableValue; +import java.lang.StableValue; import jdk.internal.misc.Unsafe; import jdk.internal.vm.annotation.ForceInline; import jdk.internal.vm.annotation.Stable; diff --git a/src/jdk.unsupported/share/classes/sun/misc/Unsafe.java b/src/jdk.unsupported/share/classes/sun/misc/Unsafe.java index c5ea1480ee566..ddda1ea7126f6 100644 --- a/src/jdk.unsupported/share/classes/sun/misc/Unsafe.java +++ b/src/jdk.unsupported/share/classes/sun/misc/Unsafe.java @@ -993,7 +993,7 @@ private static void assertNotTrusted(Field f) { throw new UnsupportedOperationException("can't get base address on a record class: " + f); } Class fieldType = f.getType(); - if (fieldType.getName().equals("jdk.internal.lang.StableValue")) { + if (fieldType.getName().equals("java.lang.StableValue")) { throw new UnsupportedOperationException("can't get field offset for a field of type " + fieldType.getName() + ": " + f); } } diff --git a/test/jdk/java/lang/StableValue/CachingFunctionTest.java b/test/jdk/java/lang/StableValue/CachingFunctionTest.java index 2816bd221d129..3b8ed2cf89158 100644 --- a/test/jdk/java/lang/StableValue/CachingFunctionTest.java +++ b/test/jdk/java/lang/StableValue/CachingFunctionTest.java @@ -28,7 +28,7 @@ * @run junit/othervm --enable-preview CachingFunctionTest */ -import jdk.internal.lang.StableValue; +import java.lang.StableValue; import org.junit.jupiter.api.Test; import java.util.Set; diff --git a/test/jdk/java/lang/StableValue/CachingIntFunctionTest.java b/test/jdk/java/lang/StableValue/CachingIntFunctionTest.java index ac00c54c5cdbf..f5a879c2b96d0 100644 --- a/test/jdk/java/lang/StableValue/CachingIntFunctionTest.java +++ b/test/jdk/java/lang/StableValue/CachingIntFunctionTest.java @@ -28,7 +28,7 @@ * @run junit/othervm --enable-preview CachingIntFunctionTest */ -import jdk.internal.lang.StableValue; +import java.lang.StableValue; import org.junit.jupiter.api.Test; import java.util.concurrent.ThreadFactory; diff --git a/test/jdk/java/lang/StableValue/CachingSupplierTest.java b/test/jdk/java/lang/StableValue/CachingSupplierTest.java index b5747d68ca502..e0144577b66e1 100644 --- a/test/jdk/java/lang/StableValue/CachingSupplierTest.java +++ b/test/jdk/java/lang/StableValue/CachingSupplierTest.java @@ -28,7 +28,7 @@ * @run junit/othervm --enable-preview CachingSupplierTest */ -import jdk.internal.lang.StableValue; +import java.lang.StableValue; import org.junit.jupiter.api.Test; import java.util.concurrent.ThreadFactory; diff --git a/test/jdk/java/lang/StableValue/JEP.md b/test/jdk/java/lang/StableValue/JEP.md index 21b2c1bc993e9..6c7f724766a36 100644 --- a/test/jdk/java/lang/StableValue/JEP.md +++ b/test/jdk/java/lang/StableValue/JEP.md @@ -110,7 +110,9 @@ correctness: ``` // Double-checked locking idiom class Foo { + private volatile Logger logger; + public Logger logger() { Logger v = logger; if (v == null) { @@ -126,7 +128,7 @@ class Foo { } ``` The double-checked locking idiom is brittle and easy to get -subtly wrong (see _Java Concurrency in Practice_, 16.2.4.) For example, a common error +subtly wrong (see _Java Concurrency in Practice_, 16.2.4, by Brian Goetz) For example, a common error is forgetting to declare the field `volatile` resulting in the risk of observing incomplete objects. While the double-checked locking idiom can be used for both class and instance @@ -273,8 +275,8 @@ called *only once*. This brings us to the introduction of _cached functions_. ### Cached functions -So far, we have talked about the fundamental features of StableValue as securely -wrapped `@Stable` value holders. However, it has become apparent, stable primitives are amenable +So far, we have talked about the fundamental features of StableValue as s securely +wrapped `@Stable` value holder. However, it has become apparent, stable primitives are amenable to composition with other constructs in order to create more high-level and powerful features. [Cached (or Memoized) functions](https://en.wikipedia.org/wiki/Memoization) are functions where the output for a @@ -310,10 +312,10 @@ of this chapter. In the example above, the original `Supplier` provided is invoked at most once per loading of the containing class `Foo` (`Foo`, in turn, can be loaded at most once into any given `ClassLoader`) and it is backed -by a lazily computed stable value upholding the invoke-at-most-once invariant. +by a lazily computed stable value. -Similarly to how a `Supplier` can be cached using a backing stable value, a similar pattern -can be used for an `IntFunction` that will record its cached values in a backing list of +Analogous to how a `Supplier` can be cached using a backing stable value, a similar pattern +can be used for an `IntFunction` that will record its cached values in a backing array of stable value elements. Here is how the error message example above can be improved using a caching `IntFunction`: @@ -348,6 +350,8 @@ class ErrorMessages { // ``` +Note: Again, the last null parameter signifies an optional thread factory that will be explained at the end of this chapter. + Finally, the most general cached function variant provided is a cached `Function` which, for example, can make sure `Logger::getLogger` in one of the first examples above is invoked at most once per input value (provided it executes successfully) in a multi-threaded environment. Such a cached @@ -409,7 +413,7 @@ Logger logger = LOGGERS.apply(NAME); This can provide a best-of-several-worlds situation where the cached function can be quickly defined (as no computation is made by the defining thread), the holder value is computed in background threads (thus neither interfering significantly with the critical startup path nor with future accessing threads), and the threads actually -accessing the holder value can access the holder value with as-if-final performance and without having to compute +accessing the holder value can directly access the holder value with as-if-final performance and without having to compute a holder value. This is true under the assumption, that the background threads can complete computation before accessing threads requires a holder value. If this is not the case, at least some reduction of blocking time can be enjoyed as the background threads have had a head start compared to the accessing threads. @@ -503,5 +507,5 @@ will likely suffice to reach the goals of this JEP. ## Dependencies -The work described here will likely enable subsequent work to provide pre-evaluated computed -constant fields at compile, condensation, and/or runtime. +The work described here will likely enable subsequent work to provide pre-evaluated stable value fields at +compile, condensation, and/or runtime. diff --git a/test/jdk/java/lang/StableValue/JepTest.java b/test/jdk/java/lang/StableValue/JepTest.java index c1f801533b99c..df8df4cb62251 100644 --- a/test/jdk/java/lang/StableValue/JepTest.java +++ b/test/jdk/java/lang/StableValue/JepTest.java @@ -28,13 +28,12 @@ * @run junit/othervm --enable-preview JepTest */ -import jdk.internal.lang.StableValue; +import java.lang.StableValue; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Function; diff --git a/test/jdk/java/lang/StableValue/LazyListTest.java b/test/jdk/java/lang/StableValue/LazyListTest.java index ea32eb4b3e137..8e03622f832ca 100644 --- a/test/jdk/java/lang/StableValue/LazyListTest.java +++ b/test/jdk/java/lang/StableValue/LazyListTest.java @@ -28,7 +28,7 @@ * @run junit/othervm --enable-preview LazyListTest */ -import jdk.internal.lang.StableValue; +import java.lang.StableValue; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; diff --git a/test/jdk/java/lang/StableValue/LazyMapTest.java b/test/jdk/java/lang/StableValue/LazyMapTest.java index dc8547178ad8c..bd70f08b38bff 100644 --- a/test/jdk/java/lang/StableValue/LazyMapTest.java +++ b/test/jdk/java/lang/StableValue/LazyMapTest.java @@ -28,7 +28,7 @@ * @run junit/othervm --enable-preview LazyMapTest */ -import jdk.internal.lang.StableValue; +import java.lang.StableValue; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; diff --git a/test/jdk/java/lang/StableValue/StableValueTest.java b/test/jdk/java/lang/StableValue/StableValueTest.java index db7a72fdffa3f..e65d780fa86df 100644 --- a/test/jdk/java/lang/StableValue/StableValueTest.java +++ b/test/jdk/java/lang/StableValue/StableValueTest.java @@ -29,7 +29,7 @@ * @run junit/othervm --enable-preview StableValueTest */ -import jdk.internal.lang.StableValue; +import java.lang.StableValue; import jdk.internal.lang.stable.StableValueImpl; import org.junit.jupiter.api.Test; @@ -43,8 +43,6 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.LockSupport; import java.util.function.BiPredicate; -import java.util.function.Function; -import java.util.function.Supplier; import java.util.stream.IntStream; import static org.junit.jupiter.api.Assertions.*; diff --git a/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java b/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java index 190d0748905a7..f2cbdcd2d604c 100644 --- a/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java +++ b/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java @@ -29,7 +29,7 @@ * @run junit/othervm --enable-preview StableValuesSafePublicationTest */ -import jdk.internal.lang.StableValue; +import java.lang.StableValue; import org.junit.jupiter.api.Test; import java.util.Arrays; diff --git a/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java b/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java index 0f85f4938508f..6bf557540dfa6 100644 --- a/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java +++ b/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java @@ -30,7 +30,7 @@ * @run junit/othervm --enable-preview TrustedFieldTypeTest */ -import jdk.internal.lang.StableValue; +import java.lang.StableValue; import org.junit.jupiter.api.Test; import java.lang.invoke.MethodHandles; diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java index 697e998ec26a2..79ac500dde1dc 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java @@ -23,7 +23,7 @@ package org.openjdk.bench.java.lang.stable; -import jdk.internal.lang.StableValue; +import java.lang.StableValue; import org.openjdk.jmh.annotations.*; import java.util.concurrent.TimeUnit; From 060a4a9cb470a1d0fb93a38234b409d828f6ae5f Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Thu, 25 Jul 2024 14:00:23 +0200 Subject: [PATCH 057/327] Update JEP links --- test/jdk/java/lang/StableValue/JEP.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/jdk/java/lang/StableValue/JEP.md b/test/jdk/java/lang/StableValue/JEP.md index 6c7f724766a36..186f8734399e0 100644 --- a/test/jdk/java/lang/StableValue/JEP.md +++ b/test/jdk/java/lang/StableValue/JEP.md @@ -213,14 +213,14 @@ computer resource management. The Stable Values & Collections API defines an interface so that client code in libraries and applications can - Define and use stable (scalar) values: - - [`StableValue.newInstance()`](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/java/lang/StableValue.html) + - [`StableValue.newInstance()`](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/java/lang/StableValue.html#newInstance()) - Define various _cached_ functions: - - [`StableValue.newCachedSupplier()`](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/java/lang/StableValue.html#newCachedSupplier(Supplier)) - - [`StableValue.newCachedIntFunction()`](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/java/lang/StableValue.html#newCachedIntFunction(int,IntFunction)) - - [`StableValue.newCachedFunction()`](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/java/lang/StableValue.html#newCachedFunction(Set,Function)) + - [`StableValue.newCachedSupplier()`](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/java/lang/StableValue.html#newCachingSupplier(java.util.function.Supplier,java.util.concurrent.ThreadFactory)) + - [`StableValue.newCachedIntFunction()`](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/java/lang/StableValue.html#newCachingIntFunction(int,java.util.function.IntFunction,java.util.concurrent.ThreadFactory)) + - [`StableValue.newCachedFunction()`](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/java/lang/StableValue.html#newCachingFunction(java.util.Set,java.util.function.Function,java.util.concurrent.ThreadFactory)) - Define _lazy_ collections: - - [`StableValue.lazyList(int size)`](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/java/lang/StableValue.html#ofList(int)) - - [`StableValue.lazyMap(Set keys)`](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/java/lang/StableValue.html#ofMap(java.util.Set)) + - [`StableValue.lazyList(int size)`](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/java/lang/StableValue.html#lazyList(int,java.util.function.IntFunction)) + - [`StableValue.lazyMap(Set keys)`](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/java/lang/StableValue.html#lazyMap(java.util.Set,java.util.function.Function)) The Stable Values & Collections API resides in the [java.lang](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/java/lang/package-summary.html) package of the [java.base](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/module-summary.html) module. From 708213a5b3cbe34a1adc533dca11f87f39659626 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Thu, 25 Jul 2024 15:11:07 +0200 Subject: [PATCH 058/327] Fix issues related to moving the StableValue class --- src/hotspot/share/ci/ciField.cpp | 3 +-- src/jdk.unsupported/share/classes/sun/misc/Unsafe.java | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hotspot/share/ci/ciField.cpp b/src/hotspot/share/ci/ciField.cpp index 71dd1b424d287..8e978f6db72f7 100644 --- a/src/hotspot/share/ci/ciField.cpp +++ b/src/hotspot/share/ci/ciField.cpp @@ -253,8 +253,7 @@ static bool trust_final_non_static_fields(ciInstanceKlass* holder) { return TrustFinalNonStaticFields; } -// Todo: Change to 'java/lang/StableValue' etc. once StableValue becomes a public API -const char* stable_value_klass_name = "jdk/internal/lang/StableValue"; +const char* stable_value_klass_name = "java/lang/StableValue"; static bool trust_final_non_static_fields_of_type(Symbol* signature) { if (signature->equals(stable_value_klass_name) == 0) { diff --git a/src/jdk.unsupported/share/classes/sun/misc/Unsafe.java b/src/jdk.unsupported/share/classes/sun/misc/Unsafe.java index ddda1ea7126f6..00bd9f5439770 100644 --- a/src/jdk.unsupported/share/classes/sun/misc/Unsafe.java +++ b/src/jdk.unsupported/share/classes/sun/misc/Unsafe.java @@ -993,6 +993,7 @@ private static void assertNotTrusted(Field f) { throw new UnsupportedOperationException("can't get base address on a record class: " + f); } Class fieldType = f.getType(); + // Todo: Change to "java.lang.StableValue.class.isAssignableFrom(fieldType)" after StableValue exist preview if (fieldType.getName().equals("java.lang.StableValue")) { throw new UnsupportedOperationException("can't get field offset for a field of type " + fieldType.getName() + ": " + f); } From 1301a50d351223e671cb23cb70bab2c65d1ea859 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Thu, 25 Jul 2024 16:32:02 +0200 Subject: [PATCH 059/327] Set Field isGTrustedFinal property directly from C --- src/hotspot/share/runtime/fieldDescriptor.cpp | 2 +- .../share/classes/java/lang/reflect/Field.java | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/hotspot/share/runtime/fieldDescriptor.cpp b/src/hotspot/share/runtime/fieldDescriptor.cpp index d326b79acfd78..aea75747ba387 100644 --- a/src/hotspot/share/runtime/fieldDescriptor.cpp +++ b/src/hotspot/share/runtime/fieldDescriptor.cpp @@ -44,7 +44,7 @@ Symbol* fieldDescriptor::generic_signature() const { bool fieldDescriptor::is_trusted_final() const { InstanceKlass* ik = field_holder(); - return is_final() && (is_static() || ik->is_hidden() || ik->is_record()); + return is_final() && (is_static() || ik->is_hidden() || ik->is_record() || ik->name()->equals("java/lang/StableValue") == 0); } AnnotationArray* fieldDescriptor::annotations() const { diff --git a/src/java.base/share/classes/java/lang/reflect/Field.java b/src/java.base/share/classes/java/lang/reflect/Field.java index c46054cf0770e..a239a262bdf51 100644 --- a/src/java.base/share/classes/java/lang/reflect/Field.java +++ b/src/java.base/share/classes/java/lang/reflect/Field.java @@ -175,12 +175,6 @@ Field copy() { @CallerSensitive public void setAccessible(boolean flag) { AccessibleObject.checkPermission(); - // Always check if the field is a final StableValue - if (StableValue.class.isAssignableFrom(type) && Modifier.isFinal(modifiers)) { - throw newInaccessibleObjectException( - "Unable to make field " + this + " accessible: " + - "Fields declared to implement " + StableValue.class.getName() + " are trusted"); - } if (flag) { checkCanSetAccessible(Reflection.getCallerClass()); } @@ -189,6 +183,10 @@ public void setAccessible(boolean flag) { @Override void checkCanSetAccessible(Class caller) { + if (isTrustedFinal()) { + throw newInaccessibleObjectException("Unable to make field " + this + " accessible " + + "because it is a trusted final field"); + } checkCanSetAccessible(caller, clazz); } From f6de5e5bcd84a8a54180c1be61055fd94fded13e Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Thu, 25 Jul 2024 16:32:28 +0200 Subject: [PATCH 060/327] Remove redundant id tags --- .../share/classes/java/lang/StableValue.java | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 48373cc8da8b5..0231464a3dba4 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -54,8 +54,7 @@ * StableValue is mainly intended to be a member of a holding class and is usually neither * exposed directly via accessors nor passed as a method parameter. * - * - *

Factories

+ *

Factories

*

* To create a new fresh (unset) StableValue, use the {@linkplain StableValue#newInstance()} * factory. @@ -114,8 +113,7 @@ * The constructs above are eligible for similar JVM optimizations as StableValue * instances. * - * - *

Memory Consistency Properties

+ *

Memory Consistency Properties

* Actions on a presumptive holder value in a thread prior to calling a method that sets * the holder value are seen by any other thread that first observes a set holder value. * @@ -123,12 +121,11 @@ * happens-before * relation because the stable value set/observe relation is not transitive. * - * - *

Nullability

+ *

Nullability

* Except for a StableValue's holder value itself, all method parameters must be * non-null or a {@link NullPointerException} will be thrown. * - * + * * Implementations of this interface can be * value-based * classes; programmers should treat instances that are From e49aaf709ff6c37c5935ef222f31657f95c8f228 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Thu, 25 Jul 2024 16:45:35 +0200 Subject: [PATCH 061/327] Fix various JavaDoc issues --- .../share/classes/java/lang/StableValue.java | 85 +++++++++++-------- 1 file changed, 49 insertions(+), 36 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 0231464a3dba4..a9b625778cbad 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -56,12 +56,13 @@ * *

Factories

*

- * To create a new fresh (unset) StableValue, use the {@linkplain StableValue#newInstance()} - * factory. + * To create a new fresh (unset) StableValue, use the + * {@linkplain StableValue#newInstance() StableValue::newInstance} factory. *

- * This class contains a number of convenience methods for creating constructs - * involving StableValue: - * + * This class also contains a number of convenience methods for creating constructs + * involving stable values: + *

    + *
  • * A cached (also called "memoized") Supplier, where a given {@code original} * Supplier is guaranteed to be successfully invoked at most once even in a multithreaded * environment, can be created like this: @@ -73,7 +74,9 @@ * {@snippet lang = java : * Supplier cached = StableValue.newCachedSupplier(original, Thread.ofVirtual().factory()); * } - *

    + *

  • + * + *
  • * A cached (also called "memoized") IntFunction, for the allowed given {@code size} * input values {@code [0, size)} and where the given {@code original} IntFunction is * guaranteed to be successfully invoked at most once per inout index even in a @@ -84,7 +87,9 @@ * Just like a cached supplier, a thread factory can be provided as a second parameter * allowing all the values for the allowed input values to be computed by distinct * background threads. - *

    + *

  • + * + *
  • * A cached (also called "memoized") Function, for the given set of allowed {@code inputs} * and where the given {@code original} function is guaranteed to be successfully invoked * at most once per input value even in a multithreaded environment, can be created like @@ -95,7 +100,9 @@ * Just like a cached supplier, a thread factory can be provided as a second parameter * allowing all the values for the allowed input values to be computed by distinct * background threads. - *

    + *

  • + * + *
  • * A lazy List of stable elements with a given {@code size} and given {@code mapper} can * be created the following way: * {@snippet lang = java : @@ -103,12 +110,17 @@ * } * The list can be used to model stable one-dimensional arrays. If two- or more * dimensional arrays are to be modeled, a List of List of ... of E can be used. - *

    - * A Map with a given set of {@code keys} and given (@code mapper) associated with + *

  • + * + *
  • + * A lazy Map with a given set of {@code keys} and given {@code mapper} associated with * stable values can be created like this: * {@snippet lang = java : * Map lazyMap = StableValue.lazyMap(keys, mapper); * } + *
  • + * + *
*

* The constructs above are eligible for similar JVM optimizations as StableValue * instances. @@ -178,7 +190,7 @@ public sealed interface StableValue /** * Sets the holder value to the provided {@code value}, or, if already set, - * throws {@linkplain IllegalStateException}} + * throws {@link IllegalStateException}} *

* When this method returns (or throws an Exception), a holder value is always set. * @@ -207,18 +219,18 @@ static StableValue newInstance() { * {@return a new caching, thread-safe, stable, lazily computed * {@linkplain Supplier supplier} that records the value of the provided * {@code original} supplier upon being first accessed via - * {@linkplain Supplier#get()}, or optionally via a background thread created from - * the provided {@code factory} (if non-null)} + * {@linkplain Supplier#get() Supplier::get}, or optionally via a background thread + * created from the provided {@code factory} (if non-null)} *

* The provided {@code original} supplier is guaranteed to be successfully invoked * at most once even in a multi-threaded environment. Competing threads invoking the - * {@linkplain Supplier#get()} method when a value is already under computation - * will block until a value is computed or an exception is thrown by the + * {@linkplain Supplier#get() Supplier::get} method when a value is already under + * computation will block until a value is computed or an exception is thrown by the * computing thread. *

* If the {@code original} Supplier invokes the returned Supplier recursively, * a StackOverflowError will be thrown when the returned - * Supplier's {@linkplain Supplier#get()} method is invoked. + * Supplier's {@linkplain Supplier#get() Supplier::get} method is invoked. *

* If the provided {@code original} supplier throws an exception, it is relayed * to the initial caller. If the memoized supplier is computed by a background thread, @@ -254,20 +266,21 @@ public void run() { /** * {@return a new caching, thread-safe, stable, lazily computed - * {@linkplain IntFunction } that, for each allowed input, records the values of the + * {@link IntFunction } that, for each allowed input, records the values of the * provided {@code original} IntFunction upon being first accessed via - * {@linkplain IntFunction#apply(int)}, or optionally via background threads created - * from the provided {@code factory} (if non-null)} + * {@linkplain IntFunction#apply(int) IntFunction::apply}, or optionally via background + * threads created from the provided {@code factory} (if non-null)} *

* The provided {@code original} IntFunction is guaranteed to be successfully invoked * at most once per allowed input, even in a multi-threaded environment. Competing - * threads invoking the {@linkplain IntFunction#apply(int)} method when a value is - * already under computation will block until a value is computed or an exception is - * thrown by the computing thread. + * threads invoking the {@linkplain IntFunction#apply(int) IntFunction::apply} method + * when a value is already under computation will block until a value is computed or + * an exception is thrown by the computing thread. *

* If the {@code original} IntFunction invokes the returned IntFunction recursively * for a particular input value, a StackOverflowError will be thrown when the returned - * IntFunction's {@linkplain IntFunction#apply(int)} method is invoked. + * IntFunction's {@linkplain IntFunction#apply(int) IntFunction::apply} method is + * invoked. *

* If the provided {@code original} IntFunction throws an exception, it is relayed * to the initial caller. If the memoized IntFunction is computed by a background @@ -304,21 +317,21 @@ static IntFunction newCachingIntFunction(int size, } /** - * {@return a new caching, thread-safe, stable, lazily computed {@linkplain Function} + * {@return a new caching, thread-safe, stable, lazily computed {@link Function} * that, for each allowed input in the given set of {@code inputs}, records the * values of the provided {@code original} Function upon being first accessed via - * {@linkplain Function#apply(Object)}, or optionally via background threads created - * from the provided {@code factory} (if non-null)} + * {@linkplain Function#apply(Object) Function::apply}, or optionally via background + * threads created from the provided {@code factory} (if non-null)} *

* The provided {@code original} Function is guaranteed to be successfully invoked * at most once per allowed input, even in a multi-threaded environment. Competing - * threads invoking the {@linkplain Function#apply(Object)} method when a value is - * already under computation will block until a value is computed or an exception is - * thrown by the computing thread. + * threads invoking the {@linkplain Function#apply(Object) Function::apply} method + * when a value is already under computation will block until a value is computed or + * an exception is thrown by the computing thread. *

* If the {@code original} Function invokes the returned Function recursively * for a particular input value, a StackOverflowError will be thrown when the returned - * IntFunction's {@linkplain IntFunction#apply(int)} method is invoked. + * Function's {@linkplain Function#apply(Object) Function::apply} method is invoked. *

* If the provided {@code original} Function throws an exception, it is relayed * to the initial caller. If the memoized Function is computed by a background @@ -358,7 +371,7 @@ static Function newCachingFunction(Set inputs, * {@return a lazy, immutable, stable List of the provided {@code size} where the * individual elements of the list are lazily computed vio the provided * {@code mapper} whenever an element is first accessed (directly or indirectly), - * for example via {@linkplain List#get(int)}} + * for example via {@linkplain List#get(int) List::get}} *

* The provided {@code mapper} IntFunction is guaranteed to be successfully invoked * at most once per list index, even in a multi-threaded environment. Competing @@ -367,12 +380,12 @@ static Function newCachingFunction(Set inputs, *

* If the {@code mapper} IntFunction invokes the returned IntFunction recursively * for a particular index, a StackOverflowError will be thrown when the returned - * List's {@linkplain List#get(int)} method is invoked. + * List's {@linkplain List#get(int) List::get} method is invoked. *

* If the provided {@code mapper} IntFunction throws an exception, it is relayed * to the initial caller and no element is computed. *

- * The returned List is not {@linkplain Serializable} + * The returned List is not {@link Serializable} * * @param size the size of the returned list * @param mapper to invoke whenever an element is first accessed @@ -390,7 +403,7 @@ static List lazyList(int size, IntFunction mapper) { * {@return a lazy, immutable, stable Map of the provided {@code keys} where the * associated values of the maps are lazily computed vio the provided * {@code mapper} whenever a value is first accessed (directly or indirectly), for - * example via {@linkplain Map#get(Object)}} + * example via {@linkplain Map#get(Object) Map::get}} *

* The provided {@code mapper} Function is guaranteed to be successfully invoked * at most once per key, even in a multi-threaded environment. Competing @@ -399,12 +412,12 @@ static List lazyList(int size, IntFunction mapper) { *

* If the {@code mapper} Function invokes the returned Map recursively * for a particular key, a StackOverflowError will be thrown when the returned - * Map's {@linkplain Map#get(Object)}} method is invoked. + * Map's {@linkplain Map#get(Object) Map::get}} method is invoked. *

* If the provided {@code mapper} Function throws an exception, it is relayed * to the initial caller and no value is computed. *

- * The returned Map is not {@linkplain Serializable} + * The returned Map is not {@link Serializable} * * @param keys the keys in the returned map * @param mapper to invoke whenever an associated value is first accessed From d1f935681dff610a5423d39dc9512911529e41f5 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Thu, 25 Jul 2024 20:00:56 +0200 Subject: [PATCH 062/327] Fix issues mention in comments in PR --- src/hotspot/share/classfile/vmSymbols.hpp | 4 ++- .../share/classes/java/lang/StableValue.java | 13 +++++----- .../classes/java/lang/reflect/Field.java | 1 - .../java/util/ImmutableCollections.java | 6 +---- .../internal/lang/stable/CachedFunction.java | 25 +++++++++++++++++++ .../lang/stable/CachedIntFunction.java | 25 +++++++++++++++++++ .../internal/lang/stable/CachedSupplier.java | 25 +++++++++++++++++++ .../internal/lang/stable/StableValueImpl.java | 1 - .../share/classes/sun/misc/Unsafe.java | 2 +- 9 files changed, 87 insertions(+), 15 deletions(-) diff --git a/src/hotspot/share/classfile/vmSymbols.hpp b/src/hotspot/share/classfile/vmSymbols.hpp index 8d1ae20eac07c..4bead4f0693b1 100644 --- a/src/hotspot/share/classfile/vmSymbols.hpp +++ b/src/hotspot/share/classfile/vmSymbols.hpp @@ -744,7 +744,9 @@ class SerializeClosure; template(jdk_internal_vm_ThreadDumper, "jdk/internal/vm/ThreadDumper") \ template(dumpThreads_name, "dumpThreads") \ template(dumpThreadsToJson_name, "dumpThreadsToJson") \ - + \ + /* StableValues & Collections */ \ + template(java_lang_StableValue, "java/lang/StableValue") \ /*end*/ // enum for figuring positions and size of Symbol::_vm_symbols[] diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index a9b625778cbad..00315c52d2b31 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -69,7 +69,7 @@ * {@snippet lang = java : * Supplier cached = StableValue.newCachedSupplier(original, null); * } - * The cached supplier can also be lazily computed by a fresh background thread if a + * The cached supplier can also be computed by a fresh background thread if a * thread factory is provided as a second parameter as shown here: * {@snippet lang = java : * Supplier cached = StableValue.newCachedSupplier(original, Thread.ofVirtual().factory()); @@ -368,8 +368,8 @@ static Function newCachingFunction(Set inputs, } /** - * {@return a lazy, immutable, stable List of the provided {@code size} where the - * individual elements of the list are lazily computed vio the provided + * {@return a lazy, shallowly immutable, stable List of the provided {@code size} + * where the individual elements of the list are lazily computed via the provided * {@code mapper} whenever an element is first accessed (directly or indirectly), * for example via {@linkplain List#get(int) List::get}} *

@@ -388,7 +388,7 @@ static Function newCachingFunction(Set inputs, * The returned List is not {@link Serializable} * * @param size the size of the returned list - * @param mapper to invoke whenever an element is first accessed + * @param mapper to invoke whenever an element is first accessed (may return null) * @param the {@code StableValue}s' element type */ static List lazyList(int size, IntFunction mapper) { @@ -400,8 +400,8 @@ static List lazyList(int size, IntFunction mapper) { } /** - * {@return a lazy, immutable, stable Map of the provided {@code keys} where the - * associated values of the maps are lazily computed vio the provided + * {@return a lazy, shallowly immutable, stable Map of the provided {@code keys} + * where the associated values of the maps are lazily computed vio the provided * {@code mapper} whenever a value is first accessed (directly or indirectly), for * example via {@linkplain Map#get(Object) Map::get}} *

@@ -421,6 +421,7 @@ static List lazyList(int size, IntFunction mapper) { * * @param keys the keys in the returned map * @param mapper to invoke whenever an associated value is first accessed + * (may return null) * @param the type of keys maintained by the returned map * @param the type of mapped values */ diff --git a/src/java.base/share/classes/java/lang/reflect/Field.java b/src/java.base/share/classes/java/lang/reflect/Field.java index a239a262bdf51..6ec8bb174fd16 100644 --- a/src/java.base/share/classes/java/lang/reflect/Field.java +++ b/src/java.base/share/classes/java/lang/reflect/Field.java @@ -26,7 +26,6 @@ package java.lang.reflect; import jdk.internal.access.SharedSecrets; -import java.lang.StableValue; import jdk.internal.reflect.CallerSensitive; import jdk.internal.reflect.FieldAccessor; import jdk.internal.reflect.Reflection; diff --git a/src/java.base/share/classes/java/util/ImmutableCollections.java b/src/java.base/share/classes/java/util/ImmutableCollections.java index cc7532cf9c09c..e2665d789015a 100644 --- a/src/java.base/share/classes/java/util/ImmutableCollections.java +++ b/src/java.base/share/classes/java/util/ImmutableCollections.java @@ -1526,11 +1526,7 @@ final class LazyMapEntrySet extends AbstractImmutableSet> { @Override public int hashCode() { - int h = 0; - for (Map.Entry e : this) { - h += e.hashCode(); - } - return h; + return LazyMap.this.hashCode(); } @jdk.internal.ValueBased diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/CachedFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/CachedFunction.java index 50f5c2e1f8370..ac0335beccb95 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/CachedFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/CachedFunction.java @@ -1,3 +1,28 @@ +/* + * Copyright (c) 2024, 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.vm.annotation.ForceInline; diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/CachedIntFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/CachedIntFunction.java index b9bec6272b8bf..9006662cf63b3 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/CachedIntFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/CachedIntFunction.java @@ -1,3 +1,28 @@ +/* + * Copyright (c) 2024, 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.vm.annotation.ForceInline; diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/CachedSupplier.java b/src/java.base/share/classes/jdk/internal/lang/stable/CachedSupplier.java index f7be8417671cc..40e8e8bc0e79d 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/CachedSupplier.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/CachedSupplier.java @@ -1,3 +1,28 @@ +/* + * Copyright (c) 2024, 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.vm.annotation.ForceInline; 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 28eea51c8ebb0..d3eac44125284 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 @@ -25,7 +25,6 @@ package jdk.internal.lang.stable; -import java.lang.StableValue; import jdk.internal.misc.Unsafe; import jdk.internal.vm.annotation.ForceInline; import jdk.internal.vm.annotation.Stable; diff --git a/src/jdk.unsupported/share/classes/sun/misc/Unsafe.java b/src/jdk.unsupported/share/classes/sun/misc/Unsafe.java index 00bd9f5439770..6398a2b3f5f2e 100644 --- a/src/jdk.unsupported/share/classes/sun/misc/Unsafe.java +++ b/src/jdk.unsupported/share/classes/sun/misc/Unsafe.java @@ -993,7 +993,7 @@ private static void assertNotTrusted(Field f) { throw new UnsupportedOperationException("can't get base address on a record class: " + f); } Class fieldType = f.getType(); - // Todo: Change to "java.lang.StableValue.class.isAssignableFrom(fieldType)" after StableValue exist preview + // Todo: Change to "java.lang.StableValue.class.isAssignableFrom(fieldType)" after StableValue exists preview if (fieldType.getName().equals("java.lang.StableValue")) { throw new UnsupportedOperationException("can't get field offset for a field of type " + fieldType.getName() + ": " + f); } From 7eaf715451f763ee44b656400096ded24c376261 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Thu, 25 Jul 2024 20:11:08 +0200 Subject: [PATCH 063/327] Simplify method --- src/hotspot/share/ci/ciField.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/hotspot/share/ci/ciField.cpp b/src/hotspot/share/ci/ciField.cpp index 8e978f6db72f7..43bba24a6fb85 100644 --- a/src/hotspot/share/ci/ciField.cpp +++ b/src/hotspot/share/ci/ciField.cpp @@ -256,10 +256,7 @@ static bool trust_final_non_static_fields(ciInstanceKlass* holder) { const char* stable_value_klass_name = "java/lang/StableValue"; static bool trust_final_non_static_fields_of_type(Symbol* signature) { - if (signature->equals(stable_value_klass_name) == 0) { - return true; - } - return false; + return (signature->equals(stable_value_klass_name) == 0); } void ciField::initialize_from(fieldDescriptor* fd) { From 966576ba7b7262c37919f295124b26c5f535895a Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Fri, 26 Jul 2024 09:48:15 +0200 Subject: [PATCH 064/327] Use symbols in ciField --- src/hotspot/share/ci/ciField.cpp | 4 +--- src/hotspot/share/classfile/vmSymbols.hpp | 1 + 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/hotspot/share/ci/ciField.cpp b/src/hotspot/share/ci/ciField.cpp index 43bba24a6fb85..0bddef4868758 100644 --- a/src/hotspot/share/ci/ciField.cpp +++ b/src/hotspot/share/ci/ciField.cpp @@ -253,10 +253,8 @@ static bool trust_final_non_static_fields(ciInstanceKlass* holder) { return TrustFinalNonStaticFields; } -const char* stable_value_klass_name = "java/lang/StableValue"; - static bool trust_final_non_static_fields_of_type(Symbol* signature) { - return (signature->equals(stable_value_klass_name) == 0); + return (signature == vmSymbols::java_lang_StableValue_signature()); } void ciField::initialize_from(fieldDescriptor* fd) { diff --git a/src/hotspot/share/classfile/vmSymbols.hpp b/src/hotspot/share/classfile/vmSymbols.hpp index 4bead4f0693b1..b2a234bc6abf2 100644 --- a/src/hotspot/share/classfile/vmSymbols.hpp +++ b/src/hotspot/share/classfile/vmSymbols.hpp @@ -747,6 +747,7 @@ class SerializeClosure; \ /* StableValues & Collections */ \ template(java_lang_StableValue, "java/lang/StableValue") \ + template(java_lang_StableValue_signature, "Ljava/lang/StableValue;") \ /*end*/ // enum for figuring positions and size of Symbol::_vm_symbols[] From e310445f190a67ac870c9823d7894bc4eff2f99c Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Fri, 26 Jul 2024 10:09:03 +0200 Subject: [PATCH 065/327] Fix typo --- src/jdk.unsupported/share/classes/sun/misc/Unsafe.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jdk.unsupported/share/classes/sun/misc/Unsafe.java b/src/jdk.unsupported/share/classes/sun/misc/Unsafe.java index 6398a2b3f5f2e..01351e600bdf9 100644 --- a/src/jdk.unsupported/share/classes/sun/misc/Unsafe.java +++ b/src/jdk.unsupported/share/classes/sun/misc/Unsafe.java @@ -993,7 +993,7 @@ private static void assertNotTrusted(Field f) { throw new UnsupportedOperationException("can't get base address on a record class: " + f); } Class fieldType = f.getType(); - // Todo: Change to "java.lang.StableValue.class.isAssignableFrom(fieldType)" after StableValue exists preview + // Todo: Change to "java.lang.StableValue.class.isAssignableFrom(fieldType)" after StableValue exits preview if (fieldType.getName().equals("java.lang.StableValue")) { throw new UnsupportedOperationException("can't get field offset for a field of type " + fieldType.getName() + ": " + f); } From c06533043700f01f2905919e050dc0574a344070 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Fri, 26 Jul 2024 10:10:43 +0200 Subject: [PATCH 066/327] Improve logical expression --- src/hotspot/share/runtime/fieldDescriptor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hotspot/share/runtime/fieldDescriptor.cpp b/src/hotspot/share/runtime/fieldDescriptor.cpp index aea75747ba387..cc6e749e42649 100644 --- a/src/hotspot/share/runtime/fieldDescriptor.cpp +++ b/src/hotspot/share/runtime/fieldDescriptor.cpp @@ -44,7 +44,7 @@ Symbol* fieldDescriptor::generic_signature() const { bool fieldDescriptor::is_trusted_final() const { InstanceKlass* ik = field_holder(); - return is_final() && (is_static() || ik->is_hidden() || ik->is_record() || ik->name()->equals("java/lang/StableValue") == 0); + return is_final() && (is_static() || ik->is_hidden() || ik->is_record() || signature() == vmSymbols::java_lang_StableValue_signature()); } AnnotationArray* fieldDescriptor::annotations() const { From 7a99e4212b3f1cec864da94ef91b78a53b0daa27 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Fri, 26 Jul 2024 10:35:15 +0200 Subject: [PATCH 067/327] Rework how final StableValue fields are protected --- .../share/classes/java/lang/reflect/Field.java | 4 ---- .../lang/StableValue/TrustedFieldTypeTest.java | 18 ++++++++++++++---- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/java.base/share/classes/java/lang/reflect/Field.java b/src/java.base/share/classes/java/lang/reflect/Field.java index 6ec8bb174fd16..12d30b29fdc6c 100644 --- a/src/java.base/share/classes/java/lang/reflect/Field.java +++ b/src/java.base/share/classes/java/lang/reflect/Field.java @@ -182,10 +182,6 @@ public void setAccessible(boolean flag) { @Override void checkCanSetAccessible(Class caller) { - if (isTrustedFinal()) { - throw newInaccessibleObjectException("Unable to make field " + this + " accessible " + - "because it is a trusted final field"); - } checkCanSetAccessible(caller, clazz); } diff --git a/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java b/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java index 6bf557540dfa6..779ed86a61733 100644 --- a/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java +++ b/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java @@ -43,7 +43,7 @@ final class TrustedFieldTypeTest { @Test - void reflection() throws NoSuchFieldException { + void reflection() throws NoSuchFieldException, IllegalAccessException { final class Holder { private final StableValue value = StableValue.newInstance(); } @@ -52,11 +52,21 @@ final class HolderNonFinal { } Field valueField = Holder.class.getDeclaredField("value"); - assertThrows(InaccessibleObjectException.class, () -> - valueField.setAccessible(true) + valueField.setAccessible(true); + Holder holder = new Holder(); + // We should be able to read the StableValue field + Object read = valueField.get(holder); + // We should NOT be able to write to the StableValue field + assertThrows(IllegalAccessException.class, () -> + valueField.set(holder, StableValue.newInstance()) ); + Field valueNonFinal = HolderNonFinal.class.getDeclaredField("value"); - assertDoesNotThrow(() -> valueNonFinal.setAccessible(true)); + valueNonFinal.setAccessible(true); + HolderNonFinal holderNonFinal = new HolderNonFinal(); + // As the field is not final, both read and write should be ok (not trusted) + Object readNonFinal = valueNonFinal.get(holderNonFinal); + valueNonFinal.set(holderNonFinal, StableValue.newInstance()); } @SuppressWarnings("removal") From bf53cd68820d5f57a5d6919e495e8cf3664513ad Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Fri, 26 Jul 2024 10:38:20 +0200 Subject: [PATCH 068/327] Remove trailing spaces --- test/jdk/java/lang/StableValue/JEP.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/jdk/java/lang/StableValue/JEP.md b/test/jdk/java/lang/StableValue/JEP.md index 186f8734399e0..09e50626264e2 100644 --- a/test/jdk/java/lang/StableValue/JEP.md +++ b/test/jdk/java/lang/StableValue/JEP.md @@ -467,7 +467,7 @@ static Logger logger(String name) { ``` In the example above, only two input values were used. However, this concept allows declaring a -large number of stable values which can be easily retrieved using arbitrarily, but pre-specified, +large number of stable values which can be easily retrieved using arbitrarily, but pre-specified, keys in a resource-efficient and performant way. For example, high-performance, non-evicting caches may now be easily and reliably realized. @@ -477,7 +477,7 @@ even though used from several threads. Even though a cached `IntFunction` may sometimes replace a lazy `List` and a cached `Function` may be used in place of a lazy `Map`, a lazy `List` or `Map` oftentimes provides better interoperability with -existing libraries. For example, if provided as a method parameter. +existing libraries. For example, if provided as a method parameter. ### Preview feature From 8c39cc1d3213c6ec7df027602aa367ec508ce950 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Fri, 26 Jul 2024 11:10:07 +0200 Subject: [PATCH 069/327] Clean up --- make/test/BuildMicrobenchmark.gmk | 1 - src/hotspot/share/classfile/vmSymbols.hpp | 2 +- src/hotspot/share/runtime/fieldDescriptor.cpp | 2 +- .../share/classes/java/lang/reflect/Field.java | 4 +--- .../classes/java/util/ImmutableCollections.java | 12 +++--------- .../internal/access/JavaUtilCollectionAccess.java | 2 +- .../java/lang/StableValue/CachingFunctionTest.java | 2 -- .../lang/StableValue/CachingIntFunctionTest.java | 4 +--- .../java/lang/StableValue/CachingSupplierTest.java | 2 -- test/jdk/java/lang/StableValue/JepTest.java | 3 --- test/jdk/java/lang/StableValue/LazyListTest.java | 2 -- test/jdk/java/lang/StableValue/LazyMapTest.java | 4 +--- test/jdk/java/lang/StableValue/StableValueTest.java | 2 -- .../StableValue/StableValuesSafePublicationTest.java | 2 -- .../java/lang/StableValue/TrustedFieldTypeTest.java | 3 --- .../bench/java/lang/stable/StableValueBenchmark.java | 2 -- 16 files changed, 9 insertions(+), 40 deletions(-) diff --git a/make/test/BuildMicrobenchmark.gmk b/make/test/BuildMicrobenchmark.gmk index e0970f887ab83..32d26be22702b 100644 --- a/make/test/BuildMicrobenchmark.gmk +++ b/make/test/BuildMicrobenchmark.gmk @@ -109,7 +109,6 @@ $(eval $(call SetupJavaCompilation, BUILD_JDK_MICROBENCHMARK, \ --add-exports java.base/jdk.internal.vm=ALL-UNNAMED \ --add-exports java.base/sun.invoke.util=ALL-UNNAMED \ --add-exports java.base/sun.security.util=ALL-UNNAMED \ - --add-exports java.base/jdk.internal.lang=ALL-UNNAMED \ --add-exports java.base/sun.security.util.math=ALL-UNNAMED \ --add-exports java.base/sun.security.util.math.intpoly=ALL-UNNAMED \ --enable-preview \ diff --git a/src/hotspot/share/classfile/vmSymbols.hpp b/src/hotspot/share/classfile/vmSymbols.hpp index b2a234bc6abf2..ca55ef1f1fd47 100644 --- a/src/hotspot/share/classfile/vmSymbols.hpp +++ b/src/hotspot/share/classfile/vmSymbols.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2024, 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 diff --git a/src/hotspot/share/runtime/fieldDescriptor.cpp b/src/hotspot/share/runtime/fieldDescriptor.cpp index cc6e749e42649..5268a457db34f 100644 --- a/src/hotspot/share/runtime/fieldDescriptor.cpp +++ b/src/hotspot/share/runtime/fieldDescriptor.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2024, 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 diff --git a/src/java.base/share/classes/java/lang/reflect/Field.java b/src/java.base/share/classes/java/lang/reflect/Field.java index 12d30b29fdc6c..928bdaeb65b21 100644 --- a/src/java.base/share/classes/java/lang/reflect/Field.java +++ b/src/java.base/share/classes/java/lang/reflect/Field.java @@ -174,9 +174,7 @@ Field copy() { @CallerSensitive public void setAccessible(boolean flag) { AccessibleObject.checkPermission(); - if (flag) { - checkCanSetAccessible(Reflection.getCallerClass()); - } + if (flag) checkCanSetAccessible(Reflection.getCallerClass()); setAccessible0(flag); } diff --git a/src/java.base/share/classes/java/util/ImmutableCollections.java b/src/java.base/share/classes/java/util/ImmutableCollections.java index e2665d789015a..4e7ee630555c4 100644 --- a/src/java.base/share/classes/java/util/ImmutableCollections.java +++ b/src/java.base/share/classes/java/util/ImmutableCollections.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2024, 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 @@ -779,9 +779,7 @@ static final class LazyList extends AbstractImmutableList { this.backing = StableValueImpl.ofList(size); } - @Override public boolean isEmpty() { - return backing.isEmpty(); - } + @Override public boolean isEmpty() { return backing.isEmpty();} @Override public int size() { return backing.size(); } @Override public Object[] toArray() { return copyInto(new Object[size()]); } @@ -1523,11 +1521,7 @@ final class LazyMapEntrySet extends AbstractImmutableSet> { @Override public Iterator> iterator() { return new LazyMapIterator(); } @Override public int size() { return delegateEntrySet.size(); } - - @Override - public int hashCode() { - return LazyMap.this.hashCode(); - } + @Override public int hashCode() { return LazyMap.this.hashCode(); } @jdk.internal.ValueBased final class LazyMapIterator implements Iterator> { diff --git a/src/java.base/share/classes/jdk/internal/access/JavaUtilCollectionAccess.java b/src/java.base/share/classes/jdk/internal/access/JavaUtilCollectionAccess.java index 3d09b893fdf85..12bca3f0571b1 100644 --- a/src/java.base/share/classes/jdk/internal/access/JavaUtilCollectionAccess.java +++ b/src/java.base/share/classes/jdk/internal/access/JavaUtilCollectionAccess.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024 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 diff --git a/test/jdk/java/lang/StableValue/CachingFunctionTest.java b/test/jdk/java/lang/StableValue/CachingFunctionTest.java index 3b8ed2cf89158..ab04d162c7ae3 100644 --- a/test/jdk/java/lang/StableValue/CachingFunctionTest.java +++ b/test/jdk/java/lang/StableValue/CachingFunctionTest.java @@ -23,12 +23,10 @@ /* @test * @summary Basic tests for CachingFunction methods - * @modules java.base/jdk.internal.lang * @compile --enable-preview -source ${jdk.version} CachingFunctionTest.java * @run junit/othervm --enable-preview CachingFunctionTest */ -import java.lang.StableValue; import org.junit.jupiter.api.Test; import java.util.Set; diff --git a/test/jdk/java/lang/StableValue/CachingIntFunctionTest.java b/test/jdk/java/lang/StableValue/CachingIntFunctionTest.java index f5a879c2b96d0..7b7806aad30da 100644 --- a/test/jdk/java/lang/StableValue/CachingIntFunctionTest.java +++ b/test/jdk/java/lang/StableValue/CachingIntFunctionTest.java @@ -23,12 +23,10 @@ /* @test * @summary Basic tests for CachingIntFunction methods - * @modules java.base/jdk.internal.lang - * @compile --enable-preview -source ${jdk.version} CachingIntFunctionTest.java + * @compile --enable-preview -source ${jdk.version} CachingIntFunctionTest.java * @run junit/othervm --enable-preview CachingIntFunctionTest */ -import java.lang.StableValue; import org.junit.jupiter.api.Test; import java.util.concurrent.ThreadFactory; diff --git a/test/jdk/java/lang/StableValue/CachingSupplierTest.java b/test/jdk/java/lang/StableValue/CachingSupplierTest.java index e0144577b66e1..0ec21ca8ad7ff 100644 --- a/test/jdk/java/lang/StableValue/CachingSupplierTest.java +++ b/test/jdk/java/lang/StableValue/CachingSupplierTest.java @@ -23,12 +23,10 @@ /* @test * @summary Basic tests for CachingSupplier methods - * @modules java.base/jdk.internal.lang * @compile --enable-preview -source ${jdk.version} CachingSupplierTest.java * @run junit/othervm --enable-preview CachingSupplierTest */ -import java.lang.StableValue; import org.junit.jupiter.api.Test; import java.util.concurrent.ThreadFactory; diff --git a/test/jdk/java/lang/StableValue/JepTest.java b/test/jdk/java/lang/StableValue/JepTest.java index df8df4cb62251..a040f536ee5aa 100644 --- a/test/jdk/java/lang/StableValue/JepTest.java +++ b/test/jdk/java/lang/StableValue/JepTest.java @@ -23,13 +23,10 @@ /* @test * @summary Basic tests for JepTest implementations - * @modules java.base/jdk.internal.lang * @compile --enable-preview -source ${jdk.version} JepTest.java * @run junit/othervm --enable-preview JepTest */ -import java.lang.StableValue; - import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Files; diff --git a/test/jdk/java/lang/StableValue/LazyListTest.java b/test/jdk/java/lang/StableValue/LazyListTest.java index 8e03622f832ca..4c592eab28b4b 100644 --- a/test/jdk/java/lang/StableValue/LazyListTest.java +++ b/test/jdk/java/lang/StableValue/LazyListTest.java @@ -23,12 +23,10 @@ /* @test * @summary Basic tests for LazyList methods - * @modules java.base/jdk.internal.lang * @compile --enable-preview -source ${jdk.version} LazyListTest.java * @run junit/othervm --enable-preview LazyListTest */ -import java.lang.StableValue; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; diff --git a/test/jdk/java/lang/StableValue/LazyMapTest.java b/test/jdk/java/lang/StableValue/LazyMapTest.java index bd70f08b38bff..565b99699f428 100644 --- a/test/jdk/java/lang/StableValue/LazyMapTest.java +++ b/test/jdk/java/lang/StableValue/LazyMapTest.java @@ -23,12 +23,10 @@ /* @test * @summary Basic tests for LazyMap methods - * @modules java.base/jdk.internal.lang - * @compile --enable-preview -source ${jdk.version} LazyMapTest.java + * @compile --enable-preview -source ${jdk.version} LazyMapTest.java * @run junit/othervm --enable-preview LazyMapTest */ -import java.lang.StableValue; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; diff --git a/test/jdk/java/lang/StableValue/StableValueTest.java b/test/jdk/java/lang/StableValue/StableValueTest.java index e65d780fa86df..a97b2bd62b917 100644 --- a/test/jdk/java/lang/StableValue/StableValueTest.java +++ b/test/jdk/java/lang/StableValue/StableValueTest.java @@ -23,13 +23,11 @@ /* @test * @summary Basic tests for StableValue implementations - * @modules java.base/jdk.internal.lang * @modules java.base/jdk.internal.lang.stable * @compile --enable-preview -source ${jdk.version} StableValueTest.java * @run junit/othervm --enable-preview StableValueTest */ -import java.lang.StableValue; import jdk.internal.lang.stable.StableValueImpl; import org.junit.jupiter.api.Test; diff --git a/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java b/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java index f2cbdcd2d604c..c8a14b0cf18e8 100644 --- a/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java +++ b/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java @@ -23,13 +23,11 @@ /* @test * @summary Basic tests for making sure StableValue publishes values safely - * @modules java.base/jdk.internal.lang * @modules java.base/jdk.internal.misc * @compile --enable-preview -source ${jdk.version} StableValuesSafePublicationTest.java * @run junit/othervm --enable-preview StableValuesSafePublicationTest */ -import java.lang.StableValue; import org.junit.jupiter.api.Test; import java.util.Arrays; diff --git a/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java b/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java index 779ed86a61733..7f6f8df980385 100644 --- a/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java +++ b/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java @@ -24,19 +24,16 @@ /* @test * @summary Basic tests for TrustedFieldType implementations * @modules jdk.unsupported/sun.misc - * @modules java.base/jdk.internal.lang * @modules java.base/jdk.internal.lang.stable * @compile --enable-preview -source ${jdk.version} TrustedFieldTypeTest.java * @run junit/othervm --enable-preview TrustedFieldTypeTest */ -import java.lang.StableValue; import org.junit.jupiter.api.Test; import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; import java.lang.reflect.Field; -import java.lang.reflect.InaccessibleObjectException; import static org.junit.jupiter.api.Assertions.*; diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java index 79ac500dde1dc..f18f56f4293aa 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java @@ -23,7 +23,6 @@ package org.openjdk.bench.java.lang.stable; -import java.lang.StableValue; import org.openjdk.jmh.annotations.*; import java.util.concurrent.TimeUnit; @@ -39,7 +38,6 @@ @Warmup(iterations = 5, time = 1) @Measurement(iterations = 5, time = 2) @Fork(value = 2, jvmArgsAppend = { - "--add-exports=java.base/jdk.internal.lang=ALL-UNNAMED", "--enable-preview", // Prevent the use of uncommon traps "-XX:PerMethodTrapLimit=0"}) From d02cd3327fafaf9a1248a57bbc61d2ca8774eadb Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Fri, 26 Jul 2024 11:25:24 +0200 Subject: [PATCH 070/327] Break out method --- .../internal/lang/stable/StableValueImpl.java | 36 +++++++++++-------- 1 file changed, 21 insertions(+), 15 deletions(-) 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 d3eac44125284..cbf18353bb526 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 @@ -71,21 +71,7 @@ public boolean trySet(T value) { if (value() != null) { return false; } - - // Prevents reordering of store operations with other store operations. - // This means any stores made to fields in the `value` object prior to this - // point cannot be reordered with the CAS operation of the reference to the - // `value` field. - // In other words, if a loader (using plain memory semantics) can observe a - // `value` reference, any field updates made prior to this fence are - // guaranteed to be seen. - // See https://gee.cs.oswego.edu/dl/html/j9mm.html "Mixed Modes and Specializations", - // Doug Lea, 2018 - UNSAFE.storeStoreFence(); - - // This upholds the invariant, the `@Stable value` field is written to - // at most once. - return UNSAFE.compareAndSetReference(this, VALUE_OFFSET, null, wrap(value)); + return safelyPublish(this, VALUE_OFFSET, value); } @ForceInline @@ -148,6 +134,7 @@ private T valuePlain() { } // Wraps `null` values into a sentinel value + @ForceInline private static T wrap(T t) { return (t == null) ? nullSentinel() : t; } @@ -159,6 +146,7 @@ public static T unwrap(T t) { } @SuppressWarnings("unchecked") + @ForceInline private static T nullSentinel() { return (T) NULL_SENTINEL; } @@ -167,6 +155,24 @@ private static String render(T t) { return (t == null) ? ".unset" : "[" + unwrap(t) + "]"; } + @ForceInline + private static boolean safelyPublish(Object o, long offset, Object value) { + + // Prevents reordering of store operations with other store operations. + // This means any stores made to a field prior to this point cannot be + // reordered with the following CAS operation of the reference to the field. + + // In other words, if a loader (using plain memory semantics) can first observe + // a holder reference, any field updates in the holder reference made prior to + // this fence are guaranteed to be seen. + // See https://gee.cs.oswego.edu/dl/html/j9mm.html "Mixed Modes and Specializations", + // Doug Lea, 2018 + UNSAFE.storeStoreFence(); + + // This upholds the invariant, a `@Stable` field is written to at most once. + return UNSAFE.compareAndSetReference(o, offset, null, wrap(value)); + } + // Factories public static StableValueImpl newInstance() { From 1439dcfbad16d992a4c11befdae3d2cca7171700 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Fri, 26 Jul 2024 13:27:37 +0200 Subject: [PATCH 071/327] Revert back to using a utility class --- .../java/util/ImmutableCollections.java | 9 +-- .../internal/lang/stable/CachedFunction.java | 4 +- .../lang/stable/CachedIntFunction.java | 4 +- .../internal/lang/stable/CachedSupplier.java | 4 +- .../internal/lang/stable/StableValueImpl.java | 60 ++----------------- .../internal/lang/stable/StableValueUtil.java | 57 ++++++++++++++++++ 6 files changed, 74 insertions(+), 64 deletions(-) create mode 100644 src/java.base/share/classes/jdk/internal/lang/stable/StableValueUtil.java diff --git a/src/java.base/share/classes/java/util/ImmutableCollections.java b/src/java.base/share/classes/java/util/ImmutableCollections.java index 4e7ee630555c4..c4ee77c542cc0 100644 --- a/src/java.base/share/classes/java/util/ImmutableCollections.java +++ b/src/java.base/share/classes/java/util/ImmutableCollections.java @@ -42,6 +42,7 @@ import jdk.internal.access.JavaUtilCollectionAccess; import jdk.internal.access.SharedSecrets; import jdk.internal.lang.stable.StableValueImpl; +import jdk.internal.lang.stable.StableValueUtil; import jdk.internal.misc.CDS; import jdk.internal.util.NullableKeyValueHolder; import jdk.internal.vm.annotation.ForceInline; @@ -789,12 +790,12 @@ public E get(int i) { final StableValueImpl stable = backing.get(i); E e = stable.value(); if (e != null) { - return StableValueImpl.unwrap(e); + return StableValueUtil.unwrap(e); } synchronized (stable) { e = stable.value(); if (e != null) { - return StableValueImpl.unwrap(e); + return StableValueUtil.unwrap(e); } e = mapper.apply(i); stable.setOrThrow(e); @@ -1496,12 +1497,12 @@ public V get(Object key) { V computeIfUnset(K key, StableValueImpl stable) { V v = stable.value(); if (v != null) { - return StableValueImpl.unwrap(v); + return StableValueUtil.unwrap(v); } synchronized (stable) { v = stable.value(); if (v != null) { - return StableValueImpl.unwrap(v); + return StableValueUtil.unwrap(v); } v = mapper.apply(key); stable.setOrThrow(v); diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/CachedFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/CachedFunction.java index ac0335beccb95..499b30ce14fb8 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/CachedFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/CachedFunction.java @@ -45,12 +45,12 @@ public R apply(T value) { } R r = stable.value(); if (r != null) { - return StableValueImpl.unwrap(r); + return StableValueUtil.unwrap(r); } synchronized (stable) { r = stable.value(); if (r != null) { - return StableValueImpl.unwrap(r); + return StableValueUtil.unwrap(r); } r = original.apply(value); stable.setOrThrow(r); diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/CachedIntFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/CachedIntFunction.java index 9006662cf63b3..0b222db0553a0 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/CachedIntFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/CachedIntFunction.java @@ -47,12 +47,12 @@ public R apply(int value) { } R r = stable.value(); if (r != null) { - return StableValueImpl.unwrap(r); + return StableValueUtil.unwrap(r); } synchronized (stable) { r = stable.value(); if (r != null) { - return StableValueImpl.unwrap(r); + return StableValueUtil.unwrap(r); } r = original.apply(value); stable.setOrThrow(r); diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/CachedSupplier.java b/src/java.base/share/classes/jdk/internal/lang/stable/CachedSupplier.java index 40e8e8bc0e79d..28800c684d63b 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/CachedSupplier.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/CachedSupplier.java @@ -36,12 +36,12 @@ public record CachedSupplier(StableValueImpl stable, public T get() { T t = stable.value(); if (t != null) { - return StableValueImpl.unwrap(t); + return StableValueUtil.unwrap(t); } synchronized (stable) { t = stable.value(); if (t != null) { - return StableValueImpl.unwrap(t); + return StableValueUtil.unwrap(t); } t = original.get(); stable.setOrThrow(t); 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 cbf18353bb526..5b7e417dd4dba 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 @@ -25,7 +25,6 @@ package jdk.internal.lang.stable; -import jdk.internal.misc.Unsafe; import jdk.internal.vm.annotation.ForceInline; import jdk.internal.vm.annotation.Stable; @@ -37,16 +36,9 @@ public final class StableValueImpl implements StableValue { - // Unsafe allows StableValue to be used early in the boot sequence - private static final Unsafe UNSAFE = Unsafe.getUnsafe(); - - // Used to indicate a holder value is `null` (see field `value` below) - // A wrapper method `nullSentinel()` is used for generic type conversion. - private static final Object NULL_SENTINEL = new Object(); - // Unsafe offsets for direct object access private static final long VALUE_OFFSET = - UNSAFE.objectFieldOffset(StableValueImpl.class, "value"); + StableValueUtil.UNSAFE.objectFieldOffset(StableValueImpl.class, "value"); // Generally, fields annotated with `@Stable` are accessed by the JVM using special // memory semantics rules (see `parse.hpp` and `parse(1|2|3).cpp`). @@ -71,7 +63,7 @@ public boolean trySet(T value) { if (value() != null) { return false; } - return safelyPublish(this, VALUE_OFFSET, value); + return StableValueUtil.safelyPublish(this, VALUE_OFFSET, value); } @ForceInline @@ -79,7 +71,7 @@ public boolean trySet(T value) { public T orElseThrow() { final T t = value(); if (t != null) { - return unwrap(t); + return StableValueUtil.unwrap(t); } throw new NoSuchElementException("No value set"); } @@ -89,7 +81,7 @@ public T orElseThrow() { public T orElse(T other) { final T t = value(); if (t != null) { - return unwrap(t); + return StableValueUtil.unwrap(t); } return other; } @@ -115,7 +107,7 @@ public boolean equals(Object obj) { @Override public String toString() { - return "StableValue" + render(value()); + return "StableValue" + StableValueUtil.render(value()); } @SuppressWarnings("unchecked") @@ -124,7 +116,7 @@ public String toString() { // If not set, fall back to `volatile` memory semantics. public T value() { final T t = valuePlain(); - return t != null ? t : (T) UNSAFE.getReferenceVolatile(this, VALUE_OFFSET); + return t != null ? t : (T) StableValueUtil.UNSAFE.getReferenceVolatile(this, VALUE_OFFSET); } @ForceInline @@ -133,46 +125,6 @@ private T valuePlain() { return value; } - // Wraps `null` values into a sentinel value - @ForceInline - private static T wrap(T t) { - return (t == null) ? nullSentinel() : t; - } - - // Unwraps null sentinel values into `null` - @ForceInline - public static T unwrap(T t) { - return t != nullSentinel() ? t : null; - } - - @SuppressWarnings("unchecked") - @ForceInline - private static T nullSentinel() { - return (T) NULL_SENTINEL; - } - - private static String render(T t) { - return (t == null) ? ".unset" : "[" + unwrap(t) + "]"; - } - - @ForceInline - private static boolean safelyPublish(Object o, long offset, Object value) { - - // Prevents reordering of store operations with other store operations. - // This means any stores made to a field prior to this point cannot be - // reordered with the following CAS operation of the reference to the field. - - // In other words, if a loader (using plain memory semantics) can first observe - // a holder reference, any field updates in the holder reference made prior to - // this fence are guaranteed to be seen. - // See https://gee.cs.oswego.edu/dl/html/j9mm.html "Mixed Modes and Specializations", - // Doug Lea, 2018 - UNSAFE.storeStoreFence(); - - // This upholds the invariant, a `@Stable` field is written to at most once. - return UNSAFE.compareAndSetReference(o, offset, null, wrap(value)); - } - // Factories public static StableValueImpl newInstance() { diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableValueUtil.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableValueUtil.java new file mode 100644 index 0000000000000..31f39a3221422 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableValueUtil.java @@ -0,0 +1,57 @@ +package jdk.internal.lang.stable; + +import jdk.internal.misc.Unsafe; +import jdk.internal.vm.annotation.ForceInline; + +public final class StableValueUtil { + + private StableValueUtil() {} + + // Unsafe allows StableValue to be used early in the boot sequence + static final Unsafe UNSAFE = Unsafe.getUnsafe(); + + // Used to indicate a holder value is `null` (see field `value` below) + // A wrapper method `nullSentinel()` is used for generic type conversion. + static final Object NULL_SENTINEL = new Object(); + + // Wraps `null` values into a sentinel value + @ForceInline + static T wrap(T t) { + return (t == null) ? nullSentinel() : t; + } + + // Unwraps null sentinel values into `null` + @ForceInline + public static T unwrap(T t) { + return t != nullSentinel() ? t : null; + } + + @SuppressWarnings("unchecked") + @ForceInline + static T nullSentinel() { + return (T) NULL_SENTINEL; + } + + static String render(T t) { + return (t == null) ? ".unset" : "[" + unwrap(t) + "]"; + } + + @ForceInline + static boolean safelyPublish(Object o, long offset, Object value) { + + // Prevents reordering of store operations with other store operations. + // This means any stores made to a field prior to this point cannot be + // reordered with the following CAS operation of the reference to the field. + + // In other words, if a loader (using plain memory semantics) can first observe + // a holder reference, any field updates in the holder reference made prior to + // this fence are guaranteed to be seen. + // See https://gee.cs.oswego.edu/dl/html/j9mm.html "Mixed Modes and Specializations", + // Doug Lea, 2018 + UNSAFE.storeStoreFence(); + + // This upholds the invariant, a `@Stable` field is written to at most once. + return UNSAFE.compareAndSetReference(o, offset, null, wrap(value)); + } + +} From 31e2c75d4dce46298d498c0d1db5c61517cc991f Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Fri, 26 Jul 2024 13:43:01 +0200 Subject: [PATCH 072/327] Allow internal use of factories --- .../share/classes/java/lang/StableValue.java | 48 +++-------- .../java/util/ImmutableCollections.java | 4 +- .../internal/lang/stable/CachedFunction.java | 2 +- .../lang/stable/CachedIntFunction.java | 2 +- .../internal/lang/stable/StableValueImpl.java | 30 +------ .../internal/lang/stable/StableValueUtil.java | 86 +++++++++++++++++++ .../lang/StableValue/StableValueTest.java | 5 +- 7 files changed, 106 insertions(+), 71 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 00315c52d2b31..b0be761d2f79d 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -31,6 +31,7 @@ import jdk.internal.lang.stable.CachedIntFunction; import jdk.internal.lang.stable.CachedSupplier; import jdk.internal.lang.stable.StableValueImpl; +import jdk.internal.lang.stable.StableValueUtil; import java.io.Serializable; import java.util.List; @@ -249,19 +250,7 @@ static Supplier newCachingSupplier(Supplier original, ThreadFactory factory) { Objects.requireNonNull(original); // `factory` is nullable - - final Supplier memoized = CachedSupplier.of(original); - - if (factory != null) { - final Thread thread = factory.newThread(new Runnable() { - @Override - public void run() { - memoized.get(); - } - }); - thread.start(); - } - return memoized; + return StableValueUtil.newCachingSupplier(original, factory); } /** @@ -301,19 +290,12 @@ public void run() { static IntFunction newCachingIntFunction(int size, IntFunction original, ThreadFactory factory) { - - final IntFunction memoized = CachedIntFunction.of(size, original); - - if (factory != null) { - for (int i = 0; i < size; i++) { - final int input = i; - final Thread thread = factory.newThread(new Runnable() { - @Override public void run() { memoized.apply(input); } - }); - thread.start(); - } + if (size < 0) { + throw new IllegalStateException(); } - return memoized; + Objects.requireNonNull(original); + // `factory` is nullable + return StableValueUtil.newCachingIntFunction(size, original, factory); } /** @@ -353,18 +335,10 @@ static IntFunction newCachingIntFunction(int size, static Function newCachingFunction(Set inputs, Function original, ThreadFactory factory) { - - final Function memoized = CachedFunction.of(inputs, original); - - if (factory != null) { - for (final T t : inputs) { - final Thread thread = factory.newThread(new Runnable() { - @Override public void run() { memoized.apply(t); } - }); - thread.start(); - } - } - return memoized; + Objects.requireNonNull(inputs); + Objects.requireNonNull(original); + // `factory` is nullable + return StableValueUtil.newCachingFunction(inputs, original, factory); } /** diff --git a/src/java.base/share/classes/java/util/ImmutableCollections.java b/src/java.base/share/classes/java/util/ImmutableCollections.java index c4ee77c542cc0..9deb6fef3d448 100644 --- a/src/java.base/share/classes/java/util/ImmutableCollections.java +++ b/src/java.base/share/classes/java/util/ImmutableCollections.java @@ -777,7 +777,7 @@ static final class LazyList extends AbstractImmutableList { LazyList(int size, IntFunction mapper) { this.mapper = mapper; - this.backing = StableValueImpl.ofList(size); + this.backing = StableValueUtil.ofList(size); } @Override public boolean isEmpty() { return backing.isEmpty();} @@ -1474,7 +1474,7 @@ static final class LazyMap LazyMap(Set keys, Function mapper) { this.mapper = mapper; - this.delegate = StableValueImpl.ofMap(keys); + this.delegate = StableValueUtil.ofMap(keys); } @Override public boolean containsKey(Object o) { return delegate.containsKey(o); } diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/CachedFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/CachedFunction.java index 499b30ce14fb8..e174803c645c6 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/CachedFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/CachedFunction.java @@ -60,7 +60,7 @@ public R apply(T value) { public static CachedFunction of(Set inputs, Function original) { - return new CachedFunction<>(StableValueImpl.ofMap(inputs), original); + return new CachedFunction<>(StableValueUtil.ofMap(inputs), original); } } diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/CachedIntFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/CachedIntFunction.java index 0b222db0553a0..e090393add028 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/CachedIntFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/CachedIntFunction.java @@ -61,7 +61,7 @@ public R apply(int value) { } public static CachedIntFunction of(int size, IntFunction original) { - return new CachedIntFunction<>(StableValueImpl.ofList(size), original); + return new CachedIntFunction<>(StableValueUtil.ofList(size), original); } } 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 5b7e417dd4dba..7aef2717614e2 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 @@ -28,15 +28,12 @@ import jdk.internal.vm.annotation.ForceInline; import jdk.internal.vm.annotation.Stable; -import java.util.List; -import java.util.Map; import java.util.NoSuchElementException; import java.util.Objects; -import java.util.Set; public final class StableValueImpl implements StableValue { - // Unsafe offsets for direct object access + // Unsafe offsets for direct field access private static final long VALUE_OFFSET = StableValueUtil.UNSAFE.objectFieldOffset(StableValueImpl.class, "value"); @@ -125,33 +122,10 @@ private T valuePlain() { return value; } - // Factories + // Factory public static StableValueImpl newInstance() { return new StableValueImpl<>(); } - public static List> ofList(int size) { - if (size < 0) { - throw new IllegalArgumentException(); - } - @SuppressWarnings("unchecked") - final var stableValues = (StableValueImpl[]) new StableValueImpl[size]; - for (int i = 0; i < size; i++) { - stableValues[i] = newInstance(); - } - return List.of(stableValues); - } - - public static Map> ofMap(Set keys) { - Objects.requireNonNull(keys); - @SuppressWarnings("unchecked") - final var entries = (Map.Entry>[]) new Map.Entry[keys.size()]; - int i = 0; - for (K key : keys) { - entries[i++] = Map.entry(key, newInstance()); - } - return Map.ofEntries(entries); - } - } diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableValueUtil.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableValueUtil.java index 31f39a3221422..ff0864cc93204 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableValueUtil.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableValueUtil.java @@ -3,6 +3,15 @@ import jdk.internal.misc.Unsafe; import jdk.internal.vm.annotation.ForceInline; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ThreadFactory; +import java.util.function.Function; +import java.util.function.IntFunction; +import java.util.function.Supplier; + public final class StableValueUtil { private StableValueUtil() {} @@ -54,4 +63,81 @@ static boolean safelyPublish(Object o, long offset, Object value) { return UNSAFE.compareAndSetReference(o, offset, null, wrap(value)); } + // Factories + + public static List> ofList(int size) { + if (size < 0) { + throw new IllegalArgumentException(); + } + @SuppressWarnings("unchecked") + final var stableValues = (StableValueImpl[]) new StableValueImpl[size]; + for (int i = 0; i < size; i++) { + stableValues[i] = StableValueImpl.newInstance(); + } + return List.of(stableValues); + } + + public static Map> ofMap(Set keys) { + Objects.requireNonNull(keys); + @SuppressWarnings("unchecked") + final var entries = (Map.Entry>[]) new Map.Entry[keys.size()]; + int i = 0; + for (K key : keys) { + entries[i++] = Map.entry(key, StableValueImpl.newInstance()); + } + return Map.ofEntries(entries); + } + + public static Supplier newCachingSupplier(Supplier original, + ThreadFactory factory) { + + final Supplier memoized = CachedSupplier.of(original); + + if (factory != null) { + final Thread thread = factory.newThread(new Runnable() { + @Override + public void run() { + memoized.get(); + } + }); + thread.start(); + } + return memoized; + } + + public static IntFunction newCachingIntFunction(int size, + IntFunction original, + ThreadFactory factory) { + + final IntFunction memoized = CachedIntFunction.of(size, original); + + if (factory != null) { + for (int i = 0; i < size; i++) { + final int input = i; + final Thread thread = factory.newThread(new Runnable() { + @Override public void run() { memoized.apply(input); } + }); + thread.start(); + } + } + return memoized; + } + + public static Function newCachingFunction(Set inputs, + Function original, + ThreadFactory factory) { + + final Function memoized = CachedFunction.of(inputs, original); + + if (factory != null) { + for (final T t : inputs) { + final Thread thread = factory.newThread(new Runnable() { + @Override public void run() { memoized.apply(t); } + }); + thread.start(); + } + } + return memoized; + } + } diff --git a/test/jdk/java/lang/StableValue/StableValueTest.java b/test/jdk/java/lang/StableValue/StableValueTest.java index a97b2bd62b917..ea8bd8f42210d 100644 --- a/test/jdk/java/lang/StableValue/StableValueTest.java +++ b/test/jdk/java/lang/StableValue/StableValueTest.java @@ -29,6 +29,7 @@ */ import jdk.internal.lang.stable.StableValueImpl; +import jdk.internal.lang.stable.StableValueUtil; import org.junit.jupiter.api.Test; import java.util.BitSet; @@ -108,7 +109,7 @@ void testEquals() { @Test void ofList() { - List> list = StableValueImpl.ofList(13); + List> list = StableValueUtil.ofList(13); assertEquals(13, list.size()); // Check, every StableValue is distinct Map, Boolean> idMap = new IdentityHashMap<>(); @@ -118,7 +119,7 @@ void ofList() { @Test void ofMap() { - Map> map = StableValueImpl.ofMap(Set.of(1, 2, 3)); + Map> map = StableValueUtil.ofMap(Set.of(1, 2, 3)); assertEquals(3, map.size()); // Check, every StableValue is distinct Map, Boolean> idMap = new IdentityHashMap<>(); From 2473ed718cdcd5458336c524ea83d5ed79e4e028 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 29 Jul 2024 11:29:18 +0200 Subject: [PATCH 073/327] Update JEP --- test/jdk/java/lang/StableValue/JEP.md | 212 +++++++++----------------- 1 file changed, 72 insertions(+), 140 deletions(-) diff --git a/test/jdk/java/lang/StableValue/JEP.md b/test/jdk/java/lang/StableValue/JEP.md index 09e50626264e2..5b7a2c4a09f91 100644 --- a/test/jdk/java/lang/StableValue/JEP.md +++ b/test/jdk/java/lang/StableValue/JEP.md @@ -21,58 +21,36 @@ This might be the subject of a future JEP. ## Motivation -Some internal JDK classes are relying heavily on the annotation `jdk.internal.vm.annotation.@Stable` -to mark scalar and array fields whose values or elements will change *at most once*, thereby providing -crucial performance, energy efficiency, and flexibility benefits. - -Unfortunately, the powerful `@Stable` annotation cannot be used directly by client code thereby severely -restricting its applicability. The Stable Values & Collections API rectifies this imbalance -between internal and client code by providing safe wrappers around the `@Stable` annotation. Hence, all _the -important benefits of `@Stable` are now made available to regular Java developers and third-party -library developers_. - -One of the benefits with `@Stable` is it makes a marked field eligible for [constant-folding](https://en.wikipedia.org/wiki/Constant_folding). -Publicly exposing `@Stable` without a safe API, like the Stable Values & Collections API, would have -rendered it unsafe as further updating a `@Stable` field after its initial update will result -in undefined behavior, as the JIT compiler might have *already* constant-folded the (now overwritten) -field value. - -### Existing solutions - -Most Java developers have heard the advice "prefer immutability" (Effective -Java, Third Edition, Item 17, by Joshua Bloch). Immutability confers many advantages including: - -* an immutable object can only be in one state -* the invariants of an immutable object can be enforced by its constructor -* immutable objects can be freely shared across threads -* immutability enables all manner of runtime optimizations - -Java's main tool for managing immutability is `final` fields (and more recently, `record` classes). -Unfortunately, `final` fields come with restrictions. Final instance fields must be set by the end of -the constructor, and `static final` fields during class initialization. Moreover, the order in which -`final` field initializers are executed is determined by the [textual order](https://docs.oracle.com/javase/specs/jls/se7/html/jls-13.html#jls-12.4.1) -and is then made explicit in the resulting class file. As such, the initialization of a `final` -field is fixed in time; it cannot be arbitrarily moved forward. In other words, developers -cannot cause specific constants to be initialized after the class or object is initialized. -This means that developers are forced to choose between finality and all its -benefits, and flexibility over the timing of initialization. Developers have -devised several strategies to ameliorate this imbalance, but none are -ideal. - -For instance, monolithic class initializers can be broken up by leveraging the -laziness already built into class loading. Often referred to as the -[_class-holder idiom_](https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom), -this technique moves lazily initialized state into a helper class which is then -loaded on-demand, so its initialization is only performed when the data is -actually needed, rather than unconditionally initializing constants when a class -is first referenced: +Most Java developers have heard the advice "prefer immutability" (Effective Java, Third Edition, Item 17, by +Joshua Bloch). Immutability confers many advantages, as immutable objects can be only in one state, and can +therefore be freely shared across multiple threads. + +Java's main tool for managing immutability is `final` fields (and more recently, `record` classes). Unfortunately, +`final` fields come with restrictions. Instance `final` fields must be set by the end of the constructor; `static +final` fields must be set during class initialization. Moreover, the order in which `final` fields are initialized +is determined by the [textual order](https://docs.oracle.com/javase/specs/jls/se7/html/jls-13.html#jls-12.4.1) in +which the fields are declared. This order is then made explicit in the resulting class file. + +As such, the initialization of a `final` field is fixed in time; it cannot be arbitrarily moved forward. In other +words, developers are forced to choose between finality and all its benefits, and flexibility over the timing of +initialization. Developers have devised several strategies to ameliorate this imbalance, but none are ideal. + +For instance, initialization of `static` and `final` fields can be broken up by leveraging the laziness already +built into class loading. Often referred to as the +[*class-holder idiom*](https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom), this technique moves +lazily initialized state into a helper class which is then loaded on-demand, so its initialization is only +performed when the data is actually needed, rather than unconditionally initializing constants when a class is +first referenced: + ``` // ordinary static initialization private static final Logger LOGGER = Logger.getLogger("com.company.Foo"); ... LOGGER.log(Level.DEBUG, ...); ``` -we can defer initialization until we actually need it: + +we can defer initialization until we actually need it, like so: + ``` // Initialization-on-demand holder idiom Logger logger() { @@ -84,29 +62,20 @@ Logger logger() { ... LOGGER.log(Level.DEBUG, ...); ``` -The code above ensures that the `Logger` object is created only when actually -required. The (possibly expensive) initializer for the logger lives in the -nested `Holder` class, which will only be initialized when the `logger` method -accesses the `LOGGER` field. While this idiom works well, its reliance on the -class loading process comes with significant drawbacks. First, each constant -whose computation needs to be deferred generally requires its own holder -class, thus introducing a significant static footprint cost. Second, this idiom -is only really applicable if the field initialization is suitably isolated, not -relying on any other parts of the object state. - -It should be noted that even though eventually outputting a message is slow compared to -obtaining the `Logger` instance itself, the `LOGGER::log`method starts with checking if -the selected `Level` is enabled or not. This latter check is a relatively fast operation -and so, in the case of disabled loggers, the `Logger` instance retrieval performance is -important. For example, logger output for `Level.DEBUG` is almost always disabled in production -environments. - -Alternatively, the [_double-checked locking idiom_](https://en.wikipedia.org/wiki/Double-checked_locking), can also be used -for deferring the evaluation of field initializers. The idea is to optimistically -check if the field's value is non-null and if so, use that value directly; but -if the value observed is null, then the field must be initialized, which, to be -safe under multi-threaded access, requires acquiring a lock to ensure + +The code above ensures that the `Logger` object is created only when actually required. The (possibly expensive) +initializer for the logger lives in the nested `Holder` class, which will only be initialized when the `logger` +method accesses the `LOGGER` field. While this idiom works well, its reliance on the class loading process comes +with significant drawbacks. First, each constant whose computation needs to be deferred generally requires its own +holder class, thus introducing a significant static footprint cost. Second, this idiom is only really applicable +if the field initialization is suitably isolated, not relying on any other parts of the object state. + +Alternatively, the [*double-checked locking idiom*](https://en.wikipedia.org/wiki/Double-checked_locking), can be +used for deferring the evaluation of instance field initializers. The idea is to optimistically check if the +field's value is non-null and if so, use that value directly; but if the value observed is null, then the field +must be initialized, which, to be safe under multi-threaded access, requires acquiring a lock to ensure correctness: + ``` // Double-checked locking idiom class Foo { @@ -127,86 +96,49 @@ class Foo { } } ``` -The double-checked locking idiom is brittle and easy to get -subtly wrong (see _Java Concurrency in Practice_, 16.2.4, by Brian Goetz) For example, a common error -is forgetting to declare the field `volatile` resulting in the risk of observing incomplete objects. -While the double-checked locking idiom can be used for both class and instance -variables, its usage requires that the field subject to initialization is marked -as non-final. This is not ideal for several reasons: +The double-checked locking idiom is brittle and easy to get subtly wrong (see *Java Concurrency in Practice*, +16.2.4, by Brian Goetz). For example, a common error is forgetting to declare the field `volatile` resulting in +the risk of observing incomplete objects. A more fundamental problem with the double-checked locking idiom is that +access to the `logger` field cannot be adequately optimized by just-in-time compilers, as they cannot reliably +assume that the field value will, in fact, change only once. -* it would be possible for code to accidentally modify the field value, thus violating -the immutability assumption of the enclosing class. -* access to the field cannot be adequately optimized by just-in-time compilers, as they -cannot reliably assume that the field value will, in fact, never change. An example of -similar optimizations in existing Java implementations is when a `MethodHandle` is held -in a `static final` field, allowing the runtime to generate machine code that is competitive -with direct invocation of the corresponding method. - -Furthermore, the idiom shown above needs to be modified to properly handle `null` values, for example -using a [sentinel](https://en.wikipedia.org/wiki/Sentinel_value) value. - -The situation is even worse when clients need to operate on a _collection_ of immutable values. - -An example of this is an array that holds HTML pages that correspond to an error code in the range [0, 7] -where each element is pulled in from the file system on-demand, once actually used: +Internal JDK classes can address some of the shortcomings of the approaches described above by using the internal +`jdk.internal.vm.annotation.@Stable` annotation. This annotation is used to mark scalar and array fields whose +values or elements will change *at most once*, thereby providing crucial performance, energy efficiency, and +flexibility benefits. With the help of `@Stable` the above example can be rewritten as follows: ``` -class ErrorMessages { - - private static final int SIZE = 8; - - // 1. Declare an array of error pages to serve up - private static final String[] MESSAGES = new String[SIZE]; +// Double-checked locking idiom +class Foo { - // 2. Define a function that is to be called the first - // time a particular message number is referenced - private static String readFromFile(int messageNumber) { - try { - return Files.readString(Path.of("message-" + messageNumber + ".html")); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } + @Stable + private Logger logger; - static synchronized String message(int messageNumber) { - // 3. Access the memoized array element under synchronization - // and compute-and-store if absent. - String page = MESSAGES[messageNumber]; - if (page == null) { - page = readFromFile(messageNumber); - MESSAGES[messageNumber] = page; + public Logger logger() { + if (logger == null) { + logger = Logger.getLogger("com.company.Foo"); } - return page; + return logger; } - - } -``` -We can now retrieve an error page like so: +} ``` -String errorPage = ErrorMessages.errorPage(2); -// -// -// -// Payment was denied: Insufficient funds. -// -``` +Note how the `logger` field no longer needs to be marked as `volatile`. And, since the JVM knows that `logger` can +change at most once, subsequent accesses to the `logger` field can be +[constant-folded](https://en.wikipedia.org/wiki/Constant_folding) away. Alas, the powerful `@Stable` is +fundamentally unsafe: as the attentive reader might have noticed, the above code is correct only as long as +`getLogger` returns, for any given string argument passed to it, a `Logger` object with the *same identity*. +This is crucial to ensure that the `logger` field is mutated only once: spurious racy updates to `logger` can be +safely ignored, as they don't affect the value stored in that field. + +What we are missing -- in all cases -- is a *safe* way to *promise* that a constant will be initialized by the +time it is used, with a value that is computed at most once. Such a mechanism would give the Java runtime maximum +opportunity to stage and optimize its computation, thus avoiding the penalties (static footprint, loss of runtime +optimizations) that plague the workarounds shown above, as well as the unsafety associated with the `@Stable` +annotation. Moreover, such a mechanism should gracefully scale to handle collections of constant values, while +retaining efficient computer resource management. -Unfortunately, this approach provides a plethora of challenges. First, retrieving the values -from a static array is slow, as said values cannot be constant-folded. Even worse, access to the -array is guarded by synchronization that is not only slow but will block access to the array for -all elements whenever one of the elements is under computation. Furthermore, the class holder idiom -(see above) is undoubtedly insufficient in this case, as the number of required holder classes is -*statically unbounded* - it depends on the value of the parameter `SIZE` which may change in future -variants of the code. - -What we are missing -- in all cases -- is a way to *promise* that a constant will be initialized -by the time it is used, with a value that is computed at most once. Such a mechanism would give -the Java runtime maximum opportunity to stage and optimize its computation, thus avoiding the penalties -(static footprint, loss of runtime optimizations) that plague the workarounds shown above. Moreover, such -a mechanism should gracefully scale to handle collections of constant values, while retaining efficient -computer resource management. ## Description @@ -267,7 +199,7 @@ ways, this is similar to the holder-class idiom in the sense it offers the same performance and constant-folding characteristics but, `StableValue` incurs a lower static footprint since no additional class is required. -Looking at the basic example above, it becomes evident, several threads may invoke the `Logger::getLogger` +Looking at the basic example above, it becomes evident, that several threads may invoke the `Logger::getLogger` method simultaneously if they call the `logger()` method at about the same time. Even though `StableValue` will guarantee, that only one of these results will ever be exposed to the many competing threads, there might be applications where it is a requirement, that a supplying method is @@ -316,8 +248,8 @@ by a lazily computed stable value. Analogous to how a `Supplier` can be cached using a backing stable value, a similar pattern can be used for an `IntFunction` that will record its cached values in a backing array of -stable value elements. Here is how the error message example above can be improved using -a caching `IntFunction`: +stable value elements. An example of this is an array that holds HTML pages that correspond to an error code in +the range [0, 7] where each element is pulled in from the file system on-demand: ``` class ErrorMessages { From ae575f9f016944864a83373159f6dc843bc5133a Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 29 Jul 2024 11:37:44 +0200 Subject: [PATCH 074/327] Reword JEP slightly --- test/jdk/java/lang/StableValue/JEP.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test/jdk/java/lang/StableValue/JEP.md b/test/jdk/java/lang/StableValue/JEP.md index 5b7a2c4a09f91..a6b4b8d914386 100644 --- a/test/jdk/java/lang/StableValue/JEP.md +++ b/test/jdk/java/lang/StableValue/JEP.md @@ -109,7 +109,7 @@ values or elements will change *at most once*, thereby providing crucial perform flexibility benefits. With the help of `@Stable` the above example can be rewritten as follows: ``` -// Double-checked locking idiom +// Double-checked locking idiom (JDK internal use only) class Foo { @Stable @@ -127,10 +127,9 @@ class Foo { Note how the `logger` field no longer needs to be marked as `volatile`. And, since the JVM knows that `logger` can change at most once, subsequent accesses to the `logger` field can be [constant-folded](https://en.wikipedia.org/wiki/Constant_folding) away. Alas, the powerful `@Stable` is -fundamentally unsafe: as the attentive reader might have noticed, the above code is correct only as long as +fundamentally _unsafe_: as the attentive reader might have noticed, the above code is correct only as long as `getLogger` returns, for any given string argument passed to it, a `Logger` object with the *same identity*. -This is crucial to ensure that the `logger` field is mutated only once: spurious racy updates to `logger` can be -safely ignored, as they don't affect the value stored in that field. +This is crucial to ensure that the `logger` field is mutated only once: spurious racy update attempts to `logger` can be safely ignored, only if they don't change the value stored in that field. What we are missing -- in all cases -- is a *safe* way to *promise* that a constant will be initialized by the time it is used, with a value that is computed at most once. Such a mechanism would give the Java runtime maximum From ba09af3904c9dcc244afd3bdc71a9ad8904afed6 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 29 Jul 2024 12:36:31 +0200 Subject: [PATCH 075/327] Remove trailing space --- test/jdk/java/lang/StableValue/JEP.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/jdk/java/lang/StableValue/JEP.md b/test/jdk/java/lang/StableValue/JEP.md index a6b4b8d914386..71ce91fbd44b2 100644 --- a/test/jdk/java/lang/StableValue/JEP.md +++ b/test/jdk/java/lang/StableValue/JEP.md @@ -248,7 +248,7 @@ by a lazily computed stable value. Analogous to how a `Supplier` can be cached using a backing stable value, a similar pattern can be used for an `IntFunction` that will record its cached values in a backing array of stable value elements. An example of this is an array that holds HTML pages that correspond to an error code in -the range [0, 7] where each element is pulled in from the file system on-demand: +the range [0, 7] where each element is pulled in from the file system on-demand: ``` class ErrorMessages { From 3532f7a2a0de8d8c15bc1b0a2722203379d08c79 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 29 Jul 2024 12:46:08 +0200 Subject: [PATCH 076/327] Improve code comments in JEP --- test/jdk/java/lang/StableValue/JEP.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/jdk/java/lang/StableValue/JEP.md b/test/jdk/java/lang/StableValue/JEP.md index 71ce91fbd44b2..926b89ab7e7aa 100644 --- a/test/jdk/java/lang/StableValue/JEP.md +++ b/test/jdk/java/lang/StableValue/JEP.md @@ -171,6 +171,8 @@ class Foo { if (!LOGGER.isSet()) { // 2. Set the stable value _after_ the field was declared + // If another thread has already set a value, this is a + // no-op and we will continue to 3. and get that value. LOGGER.trySet(Logger.getLogger("com.company.Foo")); } From ceede1e11f7d470fcd07cbe84e16c2a479713531 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 29 Jul 2024 12:47:19 +0200 Subject: [PATCH 077/327] Remove redundant spaces --- test/jdk/java/lang/StableValue/CachingIntFunctionTest.java | 2 +- test/jdk/java/lang/StableValue/LazyMapTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/jdk/java/lang/StableValue/CachingIntFunctionTest.java b/test/jdk/java/lang/StableValue/CachingIntFunctionTest.java index 7b7806aad30da..6315f04cc35e7 100644 --- a/test/jdk/java/lang/StableValue/CachingIntFunctionTest.java +++ b/test/jdk/java/lang/StableValue/CachingIntFunctionTest.java @@ -23,7 +23,7 @@ /* @test * @summary Basic tests for CachingIntFunction methods - * @compile --enable-preview -source ${jdk.version} CachingIntFunctionTest.java + * @compile --enable-preview -source ${jdk.version} CachingIntFunctionTest.java * @run junit/othervm --enable-preview CachingIntFunctionTest */ diff --git a/test/jdk/java/lang/StableValue/LazyMapTest.java b/test/jdk/java/lang/StableValue/LazyMapTest.java index 565b99699f428..0dab9f009919f 100644 --- a/test/jdk/java/lang/StableValue/LazyMapTest.java +++ b/test/jdk/java/lang/StableValue/LazyMapTest.java @@ -23,7 +23,7 @@ /* @test * @summary Basic tests for LazyMap methods - * @compile --enable-preview -source ${jdk.version} LazyMapTest.java + * @compile --enable-preview -source ${jdk.version} LazyMapTest.java * @run junit/othervm --enable-preview LazyMapTest */ From e97139ef29f689903d91f577533d3131795ab90f Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 29 Jul 2024 12:50:44 +0200 Subject: [PATCH 078/327] Clarify fence operations in JEP --- test/jdk/java/lang/StableValue/JEP.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/jdk/java/lang/StableValue/JEP.md b/test/jdk/java/lang/StableValue/JEP.md index 926b89ab7e7aa..753ea214c3374 100644 --- a/test/jdk/java/lang/StableValue/JEP.md +++ b/test/jdk/java/lang/StableValue/JEP.md @@ -192,8 +192,8 @@ Null-averse applications can also use `StableValue>`. When retrieving values, `StableValue` instances holding reference values can be faster than reference values managed via double-checked-idiom constructs as stable values rely -on explicit memory barriers rather than performing volatile access on each retrieval -operation. +on explicit memory barriers needed only during the single store operation rather than performing +volatile access on each retrieval operation. In addition, stable values are eligible for constant folding optimizations by the JVM. In many ways, this is similar to the holder-class idiom in the sense it offers the same From 9990741bee3781c70ccc13d0bf7d08bef16dceb2 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 29 Jul 2024 14:45:28 +0200 Subject: [PATCH 079/327] Update JEP --- test/jdk/java/lang/StableValue/JEP.md | 45 +++++++++------------ test/jdk/java/lang/StableValue/JepTest.java | 22 ++++++++++ 2 files changed, 41 insertions(+), 26 deletions(-) diff --git a/test/jdk/java/lang/StableValue/JEP.md b/test/jdk/java/lang/StableValue/JEP.md index 753ea214c3374..3c3c38ebe7281 100644 --- a/test/jdk/java/lang/StableValue/JEP.md +++ b/test/jdk/java/lang/StableValue/JEP.md @@ -43,7 +43,7 @@ performed when the data is actually needed, rather than unconditionally initiali first referenced: ``` -// ordinary static initialization +// Ordinary static initialization private static final Logger LOGGER = Logger.getLogger("com.company.Foo"); ... LOGGER.log(Level.DEBUG, ...); @@ -249,46 +249,39 @@ by a lazily computed stable value. Analogous to how a `Supplier` can be cached using a backing stable value, a similar pattern can be used for an `IntFunction` that will record its cached values in a backing array of -stable value elements. An example of this is an array that holds HTML pages that correspond to an error code in -the range [0, 7] where each element is pulled in from the file system on-demand: +stable value elements. Here is an example where we manually map logger numbers +(0 -> "com.company.Foo" , 1 -> "com.company.Bar") to loggers: ``` -class ErrorMessages { - - private static final int SIZE = 8; +class CachedNum { // 1. Centrally declare a cached IntFunction backed by a list of StableValue elements - private static final IntFunction ERROR_FUNCTION = - StableValue.newCachingIntFunction(SIZE, ErrorMessages::readFromFile, null); + private static final IntFunction LOGGERS = + StableValue.newCachingIntFunction(2, CachedNum::fromNumber, null); // 2. Define a function that is to be called the first // time a particular message number is referenced - private static String readFromFile(int messageNumber) { - try { - return Files.readString(Path.of("message-" + messageNumber + ".html")); - } catch (IOException e) { - throw new UncheckedIOException(e); - } + // The given loggerNumber is manually mapped to loggers + private static Logger fromNumber(int loggerNumber) { + return switch (loggerNumber) { + case 0 -> Logger.getLogger("com.company.Foo"); + case 1 -> Logger.getLogger("com.company.Bar"); + default -> throw new IllegalArgumentException(); + }; } // 3. Access the cached element with as-declared-final performance // (evaluation made before the first access) - String msg = ERROR_FUNCTION.apply(2); + Logger logger = LOGGERS.apply(0); } - -// -// -// -// Payment was denied: Insufficient funds. -// ``` Note: Again, the last null parameter signifies an optional thread factory that will be explained at the end of this chapter. -Finally, the most general cached function variant provided is a cached `Function` which, for example, -can make sure `Logger::getLogger` in one of the first examples above is invoked at most once -per input value (provided it executes successfully) in a multi-threaded environment. Such a cached -`Function` is almost always faster and more resource efficient than a `ConcurrentHashMap`. +As can be seen, manually mapping numbers to strings is a bit tedious. This brings us to the most general cached function +variant provided is a cached `Function` which, for example, can make sure `Logger::getLogger` in one of the first examples +above is invoked at most once per input value (provided it executes successfully) in a multi-threaded environment. Such a +cached `Function` is almost always faster and more resource efficient than a `ConcurrentHashMap`. Here is what a caching `Function` lazily holding two loggers could look like: @@ -370,7 +363,7 @@ method while additionally providing the `size` of the desired `List` and a mappe List lazyList = StableValue.lazyList(size, mapper); ``` -Note how there's only one field of type `List` to initialize even though every +Note how there's only one variable of type `List` to initialize even though every computation is performed independently of the other element of the list when accessed (i.e. no blocking will occur across threads computing distinct elements simultaneously). Also, the `IntSupplier` mapper provided at creation is only invoked at most once for each distinct input diff --git a/test/jdk/java/lang/StableValue/JepTest.java b/test/jdk/java/lang/StableValue/JepTest.java index a040f536ee5aa..5892bfdffa72a 100644 --- a/test/jdk/java/lang/StableValue/JepTest.java +++ b/test/jdk/java/lang/StableValue/JepTest.java @@ -84,6 +84,27 @@ static Logger logger(String name) { } } + static + class CachedNum { + // 1. Centrally declare a cached IntFunction backed by a list of StableValue elements + private static final IntFunction LOGGERS = + StableValue.newCachingIntFunction(2, CachedNum::fromNumber, null); + + // 2. Define a function that is to be called the first + // time a particular message number is referenced + // The given loggerNumber is manually mapped to loggers + private static Logger fromNumber(int loggerNumber) { + return switch (loggerNumber) { + case 0 -> Logger.getLogger("com.company.Foo"); + case 1 -> Logger.getLogger("com.company.Bar"); + default -> throw new IllegalArgumentException(); + }; + } + + // 3. Access the cached element with as-declared-final performance + // (evaluation made before the first access) + Logger logger = LOGGERS.apply(0); + } class Cached { @@ -141,4 +162,5 @@ private static String readFromFile(int messageNumber) { String msg = ERROR_FUNCTION.apply(2); } } + } From da47d8cded59d62f455ee73453dc28b7347f286c Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 29 Jul 2024 17:39:14 +0200 Subject: [PATCH 080/327] Add benchmarks and rework CachedSupplier and CachedIntFunction --- .../internal/lang/stable/CachedFunction.java | 10 ++ .../lang/stable/CachedIntFunction.java | 53 +++++++-- .../internal/lang/stable/CachedSupplier.java | 44 +++++-- .../internal/lang/stable/StableValueUtil.java | 5 + .../StableValue/CachingIntFunctionTest.java | 5 +- .../lang/StableValue/CachingSupplierTest.java | 5 +- .../lang/stable/CachedFunctionBenchmark.java | 108 ++++++++++++++++++ .../stable/CachedIntFunctionBenchmark.java | 103 +++++++++++++++++ .../lang/stable/CachedSupplierBenchmark.java | 95 +++++++++++++++ .../lang/stable/StableValueBenchmark.java | 14 +-- 10 files changed, 413 insertions(+), 29 deletions(-) create mode 100644 test/micro/org/openjdk/bench/java/lang/stable/CachedFunctionBenchmark.java create mode 100644 test/micro/org/openjdk/bench/java/lang/stable/CachedIntFunctionBenchmark.java create mode 100644 test/micro/org/openjdk/bench/java/lang/stable/CachedSupplierBenchmark.java diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/CachedFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/CachedFunction.java index e174803c645c6..5ceefff84faa8 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/CachedFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/CachedFunction.java @@ -58,6 +58,16 @@ public R apply(T value) { return r; } + @Override + public int hashCode() { + return System.identityHashCode(this); + } + + @Override + public boolean equals(Object obj) { + return obj == this; + } + public static CachedFunction of(Set inputs, Function original) { return new CachedFunction<>(StableValueUtil.ofMap(inputs), original); diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/CachedIntFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/CachedIntFunction.java index e090393add028..5fe15303e47a5 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/CachedIntFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/CachedIntFunction.java @@ -26,42 +26,75 @@ package jdk.internal.lang.stable; import jdk.internal.vm.annotation.ForceInline; +import jdk.internal.vm.annotation.Stable; -import java.util.List; +import java.util.Arrays; import java.util.function.IntFunction; +import java.util.stream.Collectors; // Note: It would be possible to just use `LazyList::get` instead of this // class but explicitly providing a class like this provides better // debug capability, exception handling, and may provide better performance. -public record CachedIntFunction(List> stables, - IntFunction original) implements IntFunction { +public final class CachedIntFunction implements IntFunction { + + private static final long VALUES_OFFSET = + StableValueUtil.UNSAFE.objectFieldOffset(CachedIntFunction.class, "values"); + + private final IntFunction original; + private final Object[] mutexes; + @Stable + private final Object[] values; + + public CachedIntFunction(int size, + IntFunction original) { + this.original = original; + this.mutexes = new Object[size]; + for (int i = 0; i < size; i++) { + mutexes[i] = new Object(); + } + this.values = new Object[size]; + } + + @SuppressWarnings("unchecked") @ForceInline @Override public R apply(int value) { - final StableValueImpl stable; + R r; try { // Todo: Will the exception handling here impair performance? - stable = stables.get(value); + r = (R) values[value]; } catch (IndexOutOfBoundsException e) { throw new IllegalArgumentException(e); } - R r = stable.value(); if (r != null) { return StableValueUtil.unwrap(r); } - synchronized (stable) { - r = stable.value(); + synchronized (mutexes[value]) { + r = (R) values[value]; if (r != null) { return StableValueUtil.unwrap(r); } r = original.apply(value); - stable.setOrThrow(r); + StableValueUtil.safelyPublish(values, StableValueUtil.arrayOffset(value), r); } return r; } public static CachedIntFunction of(int size, IntFunction original) { - return new CachedIntFunction<>(StableValueUtil.ofList(size), original); + return new CachedIntFunction<>(size, original); + } + + @Override + public String toString() { + return "CachedIntFunction[values=" + + "[" + valuesAsString() + "]" + + ", original=" + original + ']'; + } + + private String valuesAsString() { + return Arrays.stream(values, 0, values.length) + .map(StableValueUtil::render) + .collect(Collectors.joining(", ")); } } diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/CachedSupplier.java b/src/java.base/share/classes/jdk/internal/lang/stable/CachedSupplier.java index 28800c684d63b..45b689ea0a1a5 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/CachedSupplier.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/CachedSupplier.java @@ -26,31 +26,59 @@ package jdk.internal.lang.stable; import jdk.internal.vm.annotation.ForceInline; +import jdk.internal.vm.annotation.Stable; +import java.util.Objects; import java.util.function.Supplier; -public record CachedSupplier(StableValueImpl stable, - Supplier original) implements Supplier { +/** + * Implementation of a cached supplier. + *

+ * For performance reasons (~10%), we are not delegating to a StableValue but are using + * the more primitive functions in StableValueUtil that are shared with StableValueImpl. + * + * @param + */ +public final class CachedSupplier implements Supplier { + + private static final long VALUE_OFFSET = + StableValueUtil.UNSAFE.objectFieldOffset(CachedSupplier.class, "value"); + + private final Supplier original; + private final Object mutex = new Object(); + @Stable + private T value; + + public CachedSupplier(Supplier original) { + this.original = original; + } + + @SuppressWarnings("unchecked") @ForceInline @Override public T get() { - T t = stable.value(); - if (t != null) { + T t = value; + if (value != null) { return StableValueUtil.unwrap(t); } - synchronized (stable) { - t = stable.value(); + synchronized (mutex) { + t = value; if (t != null) { return StableValueUtil.unwrap(t); } t = original.get(); - stable.setOrThrow(t); + StableValueUtil.safelyPublish(this, VALUE_OFFSET, t); } return t; } public static CachedSupplier of(Supplier original) { - return new CachedSupplier<>(StableValueImpl.newInstance(), original); + return new CachedSupplier<>(original); + } + + @Override + public String toString() { + return "CachedSupplier[value=" + StableValueUtil.render(StableValueUtil.UNSAFE.getReferenceVolatile(this, VALUE_OFFSET)) + ", original=" + original + "]"; } } diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableValueUtil.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableValueUtil.java index ff0864cc93204..8d70288cbb516 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableValueUtil.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableValueUtil.java @@ -63,6 +63,11 @@ static boolean safelyPublish(Object o, long offset, Object value) { return UNSAFE.compareAndSetReference(o, offset, null, wrap(value)); } + @ForceInline + static long arrayOffset(int index) { + return Unsafe.ARRAY_OBJECT_BASE_OFFSET + (long) index * Unsafe.ARRAY_OBJECT_INDEX_SCALE; + } + // Factories public static List> ofList(int size) { diff --git a/test/jdk/java/lang/StableValue/CachingIntFunctionTest.java b/test/jdk/java/lang/StableValue/CachingIntFunctionTest.java index 6315f04cc35e7..de893ca4e41f5 100644 --- a/test/jdk/java/lang/StableValue/CachingIntFunctionTest.java +++ b/test/jdk/java/lang/StableValue/CachingIntFunctionTest.java @@ -42,11 +42,12 @@ final class CachingIntFunctionTest { void basic() { StableTestUtil.CountingIntFunction cif = new StableTestUtil.CountingIntFunction<>(i -> i); var cached = StableValue.newCachingIntFunction(SIZE, cif, null); + assertEquals("CachedIntFunction[values=[.unset, .unset], original=" + cif + "]", cached.toString()); assertEquals(1, cached.apply(1)); assertEquals(1, cif.cnt()); assertEquals(1, cached.apply(1)); assertEquals(1, cif.cnt()); - assertEquals("CachedIntFunction[stables=[StableValue.unset, StableValue[1]], original=" + cif + "]", cached.toString()); + assertEquals("CachedIntFunction[values=[.unset, [1]], original=" + cif + "]", cached.toString()); assertThrows(IllegalArgumentException.class, () -> cached.apply(SIZE + 1)); } @@ -80,7 +81,7 @@ void exception() { assertEquals(1, cif.cnt()); assertThrows(UnsupportedOperationException.class, () -> cached.apply(1)); assertEquals(2, cif.cnt()); - assertEquals("CachedIntFunction[stables=[StableValue.unset, StableValue.unset], original=" + cif + "]", cached.toString()); + assertEquals("CachedIntFunction[values=[.unset, .unset], original=" + cif + "]", cached.toString()); } } diff --git a/test/jdk/java/lang/StableValue/CachingSupplierTest.java b/test/jdk/java/lang/StableValue/CachingSupplierTest.java index 0ec21ca8ad7ff..7a767d0695410 100644 --- a/test/jdk/java/lang/StableValue/CachingSupplierTest.java +++ b/test/jdk/java/lang/StableValue/CachingSupplierTest.java @@ -40,11 +40,12 @@ final class CachingSupplierTest { void basic() { StableTestUtil.CountingSupplier cs = new StableTestUtil.CountingSupplier<>(() -> 42); var cached = StableValue.newCachingSupplier(cs, null); + assertEquals("CachedSupplier[value=.unset, original=" + cs + "]", cached.toString()); assertEquals(42, cached.get()); assertEquals(1, cs.cnt()); assertEquals(42, cached.get()); assertEquals(1, cs.cnt()); - assertEquals("CachedSupplier[stable=StableValue[42], original=" + cs + "]", cached.toString()); + assertEquals("CachedSupplier[value=[42], original=" + cs + "]", cached.toString()); } @Test @@ -76,7 +77,7 @@ void exception() { assertEquals(1, cs.cnt()); assertThrows(UnsupportedOperationException.class, cached::get); assertEquals(2, cs.cnt()); - assertEquals("CachedSupplier[stable=StableValue.unset, original=" + cs + "]", cached.toString()); + assertEquals("CachedSupplier[value=.unset, original=" + cs + "]", cached.toString()); } } diff --git a/test/micro/org/openjdk/bench/java/lang/stable/CachedFunctionBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/CachedFunctionBenchmark.java new file mode 100644 index 0000000000000..15041cbc53451 --- /dev/null +++ b/test/micro/org/openjdk/bench/java/lang/stable/CachedFunctionBenchmark.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2024, 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 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.OperationsPerInvocation; +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.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; +import java.util.function.IntFunction; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +/** + * 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, jvmArgsAppend = { + "--enable-preview", + // Prevent the use of uncommon traps + "-XX:PerMethodTrapLimit=0"}) +@Threads(Threads.MAX) // Benchmark under contention +@OperationsPerInvocation(100) +public class CachedFunctionBenchmark { + + private static final int SIZE = 100; + private static final Set SET = IntStream.range(0, SIZE).boxed().collect(Collectors.toSet()); + + private static final Map STABLE = StableValue.lazyMap(SET, Function.identity()); + private static final Function FUNCTION = StableValue.newCachingFunction(SET, Function.identity(), null); + + private final Map stable = StableValue.lazyMap(SET, Function.identity()); + private final Function function = StableValue.newCachingFunction(SET, Function.identity(), null); + + @Benchmark + public int stable() { + int sum = 0; + for (int i = 0; i < SIZE; i++) { + sum += stable.get(i); + } + return sum; + } + + @Benchmark + public int function() { + int sum = 0; + for (int i = 0; i < SIZE; i++) { + sum += function.apply(i); + } + return sum; + } + + @Benchmark + public int staticStable() { + int sum = 0; + for (int i = 0; i < SIZE; i++) { + sum += STABLE.get(i); + } + return sum; + } + + @Benchmark + public int staticIntFunction() { + int sum = 0; + for (int i = 0; i < SIZE; i++) { + sum += FUNCTION.apply(i); + } + return sum; + } + +} diff --git a/test/micro/org/openjdk/bench/java/lang/stable/CachedIntFunctionBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/CachedIntFunctionBenchmark.java new file mode 100644 index 0000000000000..5096cbb982ea3 --- /dev/null +++ b/test/micro/org/openjdk/bench/java/lang/stable/CachedIntFunctionBenchmark.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2024, 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 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.OperationsPerInvocation; +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.List; +import java.util.concurrent.TimeUnit; +import java.util.function.IntFunction; + +/** + * 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, jvmArgsAppend = { + "--enable-preview", + // Prevent the use of uncommon traps + "-XX:PerMethodTrapLimit=0"}) +@Threads(Threads.MAX) // Benchmark under contention +@OperationsPerInvocation(100) +public class CachedIntFunctionBenchmark { + + private static final int SIZE = 100; + private static final IntFunction IDENTITY = i -> i; + + private static final List STABLE = StableValue.lazyList(SIZE, IDENTITY); + private static final IntFunction INT_FUNCTION = StableValue.newCachingIntFunction(SIZE, IDENTITY, null); + + private final List stable = StableValue.lazyList(SIZE, IDENTITY); + private final IntFunction intFunction = StableValue.newCachingIntFunction(SIZE, IDENTITY, null); + + @Benchmark + public int stable() { + int sum = 0; + for (int i = 0; i < SIZE; i++) { + sum += stable.get(i); + } + return sum; + } + + @Benchmark + public int intFunction() { + int sum = 0; + for (int i = 0; i < SIZE; i++) { + sum += intFunction.apply(i); + } + return sum; + } + + @Benchmark + public int staticStable() { + int sum = 0; + for (int i = 0; i < SIZE; i++) { + sum += STABLE.get(i); + } + return sum; + } + + @Benchmark + public int staticIntFunction() { + int sum = 0; + for (int i = 0; i < SIZE; i++) { + sum += INT_FUNCTION.apply(i); + } + return sum; + } + +} diff --git a/test/micro/org/openjdk/bench/java/lang/stable/CachedSupplierBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/CachedSupplierBenchmark.java new file mode 100644 index 0000000000000..64725910bf6d4 --- /dev/null +++ b/test/micro/org/openjdk/bench/java/lang/stable/CachedSupplierBenchmark.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2024, 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 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.OperationsPerInvocation; +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.Supplier; + +/** + * 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, jvmArgsAppend = { + "--enable-preview", + // Prevent the use of uncommon traps + "-XX:PerMethodTrapLimit=0"}) +@Threads(Threads.MAX) // Benchmark under contention +@OperationsPerInvocation(2) +public class CachedSupplierBenchmark { + + private static final int VALUE = 42; + private static final int VALUE2 = 23; + + private static final StableValue STABLE = init(StableValue.newInstance(), VALUE); + private static final StableValue STABLE2 = init(StableValue.newInstance(), VALUE2); + private static final Supplier SUPPLIER = StableValue.newCachingSupplier(() -> VALUE, null); + private static final Supplier SUPPLIER2 = StableValue.newCachingSupplier(() -> VALUE, null); + + private final StableValue stable = init(StableValue.newInstance(), VALUE); + private final StableValue stable2 = init(StableValue.newInstance(), VALUE2); + private final Supplier supplier = StableValue.newCachingSupplier(() -> VALUE, null); + private final Supplier supplier2 = StableValue.newCachingSupplier(() -> VALUE2, null); + + @Benchmark + public int stable() { + return stable.orElseThrow() + stable2.orElseThrow(); + } + + @Benchmark + public int supplier() { + return supplier.get() + supplier2.get(); + } + + @Benchmark + public int staticStable() { + return STABLE.orElseThrow() + STABLE2.orElseThrow(); + } + + @Benchmark + public int staticSupplier() { + return SUPPLIER.get() + SUPPLIER2.get(); + } + + private static StableValue init(StableValue m, Integer value) { + m.trySet(value); + return m; + } + +} diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java index f18f56f4293aa..52b27ebc4bd8a 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java @@ -48,6 +48,13 @@ public class StableValueBenchmark { private static final int VALUE = 42; private static final int VALUE2 = 23; + private static final StableValue STABLE = init(StableValue.newInstance(), VALUE); + private static final StableValue STABLE2 = init(StableValue.newInstance(), VALUE2); + private static final StableValue DCL = init(StableValue.newInstance(), VALUE); + private static final StableValue DCL2 = init(StableValue.newInstance(), VALUE2); + private static final AtomicReference ATOMIC = new AtomicReference<>(VALUE); + private static final AtomicReference ATOMIC2 = new AtomicReference<>(VALUE2); + private final StableValue stable = init(StableValue.newInstance(), VALUE); private final StableValue stable2 = init(StableValue.newInstance(), VALUE2); private final StableValue stableNull = StableValue.newInstance(); @@ -59,13 +66,6 @@ public class StableValueBenchmark { private final Supplier supplier = () -> VALUE; private final Supplier supplier2 = () -> VALUE2; - private static final StableValue STABLE = init(StableValue.newInstance(), VALUE); - private static final StableValue STABLE2 = init(StableValue.newInstance(), VALUE2); - private static final StableValue DCL = init(StableValue.newInstance(), VALUE); - private static final StableValue DCL2 = init(StableValue.newInstance(), VALUE2); - private static final AtomicReference ATOMIC = new AtomicReference<>(VALUE); - private static final AtomicReference ATOMIC2 = new AtomicReference<>(VALUE2); - @Setup public void setup() { From 58e65cf5e09121fe1c9e22e7cacee45c32d4c5bf Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 30 Jul 2024 15:03:57 +0200 Subject: [PATCH 081/327] Improve CachedFunction::toString --- .../internal/lang/stable/CachedFunction.java | 25 +++++++++++++++++-- .../lang/stable/CachedIntFunction.java | 11 +++++--- .../internal/lang/stable/CachedSupplier.java | 2 +- .../lang/StableValue/CachingFunctionTest.java | 12 ++++----- 4 files changed, 38 insertions(+), 12 deletions(-) diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/CachedFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/CachedFunction.java index 5ceefff84faa8..e1ba6b663036d 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/CachedFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/CachedFunction.java @@ -30,16 +30,17 @@ import java.util.Map; import java.util.Set; import java.util.function.Function; +import java.util.stream.Collectors; // Note: It would be possible to just use `LazyMap::get` with some additional logic // instead of this class but explicitly providing a class like this provides better // debug capability, exception handling, and may provide better performance. -public record CachedFunction(Map> stables, +public record CachedFunction(Map> values, Function original) implements Function { @ForceInline @Override public R apply(T value) { - final StableValueImpl stable = stables.get(value); + final StableValueImpl stable = values.get(value); if (stable == null) { throw new IllegalArgumentException("Input not allowed: " + value); } @@ -68,6 +69,26 @@ public boolean equals(Object obj) { return obj == this; } + @Override + public String toString() { + return "CachedFunction[values=" + renderValues() + ", original=" + original + "]"; + } + + private String renderValues() { + final StringBuilder sb = new StringBuilder(); + sb.append("{"); + for (var e:values.entrySet()) { + final Object value = e.getValue().value(); + if (value == this) { + sb.append("(self)"); + } else { + sb.append(e.getKey()).append('=').append(StableValueUtil.render(value)); + } + } + sb.append("}"); + return sb.toString(); + } + public static CachedFunction of(Set inputs, Function original) { return new CachedFunction<>(StableValueUtil.ofMap(inputs), original); diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/CachedIntFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/CachedIntFunction.java index 5fe15303e47a5..8a3eec0e650d8 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/CachedIntFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/CachedIntFunction.java @@ -35,11 +35,16 @@ // Note: It would be possible to just use `LazyList::get` instead of this // class but explicitly providing a class like this provides better // debug capability, exception handling, and may provide better performance. +/** + * Implementation of a cached IntFunction. + *

+ * For performance reasons (~10%), we are not delegating to a StableList but are using + * the more primitive functions in StableValueUtil that are shared with StableList/StableValueImpl. + * + * @param the return type + */ public final class CachedIntFunction implements IntFunction { - private static final long VALUES_OFFSET = - StableValueUtil.UNSAFE.objectFieldOffset(CachedIntFunction.class, "values"); - private final IntFunction original; private final Object[] mutexes; @Stable diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/CachedSupplier.java b/src/java.base/share/classes/jdk/internal/lang/stable/CachedSupplier.java index 45b689ea0a1a5..d58ce2387237b 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/CachedSupplier.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/CachedSupplier.java @@ -37,7 +37,7 @@ * For performance reasons (~10%), we are not delegating to a StableValue but are using * the more primitive functions in StableValueUtil that are shared with StableValueImpl. * - * @param + * @param the return type */ public final class CachedSupplier implements Supplier { diff --git a/test/jdk/java/lang/StableValue/CachingFunctionTest.java b/test/jdk/java/lang/StableValue/CachingFunctionTest.java index ab04d162c7ae3..4344bce1a833f 100644 --- a/test/jdk/java/lang/StableValue/CachingFunctionTest.java +++ b/test/jdk/java/lang/StableValue/CachingFunctionTest.java @@ -45,10 +45,10 @@ void basic() { assertEquals(1, cif.cnt()); assertEquals(42, cached.apply(42)); assertEquals(1, cif.cnt()); - assertTrue(cached.toString().startsWith("CachedFunction[stables={")); + assertTrue(cached.toString().startsWith("CachedFunction[values={")); // Key order is unspecified - assertTrue(cached.toString().contains("13=StableValue.unset")); - assertTrue(cached.toString().contains("42=StableValue[42]")); + assertTrue(cached.toString().contains("13=.unset")); + assertTrue(cached.toString().contains("42=[42]")); assertTrue(cached.toString().endsWith(", original=" + cif + "]")); var x = assertThrows(IllegalArgumentException.class, () -> cached.apply(-1)); assertTrue(x.getMessage().contains("-1")); @@ -84,10 +84,10 @@ void exception() { assertEquals(1, cif.cnt()); assertThrows(UnsupportedOperationException.class, () -> cached.apply(42)); assertEquals(2, cif.cnt()); - assertTrue(cached.toString().startsWith("CachedFunction[stables={")); + assertTrue(cached.toString().startsWith("CachedFunction[values={")); // Key order is unspecified - assertTrue(cached.toString().contains("13=StableValue.unset")); - assertTrue(cached.toString().contains("42=StableValue.unset")); + assertTrue(cached.toString().contains("13=.unset")); + assertTrue(cached.toString().contains("42=.unset")); assertTrue(cached.toString().endsWith(", original=" + cif + "]")); } From 3bddb4ca3717d4f4660a9905319281ba1eb62290 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 30 Jul 2024 15:11:11 +0200 Subject: [PATCH 082/327] Revert redundant changes --- src/hotspot/share/classfile/vmSymbols.hpp | 3 +-- .../share/classes/java/lang/reflect/AccessibleObject.java | 8 ++------ src/java.base/share/classes/java/lang/reflect/Field.java | 2 +- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/hotspot/share/classfile/vmSymbols.hpp b/src/hotspot/share/classfile/vmSymbols.hpp index ca55ef1f1fd47..3d12cb095c028 100644 --- a/src/hotspot/share/classfile/vmSymbols.hpp +++ b/src/hotspot/share/classfile/vmSymbols.hpp @@ -745,8 +745,7 @@ class SerializeClosure; template(dumpThreads_name, "dumpThreads") \ template(dumpThreadsToJson_name, "dumpThreadsToJson") \ \ - /* StableValues & Collections */ \ - template(java_lang_StableValue, "java/lang/StableValue") \ + /* Stable Values */ \ template(java_lang_StableValue_signature, "Ljava/lang/StableValue;") \ /*end*/ diff --git a/src/java.base/share/classes/java/lang/reflect/AccessibleObject.java b/src/java.base/share/classes/java/lang/reflect/AccessibleObject.java index b16bf05c2998b..d0b50047031c1 100644 --- a/src/java.base/share/classes/java/lang/reflect/AccessibleObject.java +++ b/src/java.base/share/classes/java/lang/reflect/AccessibleObject.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2023, 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 @@ -385,15 +385,11 @@ private void throwInaccessibleObjectException(Class caller, Class declarin msg += " " + pn + "\"" ; if (caller != null) msg += " to " + caller.getModule(); - throw newInaccessibleObjectException(msg); - } - - static InaccessibleObjectException newInaccessibleObjectException(String msg) { InaccessibleObjectException e = new InaccessibleObjectException(msg); if (printStackTraceWhenAccessFails()) { e.printStackTrace(System.err); } - return e; + throw e; } private boolean isSubclassOf(Class queryClass, Class ofClass) { diff --git a/src/java.base/share/classes/java/lang/reflect/Field.java b/src/java.base/share/classes/java/lang/reflect/Field.java index 928bdaeb65b21..3e9d02d55095c 100644 --- a/src/java.base/share/classes/java/lang/reflect/Field.java +++ b/src/java.base/share/classes/java/lang/reflect/Field.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2023, 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 From 544b5c44bbdd472bfef27cdcf7470d285bcc520a Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 30 Jul 2024 21:18:37 +0200 Subject: [PATCH 083/327] Update JEP --- test/jdk/java/lang/StableValue/JEP.md | 298 ++++++++++++++------ test/jdk/java/lang/StableValue/JepTest.java | 83 ++++++ 2 files changed, 292 insertions(+), 89 deletions(-) diff --git a/test/jdk/java/lang/StableValue/JEP.md b/test/jdk/java/lang/StableValue/JEP.md index 3c3c38ebe7281..95af0935f9ccb 100644 --- a/test/jdk/java/lang/StableValue/JEP.md +++ b/test/jdk/java/lang/StableValue/JEP.md @@ -1,9 +1,9 @@ -# Stable Values & Collections (Preview) +# Stable Values (Preview) ## Summary -Introduce a _Stable Values & Collections API_, which provides performant immutable value holders where elements -are initialized _at most once_. Stable Values & Collections offer the performance and safety benefits of +Introduce a _Stable Values API_, which provides performant immutable value holders where elements +are initialized _at most once_. Stable Values offer the performance and safety benefits of final fields, while offering greater flexibility as to the timing of initialization. This is a [preview API](https://openjdk.org/jeps/12). ## Goals @@ -11,7 +11,7 @@ final fields, while offering greater flexibility as to the timing of initializat - Provide an easy and intuitive API to describe value holders that can change at most once. - Decouple declaration from initialization without significant footprint or performance penalties. - Reduce the amount of static initializer and/or field initialization code. -- Uphold integrity and consistency, even in a multi-threaded environment. +- Uphold integrity and consistency, even in a multithreaded environment. ## Non-goals @@ -21,64 +21,80 @@ This might be the subject of a future JEP. ## Motivation -Most Java developers have heard the advice "prefer immutability" (Effective Java, Third Edition, Item 17, by -Joshua Bloch). Immutability confers many advantages, as immutable objects can be only in one state, and can -therefore be freely shared across multiple threads. - -Java's main tool for managing immutability is `final` fields (and more recently, `record` classes). Unfortunately, -`final` fields come with restrictions. Instance `final` fields must be set by the end of the constructor; `static -final` fields must be set during class initialization. Moreover, the order in which `final` fields are initialized -is determined by the [textual order](https://docs.oracle.com/javase/specs/jls/se7/html/jls-13.html#jls-12.4.1) in -which the fields are declared. This order is then made explicit in the resulting class file. - -As such, the initialization of a `final` field is fixed in time; it cannot be arbitrarily moved forward. In other -words, developers are forced to choose between finality and all its benefits, and flexibility over the timing of -initialization. Developers have devised several strategies to ameliorate this imbalance, but none are ideal. - -For instance, initialization of `static` and `final` fields can be broken up by leveraging the laziness already -built into class loading. Often referred to as the -[*class-holder idiom*](https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom), this technique moves -lazily initialized state into a helper class which is then loaded on-demand, so its initialization is only -performed when the data is actually needed, rather than unconditionally initializing constants when a class is -first referenced: +Java allows developers to control whether fields are mutable or not. Mutable fields can be updated multiple times, and +from any arbitrary position in the code. Conversely, immutable fields (i.e. `final` fields), can only be updated +_once_, and only in very specific places: the class initializer (for a static immutable field) or the class constructor +(for an instance immutable field). Unfortunately, in Java there is no way to define a field that can be updated _at most +once_ (i.e. fields that are either not updated at all or are updated exactly once) and from _any_ arbitrary position in +the code. + +| Field kind | #Updates | Code update location | +|--------------------|----------|-----------------------------------| +| Mutable | [0, ∞) | Anywhere | +| `final` | 1 | Constructor or static initializer | +| at-most-once (N/A) | [0, 1] | Anywhere | + +"At-most-once fields" would be essential to expensive cache computations associated with method calls, so that they can +be reused across multiple calls. For instance, creating a logger or reading application configurations from an external +database. Furthermore, if the VM is made aware, a field is an "at-most-once field" and it is set, it may +[constant-fold](https://en.wikipedia.org/wiki/Constant_folding) the field value, thereby providing crucial performance +and energy efficiency gains. It is also important to stress a method called to compute a value might have intended or +unintended side effects and therefore, it would be vital to also guarantee the method is invoked at most once, even in +a multithreaded environment. Another property of at-most-once fields would be, they are written to at most once but +are likely read at many occasions. Hence, updating the field is not so time-critical whereas every effort to make +reading the field performant should be made. + +Here is how a naïve cache could look like using a mutable field and where a `Logger` instance is cached: ``` -// Ordinary static initialization -private static final Logger LOGGER = Logger.getLogger("com.company.Foo"); -... -LOGGER.log(Level.DEBUG, ...); +// A naïve cache. Do not use this solution! +public class Cache { + + private Logger logger; + + public Logger logger() { + Logger v = logger; + if (v == null) { + logger = v = Logger.getLogger("com.company.Foo"); + } + return v; + } +} ``` -we can defer initialization until we actually need it, like so: +This solution does not work in a multithreaded environment as updates made by one thread to the `logger` may not be +seen by other threads, thereby allowing the `logger` variable to be updated several times and consequently the +`Logger::getLogger` method can be called several times. + +Here is how thread safety can be added together with a guarantee, `logger` is only updated at most once +(and accordingly `Logger::getLogger` is called at most once): ``` -// Initialization-on-demand holder idiom -Logger logger() { - class Holder { - static final Logger LOGGER = Logger.getLogger("com.company.Foo"); +// A field protected by synchonization. Do not use this solution! +public class Cache { + + private Logger logger; + + public synchronized Logger logger() { + Logger v = logger; + if (v == null) { + logger = v = Logger.getLogger("com.company.Foo"); + } + return v; } - return Holder.LOGGER; } -... -LOGGER.log(Level.DEBUG, ...); ``` -The code above ensures that the `Logger` object is created only when actually required. The (possibly expensive) -initializer for the logger lives in the nested `Holder` class, which will only be initialized when the `logger` -method accesses the `LOGGER` field. While this idiom works well, its reliance on the class loading process comes -with significant drawbacks. First, each constant whose computation needs to be deferred generally requires its own -holder class, thus introducing a significant static footprint cost. Second, this idiom is only really applicable -if the field initialization is suitably isolated, not relying on any other parts of the object state. +While this works, acquiring the `synhronized` monitor is slow and prevents multiple threads from accessing the cached +`Logger` instance simultaneously once computed. -Alternatively, the [*double-checked locking idiom*](https://en.wikipedia.org/wiki/Double-checked_locking), can be -used for deferring the evaluation of instance field initializers. The idea is to optimistically check if the -field's value is non-null and if so, use that value directly; but if the value observed is null, then the field -must be initialized, which, to be safe under multi-threaded access, requires acquiring a lock to ensure -correctness: +The solution above can be modified to use the +[*double-checked locking idiom*](https://en.wikipedia.org/wiki/Double-checked_locking) which would improve the +situation a bit: ``` -// Double-checked locking idiom -class Foo { +// A field protected by double-checked locking. Do not use this solution! +public class Cache { private volatile Logger logger; @@ -96,54 +112,146 @@ class Foo { } } ``` +While the solution above is an improvement over the previous one, the double-checked locking idiom is brittle and easy +to get subtly wrong (see *Java Concurrency in Practice*, 16.2.4, by Brian Goetz). For example, a common error is forgetting +to declare the field `volatile` resulting in the risk of observing incomplete objects. Another issue is that synchronization +is made on the `Cache` instance itself, potentially opening up for deadlock situations. Furthermore, every access to the +cached value is made using `volatile` semantics which may be slow on some platforms. -The double-checked locking idiom is brittle and easy to get subtly wrong (see *Java Concurrency in Practice*, -16.2.4, by Brian Goetz). For example, a common error is forgetting to declare the field `volatile` resulting in -the risk of observing incomplete objects. A more fundamental problem with the double-checked locking idiom is that -access to the `logger` field cannot be adequately optimized by just-in-time compilers, as they cannot reliably -assume that the field value will, in fact, change only once. - -Internal JDK classes can address some of the shortcomings of the approaches described above by using the internal -`jdk.internal.vm.annotation.@Stable` annotation. This annotation is used to mark scalar and array fields whose -values or elements will change *at most once*, thereby providing crucial performance, energy efficiency, and -flexibility benefits. With the help of `@Stable` the above example can be rewritten as follows: +Now, further imagine a situation where there are several loggers to be cached and where we want to reference the cached +values using an `int` index (i.e. 0 -> "com.company.Foo0", 1 -> "com.company.Foo1", etc.): ``` -// Double-checked locking idiom (JDK internal use only) -class Foo { +// A an array of values protected by double-checked locking. Do not use this solution! +public class Cache { - @Stable - private Logger logger; + private static final VarHandle ARRAY_HANDLE = MethodHandles.arrayElementVarHandle(Logger[].class); + private static final int SIZE = 10; + private final Logger[] loggers = new Logger[SIZE]; - public Logger logger() { - if (logger == null) { - logger = Logger.getLogger("com.company.Foo"); + public Logger logger(int i) { + Logger v = (Logger) ARRAY_HANDLE.getVolatile(loggers, i); + if (v == null) { + synchronized (this) { + v = loggers[i]; + if (v == null) { + ARRAY_HANDLE.setVolatile(loggers, i, v = Logger.getLogger("com.company.Foo" + i)); + } + } } - return logger; + return v; } } ``` -Note how the `logger` field no longer needs to be marked as `volatile`. And, since the JVM knows that `logger` can -change at most once, subsequent accesses to the `logger` field can be -[constant-folded](https://en.wikipedia.org/wiki/Constant_folding) away. Alas, the powerful `@Stable` is -fundamentally _unsafe_: as the attentive reader might have noticed, the above code is correct only as long as -`getLogger` returns, for any given string argument passed to it, a `Logger` object with the *same identity*. -This is crucial to ensure that the `logger` field is mutated only once: spurious racy update attempts to `logger` can be safely ignored, only if they don't change the value stored in that field. +There is no built-in semantics for declaring an array's _elements_ should be accessed using 'volatile' semantics in +Java and so, volatile access has to be made explicit in the user code, for example via the supported API of +`VarHandle`. This solution is also plagued with the problem that it synchronizes on `this` and, in addition to exposing +itself for deadlocks, prevents any of the cached values from being computed simultaneously by distinct threads. + +While some of the problems described above might be solved (e.g. by synchronizing on internal mutex fields) they +become increasingly complex, error-prone, and hard to maintain. + +A more fundamental problem with all the solutions above is that access to the cached values cannot be adequately +optimized by just-in-time compilers, as they cannot reliably assume that field values will, in fact, change at most +once. Alas, the solutions do not express the intent of the programmer; neither to just-in-time compilers nor to +readers of the code. What we are missing -- in all cases -- is a *safe* way to *promise* that a constant will be initialized by the time it is used, with a value that is computed at most once. Such a mechanism would give the Java runtime maximum opportunity to stage and optimize its computation, thus avoiding the penalties (static footprint, loss of runtime -optimizations) that plague the workarounds shown above, as well as the unsafety associated with the `@Stable` -annotation. Moreover, such a mechanism should gracefully scale to handle collections of constant values, while -retaining efficient computer resource management. +optimizations, and brittleness) that plague the workarounds shown above and below. Moreover, such a mechanism +should gracefully scale to handle collections of constant values, while retaining efficient computer resource +management. +It would be advantageous if compute-at-most-once constructs could be expresses something along these lines: + +``` +// Declare a cache that can hold a single Logger instance +Abc cache = ... Logger.getLogger("com.company.Foo") ...; +... +// If the value is set, just return it. Otherwise computes the value. +// If another thread is computing a value, wait until it has completed. +Logger logger = cache.xxxx(); +``` + +and for several compute-at-most-once values indexed by an `int`: + +``` +// Declare a cache that can hold 10 Logger instance +Efg cache = ... 10 ... i -> Logger.getLogger("com.company.Foo" + i) ... +... +// If a value at the provided index is set, just return it. Otherwise compute the value. +// If another thread is computing a value at the provided index, wait until it has completed. +// Values can be computed in parallel by distinct threads. +Logger logger = cache.xxxx(7); +``` + +and even for several compute-at-most-once values associated with some key of arbitrary type `K` where we use Strings +as the key type in the example below: + +``` +// Declare a cache that can hold a finite number of Logger instance +// associated with the same finite number of `keys` Strings. +Hij cache = ... keys ... Logger::getLogger... +... +// If a value associated with the provided string is set, just return it. Otherwise comput it. +// If another thread is computing a value for the provided String, wait until it has completed. +// Values can be computed in parallel by distinct threads. +Logger logger = cache.xxxx("com.company.Foo"); +``` + +For interoperability with legacy code, it would also be desirable if some of the standard collection types could be +expressed as compute-at-most-once constructs in a similar fashion: + +``` +// Declare a List whose elements are lazily computed once accessed via List::get +List loggerList = ... 10 ... i -> Logger.getLogger("com.company.Foo" + i) ... + +// Declare a Map whose values are lazily computed once accessed via Map::get +Map loggerMap = ... keys ... Logger::getLogger... +``` + +A final note should be made about static fields. Initialization of `static` and `final` fields can be broken up by +leveraging the laziness already built into class loading. Often referred to as the +[*initialization-on-demand_holder_idiom*](https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom), this +technique moves lazily initialized state into a helper class which is then loaded on-demand, so its initialization is +only performed when the data is actually needed, rather than unconditionally initializing constants when a class is +first referenced: + +``` +// Ordinary static initialization +private static final Logger LOGGER = Logger.getLogger("com.company.Foo"); +... +LOGGER.log(Level.DEBUG, ...); +``` + +we can defer initialization until we actually need it, like so: + +``` +// Initialization-on-demand holder idiom +Logger logger() { + class Holder { + static final Logger LOGGER = Logger.getLogger("com.company.Foo"); + } + return Holder.LOGGER; +} +... +logger().log(Level.DEBUG, ...); +``` + +The code above ensures that the `Logger` object is created only when actually required. The (possibly expensive) +initializer for the logger lives in the nested `Holder` class, which will only be initialized when the `logger` +method accesses the `LOGGER` field. While this idiom works well, its reliance on the class loading process comes +with significant drawbacks. First, each constant whose computation needs to be deferred generally requires its own +holder class, thus introducing a significant static footprint cost. Second, this idiom is only really applicable +if the field initialization is suitably isolated, not relying on any other parts of the object state. ## Description -The Stable Values & Collections API defines an interface so that client code in libraries and applications can +The Stable Values API defines an interface so that client code in libraries and applications can -- Define and use stable (scalar) values: +- Define and use a stable value: - [`StableValue.newInstance()`](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/java/lang/StableValue.html#newInstance()) - Define various _cached_ functions: - [`StableValue.newCachedSupplier()`](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/java/lang/StableValue.html#newCachingSupplier(java.util.function.Supplier,java.util.concurrent.ThreadFactory)) @@ -153,13 +261,13 @@ The Stable Values & Collections API defines an interface so that client code in - [`StableValue.lazyList(int size)`](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/java/lang/StableValue.html#lazyList(int,java.util.function.IntFunction)) - [`StableValue.lazyMap(Set keys)`](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/java/lang/StableValue.html#lazyMap(java.util.Set,java.util.function.Function)) -The Stable Values & Collections API resides in the [java.lang](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/java/lang/package-summary.html) package of the [java.base](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/module-summary.html) module. +The Stable Values API resides in the [java.lang](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/java/lang/package-summary.html) package of the [java.base](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/module-summary.html) module. ### Stable values A _stable value_ is a thin, atomic, non-blocking, thread-safe, set-at-most-once, stable value holder eligible for certain JVM optimizations if set to a value. It is expressed as an object of type -`jdk.lang.StableValue`, which, like `Future`, is a holder for some computation that may or may not have +`java.lang.StableValue`, which, like `Future`, is a holder for some computation that may or may not have occurred yet. Fresh (unset) `StableValue` instances are created via the factory method `StableValue::newInstance`: ``` @@ -179,6 +287,8 @@ class Foo { // 3. Access the stable value with as-declared-final performance return LOGGER.orElseThrow(); } + ... + logger().log(Level.DEBUG, ...); } ``` @@ -217,9 +327,9 @@ particular input value is computed only once and is remembered such that remembe reused for subsequent calls with recurring input values. In cases where the invoke-at-most-once property of a `Supplier` is important, the -Stable Values & Collections API offers a _cached supplier_ which is a caching, thread-safe, stable, +Stable Values API offers a _cached supplier_ which is a caching, thread-safe, stable, lazily computed `Supplier` that records the value of an _original_ `Supplier` upon being first -accessed via its `Supplier::get` method. In a multi-threaded scenario, competing threads will block +accessed via its `Supplier::get` method. In a multithreaded scenario, competing threads will block until the first thread has computed a cached value. Unsurprisingly, the cached value is stored internally in a stable value. @@ -237,6 +347,8 @@ class Foo { // (single evaluation made before the first access) return LOGGER.get(); } + ... + logger().log(Level.DEBUG, ...); } ``` @@ -273,6 +385,8 @@ class CachedNum { // 3. Access the cached element with as-declared-final performance // (evaluation made before the first access) Logger logger = LOGGERS.apply(0); + ... + logger.log(Level.DEBUG, ...); } ``` @@ -280,7 +394,7 @@ Note: Again, the last null parameter signifies an optional thread factory that w As can be seen, manually mapping numbers to strings is a bit tedious. This brings us to the most general cached function variant provided is a cached `Function` which, for example, can make sure `Logger::getLogger` in one of the first examples -above is invoked at most once per input value (provided it executes successfully) in a multi-threaded environment. Such a +above is invoked at most once per input value (provided it executes successfully) in a multithreaded environment. Such a cached `Function` is almost always faster and more resource efficient than a `ConcurrentHashMap`. Here is what a caching `Function` lazily holding two loggers could look like: @@ -298,6 +412,8 @@ class Cahced { // 2. Access the cached value via the function with as-declared-final // performance (evaluation made before the first access) Logger logger = LOGGERS.apply(NAME); + ... + logger.log(Level.DEBUG, ...); } ``` @@ -315,7 +431,7 @@ to write such constructs in a few lines. #### Background threads -As noted above, the cached-returning factories in the Stable Values & Collections API offers an optional +As noted above, the cached-returning factories in the Stable Values API offers an optional tailing thread factory parameter from which new value-computing background threads will be created: ``` @@ -334,6 +450,8 @@ private static final String NAME = "com.company.Foo"; // 2. Access the cached value via the function with as-declared-final // performance (evaluation made before the first access) Logger logger = LOGGERS.apply(NAME); +... +logger.log(Level.DEBUG, ...); ``` This can provide a best-of-several-worlds situation where the cached function can be quickly defined (as no @@ -346,7 +464,7 @@ the background threads have had a head start compared to the accessing threads. ### Stable collections -The Stable Values & Collections API also provides factories that allow the creation of new +The Stable Values API also provides factories that allow the creation of new collection variants that are lazy, shallowly immutable, and stable: - `List` of stable elements @@ -367,8 +485,8 @@ Note how there's only one variable of type `List` to initialize even though e computation is performed independently of the other element of the list when accessed (i.e. no blocking will occur across threads computing distinct elements simultaneously). Also, the `IntSupplier` mapper provided at creation is only invoked at most once for each distinct input -value. The Stable Values & Collections API allows modeling this cleanly, while still preserving -good constant-folding guarantees and integrity of updates in the case of multi-threaded access. +value. The Stable Values API allows modeling this cleanly, while still preserving +good constant-folding guarantees and integrity of updates in the case of multithreaded access. It should be noted that even though a lazily computed list of stable elements might mutate its internal state upon external access, it is _still shallowly immutable_ because _no first-level @@ -390,6 +508,8 @@ static final Map LOGGERS = static Logger logger(String name) { return LOGGERS.get(name); } +... +logger("com.company.Foo").log(Level.DEBUG, ...); ``` In the example above, only two input values were used. However, this concept allows declaring a @@ -407,8 +527,8 @@ existing libraries. For example, if provided as a method parameter. ### Preview feature -The Stable Values & Collections is a [preview API](https://openjdk.org/jeps/12), disabled by default. -To use the Stable Value & Collections APIs, the JVM flag `--enable-preview` must be passed in, as follows: +The Stable Values is a [preview API](https://openjdk.org/jeps/12), disabled by default. +To use the Stable Value APIs, the JVM flag `--enable-preview` must be passed in, as follows: - Compile the program with `javac --release 24 --enable-preview Main.java` and run it with `java --enable-preview Main`; or, diff --git a/test/jdk/java/lang/StableValue/JepTest.java b/test/jdk/java/lang/StableValue/JepTest.java index 5892bfdffa72a..7f4e1d91a8908 100644 --- a/test/jdk/java/lang/StableValue/JepTest.java +++ b/test/jdk/java/lang/StableValue/JepTest.java @@ -29,6 +29,8 @@ import java.io.IOException; import java.io.UncheckedIOException; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; import java.nio.file.Files; import java.nio.file.Path; import java.util.Map; @@ -40,6 +42,87 @@ final class JepTest { + class Progression0 { + + static final + public class Cache { + + private Logger logger; + + public Logger logger() { + Logger v = logger; + if (v == null) { + logger = v = Logger.getLogger("com.company.Foo"); + } + return v; + } + } + } + + class Progression1 { + + static final + public class Cache { + + private Logger logger; + + public synchronized Logger logger() { + Logger v = logger; + if (v == null) { + logger = v = Logger.getLogger("com.company.Foo"); + } + return v; + } + } + } + + class Progression2 { + + static final + public class Cache { + + private volatile Logger logger; + + public Logger logger() { + Logger v = logger; + if (v == null) { + synchronized (this) { + v = logger; + if (v == null) { + logger = v = Logger.getLogger("com.company.Foo"); + } + } + } + return v; + } + } + } + + class Progression3 { + + static final + public class Cache { + + private static final VarHandle ARRAY_HANDLE = MethodHandles.arrayElementVarHandle(Logger[].class); + private static final int SIZE = 10; + private final Logger[] loggers = new Logger[SIZE]; + + public Logger logger(int i) { + Logger v = (Logger) ARRAY_HANDLE.getVolatile(loggers, i); + if (v == null) { + synchronized (this) { + v = loggers[i]; + if (v == null) { + ARRAY_HANDLE.setVolatile(loggers, i, v = Logger.getLogger("com.company.Foo" + i)); + } + } + } + return v; + } + } + } + + class Foo { // 1. Declare a Stable field private static final StableValue LOGGER = StableValue.newInstance(); From 7f155b35eee76a5c2a37c5286b48f9434b6b138a Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 30 Jul 2024 21:41:17 +0200 Subject: [PATCH 084/327] Minor updates to the JEP --- test/jdk/java/lang/StableValue/JEP.md | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/test/jdk/java/lang/StableValue/JEP.md b/test/jdk/java/lang/StableValue/JEP.md index 95af0935f9ccb..178ef73841c8a 100644 --- a/test/jdk/java/lang/StableValue/JEP.md +++ b/test/jdk/java/lang/StableValue/JEP.md @@ -38,9 +38,13 @@ the code. be reused across multiple calls. For instance, creating a logger or reading application configurations from an external database. Furthermore, if the VM is made aware, a field is an "at-most-once field" and it is set, it may [constant-fold](https://en.wikipedia.org/wiki/Constant_folding) the field value, thereby providing crucial performance -and energy efficiency gains. It is also important to stress a method called to compute a value might have intended or +and energy efficiency gains. + +It is also important to stress a method called to compute a value might have intended or unintended side effects and therefore, it would be vital to also guarantee the method is invoked at most once, even in -a multithreaded environment. Another property of at-most-once fields would be, they are written to at most once but +a multithreaded environment. + +Another property of at-most-once fields would be, they are written to at most once but are likely read at many occasions. Hence, updating the field is not so time-critical whereas every effort to make reading the field performant should be made. @@ -144,7 +148,7 @@ public class Cache { } ``` -There is no built-in semantics for declaring an array's _elements_ should be accessed using 'volatile' semantics in +There is no built-in semantics for declaring an array's _elements_ should be accessed using `volatile` semantics in Java and so, volatile access has to be made explicit in the user code, for example via the supported API of `VarHandle`. This solution is also plagued with the problem that it synchronizes on `this` and, in addition to exposing itself for deadlocks, prevents any of the cached values from being computed simultaneously by distinct threads. @@ -168,9 +172,9 @@ It would be advantageous if compute-at-most-once constructs could be expresses s ``` // Declare a cache that can hold a single Logger instance -Abc cache = ... Logger.getLogger("com.company.Foo") ...; +Foo cache = ... Logger.getLogger("com.company.Foo") ...; ... -// If the value is set, just return it. Otherwise computes the value. +// Just returns the value if set. Otherwise computes and returns the value. // If another thread is computing a value, wait until it has completed. Logger logger = cache.xxxx(); ``` @@ -179,9 +183,9 @@ and for several compute-at-most-once values indexed by an `int`: ``` // Declare a cache that can hold 10 Logger instance -Efg cache = ... 10 ... i -> Logger.getLogger("com.company.Foo" + i) ... +Bar cache = ... 10 ... i -> Logger.getLogger("com.company.Foo" + i) ... ... -// If a value at the provided index is set, just return it. Otherwise compute the value. +// Just returns the value at the provided index if set. Otherwise computes and returns the value. // If another thread is computing a value at the provided index, wait until it has completed. // Values can be computed in parallel by distinct threads. Logger logger = cache.xxxx(7); @@ -193,9 +197,9 @@ as the key type in the example below: ``` // Declare a cache that can hold a finite number of Logger instance // associated with the same finite number of `keys` Strings. -Hij cache = ... keys ... Logger::getLogger... +Baz cache = ... keys ... Logger::getLogger... ... -// If a value associated with the provided string is set, just return it. Otherwise comput it. +// Just returns the value associated with the provided string if set. Otherwise computes and returns the value. // If another thread is computing a value for the provided String, wait until it has completed. // Values can be computed in parallel by distinct threads. Logger logger = cache.xxxx("com.company.Foo"); @@ -206,10 +210,10 @@ expressed as compute-at-most-once constructs in a similar fashion: ``` // Declare a List whose elements are lazily computed once accessed via List::get -List loggerList = ... 10 ... i -> Logger.getLogger("com.company.Foo" + i) ... +List lazyList = ... 10 ... i -> Logger.getLogger("com.company.Foo" + i) ... // Declare a Map whose values are lazily computed once accessed via Map::get -Map loggerMap = ... keys ... Logger::getLogger... +Map lazyMap = ... keys ... Logger::getLogger... ``` A final note should be made about static fields. Initialization of `static` and `final` fields can be broken up by From 208643d9cbad95fd5844024bfa85158fcf40329e Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 30 Jul 2024 21:44:43 +0200 Subject: [PATCH 085/327] Remove trailing spaces --- test/jdk/java/lang/StableValue/JEP.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/jdk/java/lang/StableValue/JEP.md b/test/jdk/java/lang/StableValue/JEP.md index 178ef73841c8a..f7fee5dea984a 100644 --- a/test/jdk/java/lang/StableValue/JEP.md +++ b/test/jdk/java/lang/StableValue/JEP.md @@ -42,7 +42,7 @@ and energy efficiency gains. It is also important to stress a method called to compute a value might have intended or unintended side effects and therefore, it would be vital to also guarantee the method is invoked at most once, even in -a multithreaded environment. +a multithreaded environment. Another property of at-most-once fields would be, they are written to at most once but are likely read at many occasions. Hence, updating the field is not so time-critical whereas every effort to make @@ -90,7 +90,7 @@ public class Cache { ``` While this works, acquiring the `synhronized` monitor is slow and prevents multiple threads from accessing the cached -`Logger` instance simultaneously once computed. +`Logger` instance simultaneously once computed. The solution above can be modified to use the [*double-checked locking idiom*](https://en.wikipedia.org/wiki/Double-checked_locking) which would improve the @@ -182,7 +182,7 @@ Logger logger = cache.xxxx(); and for several compute-at-most-once values indexed by an `int`: ``` -// Declare a cache that can hold 10 Logger instance +// Declare a cache that can hold 10 Logger instance Bar cache = ... 10 ... i -> Logger.getLogger("com.company.Foo" + i) ... ... // Just returns the value at the provided index if set. Otherwise computes and returns the value. From 2ab683dc43827d39f79427de7abe7aba3d6fc189 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 31 Jul 2024 08:32:28 +0200 Subject: [PATCH 086/327] Improve JEP --- test/jdk/java/lang/StableValue/JEP.md | 39 ++++++++++++++++----------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/test/jdk/java/lang/StableValue/JEP.md b/test/jdk/java/lang/StableValue/JEP.md index f7fee5dea984a..74d33b8fdc3f5 100644 --- a/test/jdk/java/lang/StableValue/JEP.md +++ b/test/jdk/java/lang/StableValue/JEP.md @@ -21,12 +21,14 @@ This might be the subject of a future JEP. ## Motivation -Java allows developers to control whether fields are mutable or not. Mutable fields can be updated multiple times, and -from any arbitrary position in the code. Conversely, immutable fields (i.e. `final` fields), can only be updated -_once_, and only in very specific places: the class initializer (for a static immutable field) or the class constructor -(for an instance immutable field). Unfortunately, in Java there is no way to define a field that can be updated _at most -once_ (i.e. fields that are either not updated at all or are updated exactly once) and from _any_ arbitrary position in -the code. +Java allows developers to control whether fields are mutable or not. + +* Mutable fields can be updated multiple times, and from any arbitrary position in the code. +* Immutable fields (i.e. `final` fields), can only be updated _once_, and only in very specific places: the + class initializer (for a static immutable field) or the class constructor(for an instance immutable field). + +Unfortunately, in Java there is no way to define a field that can be updated _at most once_ (i.e. fields that are +either not updated at all or are updated exactly once) and from _any_ arbitrary position in the code: | Field kind | #Updates | Code update location | |--------------------|----------|-----------------------------------| @@ -34,21 +36,26 @@ the code. | `final` | 1 | Constructor or static initializer | | at-most-once (N/A) | [0, 1] | Anywhere | +_Table 1, showing properties of mutable, immutable, and at-most-once (currently not available) fields._ + "At-most-once fields" would be essential to expensive cache computations associated with method calls, so that they can be reused across multiple calls. For instance, creating a logger or reading application configurations from an external database. Furthermore, if the VM is made aware, a field is an "at-most-once field" and it is set, it may [constant-fold](https://en.wikipedia.org/wiki/Constant_folding) the field value, thereby providing crucial performance and energy efficiency gains. -It is also important to stress a method called to compute a value might have intended or +It is also important to stress a method called to compute an "at-most-once field" might have intended or unintended side effects and therefore, it would be vital to also guarantee the method is invoked at most once, even in a multithreaded environment. -Another property of at-most-once fields would be, they are written to at most once but -are likely read at many occasions. Hence, updating the field is not so time-critical whereas every effort to make -reading the field performant should be made. +Another property of "at-most-once fields" would be, they are written to at most once but +are likely read at many occasions. Hence, updating the field would not be so time-critical whereas every effort +should be made to make reading the field performant. + +Using existing Java semantics, it is possible to devise solutions that _partially_ emulates an "at-most-once field". In +the solutions exemplified below, an "at-most-once field" of type `Logger` is used in a cache. -Here is how a naïve cache could look like using a mutable field and where a `Logger` instance is cached: +Here is how a naïve `Logger` cache backed by a mutable field could look like: ``` // A naïve cache. Do not use this solution! @@ -67,11 +74,11 @@ public class Cache { ``` This solution does not work in a multithreaded environment as updates made by one thread to the `logger` may not be -seen by other threads, thereby allowing the `logger` variable to be updated several times and consequently the +visible to other threads, thereby allowing the `logger` variable to be updated several times and consequently the `Logger::getLogger` method can be called several times. Here is how thread safety can be added together with a guarantee, `logger` is only updated at most once -(and accordingly `Logger::getLogger` is called at most once): +(and thereby `Logger::getLogger` is called at most once): ``` // A field protected by synchonization. Do not use this solution! @@ -168,7 +175,7 @@ optimizations, and brittleness) that plague the workarounds shown above and belo should gracefully scale to handle collections of constant values, while retaining efficient computer resource management. -It would be advantageous if compute-at-most-once constructs could be expresses something along these lines: +It would be advantageous if "compute-at-most-once fields" could be expresses something along these lines: ``` // Declare a cache that can hold a single Logger instance @@ -191,8 +198,8 @@ Bar cache = ... 10 ... i -> Logger.getLogger("com.company.Foo" + i) ... Logger logger = cache.xxxx(7); ``` -and even for several compute-at-most-once values associated with some key of arbitrary type `K` where we use Strings -as the key type in the example below: +and even for several compute-at-most-once values associated with some key of arbitrary type `K` (where we use Strings +as the key type in the example below): ``` // Declare a cache that can hold a finite number of Logger instance From c646cbd82221886b28cfa810e2fae43ce2343d4c Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 31 Jul 2024 08:40:54 +0200 Subject: [PATCH 087/327] Make minor JEP updates --- test/jdk/java/lang/StableValue/JEP.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/jdk/java/lang/StableValue/JEP.md b/test/jdk/java/lang/StableValue/JEP.md index 74d33b8fdc3f5..21a40f1a7073a 100644 --- a/test/jdk/java/lang/StableValue/JEP.md +++ b/test/jdk/java/lang/StableValue/JEP.md @@ -181,7 +181,7 @@ It would be advantageous if "compute-at-most-once fields" could be expresses som // Declare a cache that can hold a single Logger instance Foo cache = ... Logger.getLogger("com.company.Foo") ...; ... -// Just returns the value if set. Otherwise computes and returns the value. +// Just returns the value if set. Otherwise computes, sets, and returns the value. // If another thread is computing a value, wait until it has completed. Logger logger = cache.xxxx(); ``` @@ -192,7 +192,7 @@ and for several compute-at-most-once values indexed by an `int`: // Declare a cache that can hold 10 Logger instance Bar cache = ... 10 ... i -> Logger.getLogger("com.company.Foo" + i) ... ... -// Just returns the value at the provided index if set. Otherwise computes and returns the value. +// Just returns the value at the provided index if set. Otherwise computes, sets, and returns the value. // If another thread is computing a value at the provided index, wait until it has completed. // Values can be computed in parallel by distinct threads. Logger logger = cache.xxxx(7); @@ -206,7 +206,7 @@ as the key type in the example below): // associated with the same finite number of `keys` Strings. Baz cache = ... keys ... Logger::getLogger... ... -// Just returns the value associated with the provided string if set. Otherwise computes and returns the value. +// Just returns the value associated with the provided string if set. Otherwise computes, sets, and returns the value. // If another thread is computing a value for the provided String, wait until it has completed. // Values can be computed in parallel by distinct threads. Logger logger = cache.xxxx("com.company.Foo"); @@ -216,10 +216,10 @@ For interoperability with legacy code, it would also be desirable if some of the expressed as compute-at-most-once constructs in a similar fashion: ``` -// Declare a List whose elements are lazily computed once accessed via List::get +// Declare a List whose elements are lazily computed upon being first accessed via List::get List lazyList = ... 10 ... i -> Logger.getLogger("com.company.Foo" + i) ... -// Declare a Map whose values are lazily computed once accessed via Map::get +// Declare a Map whose values are lazily computed upon being first accessed via Map::get Map lazyMap = ... keys ... Logger::getLogger... ``` From 4fbd5e198d3450499f655232b081b565d3571ce9 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 31 Jul 2024 08:46:18 +0200 Subject: [PATCH 088/327] Make slight JEP improvements --- test/jdk/java/lang/StableValue/JEP.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test/jdk/java/lang/StableValue/JEP.md b/test/jdk/java/lang/StableValue/JEP.md index 21a40f1a7073a..b0f899bc36fe1 100644 --- a/test/jdk/java/lang/StableValue/JEP.md +++ b/test/jdk/java/lang/StableValue/JEP.md @@ -175,7 +175,9 @@ optimizations, and brittleness) that plague the workarounds shown above and belo should gracefully scale to handle collections of constant values, while retaining efficient computer resource management. -It would be advantageous if "compute-at-most-once fields" could be expresses something along these lines: +It would be advantageous if "compute-at-most-once fields" could be expresses something along these lines where +`Foo`, `Bar`, and `Baz` indicates some Java class and the methods `Foo::xxxx`, `Bar::yyyy`, `Baz::zzzz` represents +some method with a yet-to-determine name: ``` // Declare a cache that can hold a single Logger instance @@ -195,7 +197,7 @@ Bar cache = ... 10 ... i -> Logger.getLogger("com.company.Foo" + i) ... // Just returns the value at the provided index if set. Otherwise computes, sets, and returns the value. // If another thread is computing a value at the provided index, wait until it has completed. // Values can be computed in parallel by distinct threads. -Logger logger = cache.xxxx(7); +Logger logger = cache.yyyy(7); ``` and even for several compute-at-most-once values associated with some key of arbitrary type `K` (where we use Strings @@ -209,7 +211,7 @@ Baz cache = ... keys ... Logger::getLogger... // Just returns the value associated with the provided string if set. Otherwise computes, sets, and returns the value. // If another thread is computing a value for the provided String, wait until it has completed. // Values can be computed in parallel by distinct threads. -Logger logger = cache.xxxx("com.company.Foo"); +Logger logger = cache.zzzz("com.company.Foo"); ``` For interoperability with legacy code, it would also be desirable if some of the standard collection types could be From f584837c478fa307ba3e7af48f696b95f6008254 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 31 Jul 2024 15:28:54 +0200 Subject: [PATCH 089/327] Address comments in PR --- .../share/classes/java/lang/StableValue.java | 11 +++---- .../internal/lang/stable/CachedFunction.java | 5 +-- test/jdk/java/lang/StableValue/JEP.md | 32 +++++++++---------- 3 files changed, 23 insertions(+), 25 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index b0be761d2f79d..66d89182b0243 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -27,9 +27,6 @@ import jdk.internal.access.SharedSecrets; import jdk.internal.javac.PreviewFeature; -import jdk.internal.lang.stable.CachedFunction; -import jdk.internal.lang.stable.CachedIntFunction; -import jdk.internal.lang.stable.CachedSupplier; import jdk.internal.lang.stable.StableValueImpl; import jdk.internal.lang.stable.StableValueUtil; @@ -68,12 +65,12 @@ * Supplier is guaranteed to be successfully invoked at most once even in a multithreaded * environment, can be created like this: * {@snippet lang = java : - * Supplier cached = StableValue.newCachedSupplier(original, null); + * Supplier cached = StableValue.newCachingSupplier(original, null); * } * The cached supplier can also be computed by a fresh background thread if a * thread factory is provided as a second parameter as shown here: * {@snippet lang = java : - * Supplier cached = StableValue.newCachedSupplier(original, Thread.ofVirtual().factory()); + * Supplier cached = StableValue.newCachingSupplier(original, Thread.ofVirtual().factory()); * } * * @@ -83,7 +80,7 @@ * guaranteed to be successfully invoked at most once per inout index even in a * multithreaded environment, can be created like this: * {@snippet lang = java: - * IntFunction cached = StableValue.newCachedIntFunction(size, original, null); + * IntFunction cached = StableValue.newCachingIntFunction(size, original, null); *} * Just like a cached supplier, a thread factory can be provided as a second parameter * allowing all the values for the allowed input values to be computed by distinct @@ -96,7 +93,7 @@ * at most once per input value even in a multithreaded environment, can be created like * this: * {@snippet lang = java : - * Function cached = StableValue.newCachedFunction(inputs, original, null); + * Function cached = StableValue.newCachingFunction(inputs, original, null); * } * Just like a cached supplier, a thread factory can be provided as a second parameter * allowing all the values for the allowed input values to be computed by distinct diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/CachedFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/CachedFunction.java index e1ba6b663036d..a06fd787dc1ea 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/CachedFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/CachedFunction.java @@ -79,10 +79,11 @@ private String renderValues() { sb.append("{"); for (var e:values.entrySet()) { final Object value = e.getValue().value(); + sb.append(e.getKey()).append('='); if (value == this) { - sb.append("(self)"); + sb.append("(this CachedFunction)"); } else { - sb.append(e.getKey()).append('=').append(StableValueUtil.render(value)); + sb.append(StableValueUtil.render(value)); } } sb.append("}"); diff --git a/test/jdk/java/lang/StableValue/JEP.md b/test/jdk/java/lang/StableValue/JEP.md index b0f899bc36fe1..03c1a2ca6420a 100644 --- a/test/jdk/java/lang/StableValue/JEP.md +++ b/test/jdk/java/lang/StableValue/JEP.md @@ -52,8 +52,7 @@ Another property of "at-most-once fields" would be, they are written to at most are likely read at many occasions. Hence, updating the field would not be so time-critical whereas every effort should be made to make reading the field performant. -Using existing Java semantics, it is possible to devise solutions that _partially_ emulates an "at-most-once field". In -the solutions exemplified below, an "at-most-once field" of type `Logger` is used in a cache. +Using existing Java semantics, it is possible to devise solutions that _partially_ emulates an "at-most-once field". Here is how a naïve `Logger` cache backed by a mutable field could look like: @@ -63,19 +62,18 @@ public class Cache { private Logger logger; - public Logger logger() { - Logger v = logger; - if (v == null) { - logger = v = Logger.getLogger("com.company.Foo"); + public Logger get() { + if (logger == null) { + logger = Logger.getLogger("com.company.Foo"); } - return v; + return logger; } } ``` -This solution does not work in a multithreaded environment as updates made by one thread to the `logger` may not be -visible to other threads, thereby allowing the `logger` variable to be updated several times and consequently the -`Logger::getLogger` method can be called several times. +This solution does not work in a multithreaded environment. One of many problems is, updates made by one thread to the +`logger` may not be visible to other threads, thereby allowing the `logger` variable to be updated several times and +consequently the `Logger::getLogger` method can be called several times. Here is how thread safety can be added together with a guarantee, `logger` is only updated at most once (and thereby `Logger::getLogger` is called at most once): @@ -86,12 +84,11 @@ public class Cache { private Logger logger; - public synchronized Logger logger() { - Logger v = logger; - if (v == null) { - logger = v = Logger.getLogger("com.company.Foo"); + public synchronized Logger get() { + if (logger == null) { + logger = Logger.getLogger("com.company.Foo"); } - return v; + return logger; } } ``` @@ -110,11 +107,14 @@ public class Cache { private volatile Logger logger; public Logger logger() { + // Use a local variable to save a volatile read if `logger` is non-null Logger v = logger; if (v == null) { synchronized (this) { + // Re-read the cached value under synchronization v = logger; if (v == null) { + // Assign both `logger` and `v` in one line logger = v = Logger.getLogger("com.company.Foo"); } } @@ -266,7 +266,7 @@ The Stable Values API defines an interface so that client code in libraries and - Define and use a stable value: - [`StableValue.newInstance()`](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/java/lang/StableValue.html#newInstance()) -- Define various _cached_ functions: +- Define various _caching_ functions: - [`StableValue.newCachedSupplier()`](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/java/lang/StableValue.html#newCachingSupplier(java.util.function.Supplier,java.util.concurrent.ThreadFactory)) - [`StableValue.newCachedIntFunction()`](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/java/lang/StableValue.html#newCachingIntFunction(int,java.util.function.IntFunction,java.util.concurrent.ThreadFactory)) - [`StableValue.newCachedFunction()`](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/java/lang/StableValue.html#newCachingFunction(java.util.Set,java.util.function.Function,java.util.concurrent.ThreadFactory)) From 0d3130840c8e2af366df1fadfcb91ee9ad3372e7 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 31 Jul 2024 16:53:36 +0200 Subject: [PATCH 090/327] Fix issues with caching functions --- ...chedFunction.java => CachingFunction.java} | 17 ++++---- ...tFunction.java => CachingIntFunction.java} | 14 +++---- ...chedSupplier.java => CachingSupplier.java} | 15 +++---- .../internal/lang/stable/StableValueImpl.java | 10 ++++- .../internal/lang/stable/StableValueUtil.java | 6 +-- .../lang/StableValue/CachingFunctionTest.java | 41 ++++++++++++++++++- .../StableValue/CachingIntFunctionTest.java | 41 +++++++++++++++++-- .../lang/StableValue/CachingSupplierTest.java | 39 ++++++++++++++++-- .../java/lang/StableValue/LazyListTest.java | 15 +++++++ .../java/lang/StableValue/LazyMapTest.java | 14 +++++++ .../lang/StableValue/StableValueTest.java | 34 +++++++-------- 11 files changed, 191 insertions(+), 55 deletions(-) rename src/java.base/share/classes/jdk/internal/lang/stable/{CachedFunction.java => CachingFunction.java} (81%) rename src/java.base/share/classes/jdk/internal/lang/stable/{CachedIntFunction.java => CachingIntFunction.java} (87%) rename src/java.base/share/classes/jdk/internal/lang/stable/{CachedSupplier.java => CachingSupplier.java} (79%) diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/CachedFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/CachingFunction.java similarity index 81% rename from src/java.base/share/classes/jdk/internal/lang/stable/CachedFunction.java rename to src/java.base/share/classes/jdk/internal/lang/stable/CachingFunction.java index a06fd787dc1ea..92d634e5c724d 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/CachedFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/CachingFunction.java @@ -30,13 +30,12 @@ import java.util.Map; import java.util.Set; import java.util.function.Function; -import java.util.stream.Collectors; // Note: It would be possible to just use `LazyMap::get` with some additional logic // instead of this class but explicitly providing a class like this provides better // debug capability, exception handling, and may provide better performance. -public record CachedFunction(Map> values, - Function original) implements Function { +public record CachingFunction(Map> values, + Function original) implements Function { @ForceInline @Override public R apply(T value) { @@ -71,17 +70,19 @@ public boolean equals(Object obj) { @Override public String toString() { - return "CachedFunction[values=" + renderValues() + ", original=" + original + "]"; + return "CachingFunction[values=" + renderValues() + ", original=" + original + "]"; } private String renderValues() { final StringBuilder sb = new StringBuilder(); sb.append("{"); + boolean first = true; for (var e:values.entrySet()) { + if (first) { first = false; } else { sb.append(", "); }; final Object value = e.getValue().value(); sb.append(e.getKey()).append('='); if (value == this) { - sb.append("(this CachedFunction)"); + sb.append("(this CachingFunction)"); } else { sb.append(StableValueUtil.render(value)); } @@ -90,9 +91,9 @@ private String renderValues() { return sb.toString(); } - public static CachedFunction of(Set inputs, - Function original) { - return new CachedFunction<>(StableValueUtil.ofMap(inputs), original); + public static CachingFunction of(Set inputs, + Function original) { + return new CachingFunction<>(StableValueUtil.ofMap(inputs), original); } } diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/CachedIntFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/CachingIntFunction.java similarity index 87% rename from src/java.base/share/classes/jdk/internal/lang/stable/CachedIntFunction.java rename to src/java.base/share/classes/jdk/internal/lang/stable/CachingIntFunction.java index 8a3eec0e650d8..a558ed6cade05 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/CachedIntFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/CachingIntFunction.java @@ -43,15 +43,15 @@ * * @param the return type */ -public final class CachedIntFunction implements IntFunction { +public final class CachingIntFunction implements IntFunction { private final IntFunction original; private final Object[] mutexes; @Stable private final Object[] values; - public CachedIntFunction(int size, - IntFunction original) { + public CachingIntFunction(int size, + IntFunction original) { this.original = original; this.mutexes = new Object[size]; for (int i = 0; i < size; i++) { @@ -85,20 +85,20 @@ public R apply(int value) { return r; } - public static CachedIntFunction of(int size, IntFunction original) { - return new CachedIntFunction<>(size, original); + public static CachingIntFunction of(int size, IntFunction original) { + return new CachingIntFunction<>(size, original); } @Override public String toString() { - return "CachedIntFunction[values=" + + return "CachingIntFunction[values=" + "[" + valuesAsString() + "]" + ", original=" + original + ']'; } private String valuesAsString() { return Arrays.stream(values, 0, values.length) - .map(StableValueUtil::render) + .map(v -> (v == this) ? "(this CachingIntFunction)" : StableValueUtil.render(v)) .collect(Collectors.joining(", ")); } diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/CachedSupplier.java b/src/java.base/share/classes/jdk/internal/lang/stable/CachingSupplier.java similarity index 79% rename from src/java.base/share/classes/jdk/internal/lang/stable/CachedSupplier.java rename to src/java.base/share/classes/jdk/internal/lang/stable/CachingSupplier.java index d58ce2387237b..ec653d238c266 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/CachedSupplier.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/CachingSupplier.java @@ -28,7 +28,6 @@ import jdk.internal.vm.annotation.ForceInline; import jdk.internal.vm.annotation.Stable; -import java.util.Objects; import java.util.function.Supplier; /** @@ -39,17 +38,17 @@ * * @param the return type */ -public final class CachedSupplier implements Supplier { +public final class CachingSupplier implements Supplier { private static final long VALUE_OFFSET = - StableValueUtil.UNSAFE.objectFieldOffset(CachedSupplier.class, "value"); + StableValueUtil.UNSAFE.objectFieldOffset(CachingSupplier.class, "value"); private final Supplier original; private final Object mutex = new Object(); @Stable private T value; - public CachedSupplier(Supplier original) { + public CachingSupplier(Supplier original) { this.original = original; } @@ -72,13 +71,15 @@ public T get() { return t; } - public static CachedSupplier of(Supplier original) { - return new CachedSupplier<>(original); + public static CachingSupplier of(Supplier original) { + return new CachingSupplier<>(original); } @Override public String toString() { - return "CachedSupplier[value=" + StableValueUtil.render(StableValueUtil.UNSAFE.getReferenceVolatile(this, VALUE_OFFSET)) + ", original=" + original + "]"; + @SuppressWarnings("unchecked") + final T t = (T) StableValueUtil.UNSAFE.getReferenceVolatile(this, VALUE_OFFSET); + return "CachingSupplier[value=" + (t == this ? "(this CachingSupplier)" : StableValueUtil.render(t)) + ", original=" + original + "]"; } } 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 7aef2717614e2..9698d2d1bb238 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 @@ -91,7 +91,10 @@ public boolean isSet() { @Override public int hashCode() { - return Objects.hashCode(value()); + final T t = value(); + return t == this + ? 1 + : Objects.hashCode(value()); } @Override @@ -104,7 +107,10 @@ public boolean equals(Object obj) { @Override public String toString() { - return "StableValue" + StableValueUtil.render(value()); + final T t = value(); + return t == this + ? "(this StableValue)" + : "StableValue" + StableValueUtil.render(t); } @SuppressWarnings("unchecked") diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableValueUtil.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableValueUtil.java index 8d70288cbb516..b16949aacc6f1 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableValueUtil.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableValueUtil.java @@ -96,7 +96,7 @@ public static Map> ofMap(Set keys) { public static Supplier newCachingSupplier(Supplier original, ThreadFactory factory) { - final Supplier memoized = CachedSupplier.of(original); + final Supplier memoized = CachingSupplier.of(original); if (factory != null) { final Thread thread = factory.newThread(new Runnable() { @@ -114,7 +114,7 @@ public static IntFunction newCachingIntFunction(int size, IntFunction original, ThreadFactory factory) { - final IntFunction memoized = CachedIntFunction.of(size, original); + final IntFunction memoized = CachingIntFunction.of(size, original); if (factory != null) { for (int i = 0; i < size; i++) { @@ -132,7 +132,7 @@ public static Function newCachingFunction(Set inputs, Function original, ThreadFactory factory) { - final Function memoized = CachedFunction.of(inputs, original); + final Function memoized = CachingFunction.of(inputs, original); if (factory != null) { for (final T t : inputs) { diff --git a/test/jdk/java/lang/StableValue/CachingFunctionTest.java b/test/jdk/java/lang/StableValue/CachingFunctionTest.java index 4344bce1a833f..a1012aa39c9bf 100644 --- a/test/jdk/java/lang/StableValue/CachingFunctionTest.java +++ b/test/jdk/java/lang/StableValue/CachingFunctionTest.java @@ -32,6 +32,8 @@ import java.util.Set; import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Function; import static org.junit.jupiter.api.Assertions.*; @@ -45,11 +47,13 @@ void basic() { assertEquals(1, cif.cnt()); assertEquals(42, cached.apply(42)); assertEquals(1, cif.cnt()); - assertTrue(cached.toString().startsWith("CachedFunction[values={")); + assertTrue(cached.toString().startsWith("CachingFunction[values={")); // Key order is unspecified assertTrue(cached.toString().contains("13=.unset")); assertTrue(cached.toString().contains("42=[42]")); assertTrue(cached.toString().endsWith(", original=" + cif + "]")); + // One between the values and one just before "original" + assertEquals(2L, cached.toString().chars().filter(ch -> ch == ',').count()); var x = assertThrows(IllegalArgumentException.class, () -> cached.apply(-1)); assertTrue(x.getMessage().contains("-1")); } @@ -84,11 +88,44 @@ void exception() { assertEquals(1, cif.cnt()); assertThrows(UnsupportedOperationException.class, () -> cached.apply(42)); assertEquals(2, cif.cnt()); - assertTrue(cached.toString().startsWith("CachedFunction[values={")); + assertTrue(cached.toString().startsWith("CachingFunction[values={")); // Key order is unspecified assertTrue(cached.toString().contains("13=.unset")); assertTrue(cached.toString().contains("42=.unset")); assertTrue(cached.toString().endsWith(", original=" + cif + "]")); } + @Test + void circular() { + final AtomicReference> ref = new AtomicReference<>(); + Function> cached = StableValue.newCachingFunction(Set.of(0, 1), _ -> ref.get(), null); + ref.set(cached); + cached.apply(0); + String toString = cached.toString(); + assertTrue(toString.contains("(this CachingFunction)")); + assertDoesNotThrow(cached::hashCode); + assertDoesNotThrow((() -> cached.equals(cached))); + } + + @Test + void equality() { + Set keys = Set.of(13, 42); + Function mapper = Function.identity(); + Function f0 = StableValue.newCachingFunction(keys, mapper, null); + Function f1 = StableValue.newCachingFunction(keys, mapper, null); + // No function is equal to another function + assertNotEquals(f0, f1); + } + + @Test + void hashCodeStable() { + Set keys = Set.of(13, 42); + Function f0 = StableValue.newCachingFunction(keys, Function.identity(), null); + int hBefore = f0.hashCode(); + f0.apply(42); + int hAfter = f0.hashCode(); + // hashCode() shall not change + assertEquals(hBefore, hAfter); + } + } diff --git a/test/jdk/java/lang/StableValue/CachingIntFunctionTest.java b/test/jdk/java/lang/StableValue/CachingIntFunctionTest.java index de893ca4e41f5..f1b10ac2cbd91 100644 --- a/test/jdk/java/lang/StableValue/CachingIntFunctionTest.java +++ b/test/jdk/java/lang/StableValue/CachingIntFunctionTest.java @@ -29,8 +29,12 @@ import org.junit.jupiter.api.Test; +import java.util.Set; import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Function; +import java.util.function.IntFunction; import static org.junit.jupiter.api.Assertions.*; @@ -42,12 +46,12 @@ final class CachingIntFunctionTest { void basic() { StableTestUtil.CountingIntFunction cif = new StableTestUtil.CountingIntFunction<>(i -> i); var cached = StableValue.newCachingIntFunction(SIZE, cif, null); - assertEquals("CachedIntFunction[values=[.unset, .unset], original=" + cif + "]", cached.toString()); + assertEquals("CachingIntFunction[values=[.unset, .unset], original=" + cif + "]", cached.toString()); assertEquals(1, cached.apply(1)); assertEquals(1, cif.cnt()); assertEquals(1, cached.apply(1)); assertEquals(1, cif.cnt()); - assertEquals("CachedIntFunction[values=[.unset, [1]], original=" + cif + "]", cached.toString()); + assertEquals("CachingIntFunction[values=[.unset, [1]], original=" + cif + "]", cached.toString()); assertThrows(IllegalArgumentException.class, () -> cached.apply(SIZE + 1)); } @@ -81,7 +85,38 @@ void exception() { assertEquals(1, cif.cnt()); assertThrows(UnsupportedOperationException.class, () -> cached.apply(1)); assertEquals(2, cif.cnt()); - assertEquals("CachedIntFunction[values=[.unset, .unset], original=" + cif + "]", cached.toString()); + assertEquals("CachingIntFunction[values=[.unset, .unset], original=" + cif + "]", cached.toString()); + } + + @Test + void circular() { + final AtomicReference> ref = new AtomicReference<>(); + IntFunction> cached = StableValue.newCachingIntFunction(SIZE, _ -> ref.get(), null); + ref.set(cached); + cached.apply(0); + String toString = cached.toString(); + assertTrue(toString.startsWith("CachingIntFunction[values=[(this CachingIntFunction), .unset], original=")); + assertDoesNotThrow(cached::hashCode); + assertDoesNotThrow((() -> cached.equals(cached))); + } + + @Test + void equality() { + IntFunction mapper = i -> i; + IntFunction f0 = StableValue.newCachingIntFunction(8, mapper, null); + IntFunction f1 = StableValue.newCachingIntFunction(8, mapper, null); + // No function is equal to another function + assertNotEquals(f0, f1); + } + + @Test + void hashCodeStable() { + IntFunction f0 = StableValue.newCachingIntFunction(8, i -> i, null); + int hBefore = f0.hashCode(); + f0.apply(4); + int hAfter = f0.hashCode(); + // hashCode() shall not change + assertEquals(hBefore, hAfter); } } diff --git a/test/jdk/java/lang/StableValue/CachingSupplierTest.java b/test/jdk/java/lang/StableValue/CachingSupplierTest.java index 7a767d0695410..5114967d5d2ba 100644 --- a/test/jdk/java/lang/StableValue/CachingSupplierTest.java +++ b/test/jdk/java/lang/StableValue/CachingSupplierTest.java @@ -31,6 +31,9 @@ import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.IntFunction; +import java.util.function.Supplier; import static org.junit.jupiter.api.Assertions.*; @@ -40,12 +43,12 @@ final class CachingSupplierTest { void basic() { StableTestUtil.CountingSupplier cs = new StableTestUtil.CountingSupplier<>(() -> 42); var cached = StableValue.newCachingSupplier(cs, null); - assertEquals("CachedSupplier[value=.unset, original=" + cs + "]", cached.toString()); + assertEquals("CachingSupplier[value=.unset, original=" + cs + "]", cached.toString()); assertEquals(42, cached.get()); assertEquals(1, cs.cnt()); assertEquals(42, cached.get()); assertEquals(1, cs.cnt()); - assertEquals("CachedSupplier[value=[42], original=" + cs + "]", cached.toString()); + assertEquals("CachingSupplier[value=[42], original=" + cs + "]", cached.toString()); } @Test @@ -77,7 +80,37 @@ void exception() { assertEquals(1, cs.cnt()); assertThrows(UnsupportedOperationException.class, cached::get); assertEquals(2, cs.cnt()); - assertEquals("CachedSupplier[value=.unset, original=" + cs + "]", cached.toString()); + assertEquals("CachingSupplier[value=.unset, original=" + cs + "]", cached.toString()); + } + + @Test + void circular() { + final AtomicReference> ref = new AtomicReference<>(); + Supplier> cached = StableValue.newCachingSupplier(ref::get, null); + ref.set(cached); + cached.get(); + String toString = cached.toString(); + assertTrue(toString.startsWith("CachingSupplier[value=(this CachingSupplier), original=")); + assertDoesNotThrow(cached::hashCode); + } + + @Test + void equality() { + Supplier mapper = () -> 42; + Supplier f0 = StableValue.newCachingSupplier(mapper, null); + Supplier f1 = StableValue.newCachingSupplier(mapper, null); + // No function is equal to another function + assertNotEquals(f0, f1); + } + + @Test + void hashCodeStable() { + Supplier f0 = StableValue.newCachingSupplier(() -> 42, null); + int hBefore = f0.hashCode(); + f0.get(); + int hAfter = f0.hashCode(); + // hashCode() shall not change + assertEquals(hBefore, hAfter); } } diff --git a/test/jdk/java/lang/StableValue/LazyListTest.java b/test/jdk/java/lang/StableValue/LazyListTest.java index 4c592eab28b4b..79aa3840adaf7 100644 --- a/test/jdk/java/lang/StableValue/LazyListTest.java +++ b/test/jdk/java/lang/StableValue/LazyListTest.java @@ -23,17 +23,22 @@ /* @test * @summary Basic tests for LazyList methods + * @modules java.base/jdk.internal.lang.stable * @compile --enable-preview -source ${jdk.version} LazyListTest.java * @run junit/othervm --enable-preview LazyListTest */ +import jdk.internal.lang.stable.StableValueImpl; +import jdk.internal.lang.stable.StableValueUtil; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import java.io.Serializable; import java.util.Comparator; +import java.util.IdentityHashMap; import java.util.List; +import java.util.Map; import java.util.NoSuchElementException; import java.util.RandomAccess; import java.util.Set; @@ -242,6 +247,16 @@ void randomAccess() { assertInstanceOf(RandomAccess.class, newList().subList(1, INDEX)); } + @Test + void distinct() { + List> list = StableValueUtil.ofList(13); + assertEquals(13, list.size()); + // Check, every StableValue is distinct + Map, Boolean> idMap = new IdentityHashMap<>(); + list.forEach(e -> idMap.put(e, true)); + assertEquals(13, idMap.size()); + } + // Support constructs record Operation(String name, diff --git a/test/jdk/java/lang/StableValue/LazyMapTest.java b/test/jdk/java/lang/StableValue/LazyMapTest.java index 0dab9f009919f..312f1671f204d 100644 --- a/test/jdk/java/lang/StableValue/LazyMapTest.java +++ b/test/jdk/java/lang/StableValue/LazyMapTest.java @@ -23,10 +23,13 @@ /* @test * @summary Basic tests for LazyMap methods + * @modules java.base/jdk.internal.lang.stable * @compile --enable-preview -source ${jdk.version} LazyMapTest.java * @run junit/othervm --enable-preview LazyMapTest */ +import jdk.internal.lang.stable.StableValueImpl; +import jdk.internal.lang.stable.StableValueUtil; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -34,6 +37,7 @@ import java.io.Serializable; import java.util.AbstractMap; import java.util.HashSet; +import java.util.IdentityHashMap; import java.util.Map; import java.util.Set; import java.util.function.Consumer; @@ -197,6 +201,16 @@ void serializable() { assertFalse(newEmptyMap() instanceof Serializable); } + @Test + void distinct() { + Map> map = StableValueUtil.ofMap(Set.of(1, 2, 3)); + assertEquals(3, map.size()); + // Check, every StableValue is distinct + Map, Boolean> idMap = new IdentityHashMap<>(); + map.forEach((k, v) -> idMap.put(v, true)); + assertEquals(3, idMap.size()); + } + // Support constructs record Operation(String name, diff --git a/test/jdk/java/lang/StableValue/StableValueTest.java b/test/jdk/java/lang/StableValue/StableValueTest.java index ea8bd8f42210d..cf47189dafc43 100644 --- a/test/jdk/java/lang/StableValue/StableValueTest.java +++ b/test/jdk/java/lang/StableValue/StableValueTest.java @@ -23,21 +23,15 @@ /* @test * @summary Basic tests for StableValue implementations - * @modules java.base/jdk.internal.lang.stable * @compile --enable-preview -source ${jdk.version} StableValueTest.java * @run junit/othervm --enable-preview StableValueTest */ -import jdk.internal.lang.stable.StableValueImpl; -import jdk.internal.lang.stable.StableValueUtil; import org.junit.jupiter.api.Test; import java.util.BitSet; -import java.util.IdentityHashMap; import java.util.List; -import java.util.Map; import java.util.NoSuchElementException; -import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.LockSupport; @@ -108,23 +102,23 @@ void testEquals() { } @Test - void ofList() { - List> list = StableValueUtil.ofList(13); - assertEquals(13, list.size()); - // Check, every StableValue is distinct - Map, Boolean> idMap = new IdentityHashMap<>(); - list.forEach(e -> idMap.put(e, true)); - assertEquals(13, idMap.size()); + void circular() { + StableValue> stable = StableValue.newInstance(); + stable.trySet(stable); + String toString = stable.toString(); + assertEquals(toString, "(this StableValue)"); + assertDoesNotThrow(stable::hashCode); + assertDoesNotThrow((() -> stable.equals(stable))); } @Test - void ofMap() { - Map> map = StableValueUtil.ofMap(Set.of(1, 2, 3)); - assertEquals(3, map.size()); - // Check, every StableValue is distinct - Map, Boolean> idMap = new IdentityHashMap<>(); - map.forEach((k, v) -> idMap.put(v, true)); - assertEquals(3, idMap.size()); + void hashCodeDependsOnHolderValue() { + StableValue stable = StableValue.newInstance(); + int hBefore = stable.hashCode(); + stable.trySet(42); + int hAfter = stable.hashCode(); + // hashCode() shall change + assertNotEquals(hBefore, hAfter); } private static final BiPredicate, Integer> TRY_SET = StableValue::trySet; From 344e97c6289b854b9605a045b205cf2bb1af73f4 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 31 Jul 2024 17:13:42 +0200 Subject: [PATCH 091/327] Rename and improve benchmarks --- .../lang/StableValue/CachingSupplierTest.java | 1 - ...ark.java => CachingFunctionBenchmark.java} | 4 +-- ....java => CachingIntFunctionBenchmark.java} | 2 +- ...ark.java => CachingSupplierBenchmark.java} | 2 +- .../lang/stable/StableValueBenchmark.java | 32 ++++++++++++++++--- 5 files changed, 31 insertions(+), 10 deletions(-) rename test/micro/org/openjdk/bench/java/lang/stable/{CachedFunctionBenchmark.java => CachingFunctionBenchmark.java} (97%) rename test/micro/org/openjdk/bench/java/lang/stable/{CachedIntFunctionBenchmark.java => CachingIntFunctionBenchmark.java} (98%) rename test/micro/org/openjdk/bench/java/lang/stable/{CachedSupplierBenchmark.java => CachingSupplierBenchmark.java} (98%) diff --git a/test/jdk/java/lang/StableValue/CachingSupplierTest.java b/test/jdk/java/lang/StableValue/CachingSupplierTest.java index 5114967d5d2ba..6627e88b36efd 100644 --- a/test/jdk/java/lang/StableValue/CachingSupplierTest.java +++ b/test/jdk/java/lang/StableValue/CachingSupplierTest.java @@ -32,7 +32,6 @@ import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; -import java.util.function.IntFunction; import java.util.function.Supplier; import static org.junit.jupiter.api.Assertions.*; diff --git a/test/micro/org/openjdk/bench/java/lang/stable/CachedFunctionBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/CachingFunctionBenchmark.java similarity index 97% rename from test/micro/org/openjdk/bench/java/lang/stable/CachedFunctionBenchmark.java rename to test/micro/org/openjdk/bench/java/lang/stable/CachingFunctionBenchmark.java index 15041cbc53451..da728b63a77bc 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/CachedFunctionBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/CachingFunctionBenchmark.java @@ -35,12 +35,10 @@ import org.openjdk.jmh.annotations.Threads; import org.openjdk.jmh.annotations.Warmup; -import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.function.Function; -import java.util.function.IntFunction; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -58,7 +56,7 @@ "-XX:PerMethodTrapLimit=0"}) @Threads(Threads.MAX) // Benchmark under contention @OperationsPerInvocation(100) -public class CachedFunctionBenchmark { +public class CachingFunctionBenchmark { private static final int SIZE = 100; private static final Set SET = IntStream.range(0, SIZE).boxed().collect(Collectors.toSet()); diff --git a/test/micro/org/openjdk/bench/java/lang/stable/CachedIntFunctionBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/CachingIntFunctionBenchmark.java similarity index 98% rename from test/micro/org/openjdk/bench/java/lang/stable/CachedIntFunctionBenchmark.java rename to test/micro/org/openjdk/bench/java/lang/stable/CachingIntFunctionBenchmark.java index 5096cbb982ea3..9a1e91e93d368 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/CachedIntFunctionBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/CachingIntFunctionBenchmark.java @@ -53,7 +53,7 @@ "-XX:PerMethodTrapLimit=0"}) @Threads(Threads.MAX) // Benchmark under contention @OperationsPerInvocation(100) -public class CachedIntFunctionBenchmark { +public class CachingIntFunctionBenchmark { private static final int SIZE = 100; private static final IntFunction IDENTITY = i -> i; diff --git a/test/micro/org/openjdk/bench/java/lang/stable/CachedSupplierBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/CachingSupplierBenchmark.java similarity index 98% rename from test/micro/org/openjdk/bench/java/lang/stable/CachedSupplierBenchmark.java rename to test/micro/org/openjdk/bench/java/lang/stable/CachingSupplierBenchmark.java index 64725910bf6d4..2710a46fb8ea8 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/CachedSupplierBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/CachingSupplierBenchmark.java @@ -52,7 +52,7 @@ "-XX:PerMethodTrapLimit=0"}) @Threads(Threads.MAX) // Benchmark under contention @OperationsPerInvocation(2) -public class CachedSupplierBenchmark { +public class CachingSupplierBenchmark { private static final int VALUE = 42; private static final int VALUE2 = 23; diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java index 52b27ebc4bd8a..4051bf81f62f0 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java @@ -54,6 +54,8 @@ public class StableValueBenchmark { private static final StableValue DCL2 = init(StableValue.newInstance(), VALUE2); private static final AtomicReference ATOMIC = new AtomicReference<>(VALUE); private static final AtomicReference ATOMIC2 = new AtomicReference<>(VALUE2); + private static final Holder HOLDER = new Holder(VALUE); + private static final Holder HOLDER2 = new Holder(VALUE2); private final StableValue stable = init(StableValue.newInstance(), VALUE); private final StableValue stable2 = init(StableValue.newInstance(), VALUE2); @@ -111,8 +113,8 @@ public int refSupplier() { } @Benchmark - public int staticStable() { - return STABLE.orElseThrow() + STABLE2.orElseThrow(); + public int staticAtomic() { + return ATOMIC.get() + ATOMIC2.get(); } @Benchmark @@ -121,15 +123,37 @@ public int staticDcl() { } @Benchmark - public int staticAtomic() { - return ATOMIC.get() + ATOMIC2.get(); + public int staticHolder() { + return HOLDER.get() + HOLDER2.get(); + } + + @Benchmark + public int staticStable() { + return STABLE.orElseThrow() + STABLE2.orElseThrow(); } + private static StableValue init(StableValue m, Integer value) { m.trySet(value); return m; } + // The VM should be able to constant-fold the value given in the constructor + // because StableValue fields have a special meaning. + private static final class Holder { + + private final StableValue delegate = StableValue.newInstance(); + + Holder(int value) { + delegate.setOrThrow(value); + } + + int get() { + return delegate.orElseThrow(); + } + + } + // Handles null values private static class Dcl implements Supplier { From f7c17fcfbf9244f67b276c432fe147fb2dc762ce Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 31 Jul 2024 18:28:36 +0200 Subject: [PATCH 092/327] Update table in JEP --- test/jdk/java/lang/StableValue/JEP.md | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/test/jdk/java/lang/StableValue/JEP.md b/test/jdk/java/lang/StableValue/JEP.md index 03c1a2ca6420a..9a328a661c2d1 100644 --- a/test/jdk/java/lang/StableValue/JEP.md +++ b/test/jdk/java/lang/StableValue/JEP.md @@ -23,18 +23,20 @@ This might be the subject of a future JEP. Java allows developers to control whether fields are mutable or not. -* Mutable fields can be updated multiple times, and from any arbitrary position in the code. +* Mutable fields can be updated multiple times, and from any arbitrary position in the code and by any thread. * Immutable fields (i.e. `final` fields), can only be updated _once_, and only in very specific places: the - class initializer (for a static immutable field) or the class constructor(for an instance immutable field). + class initializer (for a static immutable field) or the class constructor(for an instance immutable field). + Only the thread that creates an instance or first reference a class can update values. Unfortunately, in Java there is no way to define a field that can be updated _at most once_ (i.e. fields that are -either not updated at all or are updated exactly once) and from _any_ arbitrary position in the code: - -| Field kind | #Updates | Code update location | -|--------------------|----------|-----------------------------------| -| Mutable | [0, ∞) | Anywhere | -| `final` | 1 | Constructor or static initializer | -| at-most-once (N/A) | [0, 1] | Anywhere | +either not updated at all or are updated exactly once) and from _any_ arbitrary position in the code and by +any thread: + +| Field kind | #Updates | Code update location | Constant folding | Update thread | +|--------------------|----------|-----------------------------------|------------------|---------------| +| Mutable | [0, ∞) | Anywhere | no | any | +| `final` | 1 | Constructor or static initializer | yes | creating/init | +| at-most-once (N/A) | [0, 1] | Anywhere | only if updated | any | _Table 1, showing properties of mutable, immutable, and at-most-once (currently not available) fields._ From 0688e02123b5d712b02f0d73460baeb252ac5f1a Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 31 Jul 2024 18:39:20 +0200 Subject: [PATCH 093/327] Fix small JEP issues --- test/jdk/java/lang/StableValue/JEP.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/test/jdk/java/lang/StableValue/JEP.md b/test/jdk/java/lang/StableValue/JEP.md index 9a328a661c2d1..270e9116d956d 100644 --- a/test/jdk/java/lang/StableValue/JEP.md +++ b/test/jdk/java/lang/StableValue/JEP.md @@ -29,8 +29,9 @@ Java allows developers to control whether fields are mutable or not. Only the thread that creates an instance or first reference a class can update values. Unfortunately, in Java there is no way to define a field that can be updated _at most once_ (i.e. fields that are -either not updated at all or are updated exactly once) and from _any_ arbitrary position in the code and by -any thread: +either not updated at all or are updated exactly once) and from _any_ arbitrary position and thread in the code. + +Here is how the different kinds of fields compare: | Field kind | #Updates | Code update location | Constant folding | Update thread | |--------------------|----------|-----------------------------------|------------------|---------------| @@ -109,8 +110,10 @@ public class Cache { private volatile Logger logger; public Logger logger() { + // Use a local variable to save a volatile read if `logger` is non-null Logger v = logger; + if (v == null) { synchronized (this) { // Re-read the cached value under synchronization @@ -135,10 +138,11 @@ Now, further imagine a situation where there are several loggers to be cached an values using an `int` index (i.e. 0 -> "com.company.Foo0", 1 -> "com.company.Foo1", etc.): ``` -// A an array of values protected by double-checked locking. Do not use this solution! +// An array of values protected by double-checked locking. Do not use this solution! public class Cache { private static final VarHandle ARRAY_HANDLE = MethodHandles.arrayElementVarHandle(Logger[].class); + private static final int SIZE = 10; private final Logger[] loggers = new Logger[SIZE]; @@ -334,7 +338,7 @@ called *only once*. This brings us to the introduction of _cached functions_. ### Cached functions So far, we have talked about the fundamental features of StableValue as s securely -wrapped `@Stable` value holder. However, it has become apparent, stable primitives are amenable +wrapped stable value holder. However, it has become apparent, stable primitives are amenable to composition with other constructs in order to create more high-level and powerful features. [Cached (or Memoized) functions](https://en.wikipedia.org/wiki/Memoization) are functions where the output for a From 180bbebc2f194c3db85a30a8bbc3b8328bbae043 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Fri, 2 Aug 2024 10:20:25 +0200 Subject: [PATCH 094/327] Change cached to caching in JEP --- test/jdk/java/lang/StableValue/JEP.md | 34 +++++++++++++-------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/test/jdk/java/lang/StableValue/JEP.md b/test/jdk/java/lang/StableValue/JEP.md index 270e9116d956d..da1dd23b67134 100644 --- a/test/jdk/java/lang/StableValue/JEP.md +++ b/test/jdk/java/lang/StableValue/JEP.md @@ -335,13 +335,13 @@ method simultaneously if they call the `logger()` method at about the same time. competing threads, there might be applications where it is a requirement, that a supplying method is called *only once*. This brings us to the introduction of _cached functions_. -### Cached functions +### Caching functions So far, we have talked about the fundamental features of StableValue as s securely wrapped stable value holder. However, it has become apparent, stable primitives are amenable to composition with other constructs in order to create more high-level and powerful features. -[Cached (or Memoized) functions](https://en.wikipedia.org/wiki/Memoization) are functions where the output for a +[Caching (or Memoized) functions](https://en.wikipedia.org/wiki/Memoization) are functions where the output for a particular input value is computed only once and is remembered such that remembered outputs can be reused for subsequent calls with recurring input values. @@ -352,7 +352,7 @@ accessed via its `Supplier::get` method. In a multithreaded scenario, competing until the first thread has computed a cached value. Unsurprisingly, the cached value is stored internally in a stable value. -Here is how the code in the previous example can be improved using a cached supplier: +Here is how the code in the previous example can be improved using a caching supplier: ``` class Foo { @@ -386,7 +386,7 @@ stable value elements. Here is an example where we manually map logger numbers ``` class CachedNum { - // 1. Centrally declare a cached IntFunction backed by a list of StableValue elements + // 1. Centrally declare a caching IntFunction backed by a list of StableValue elements private static final IntFunction LOGGERS = StableValue.newCachingIntFunction(2, CachedNum::fromNumber, null); @@ -411,17 +411,17 @@ class CachedNum { Note: Again, the last null parameter signifies an optional thread factory that will be explained at the end of this chapter. -As can be seen, manually mapping numbers to strings is a bit tedious. This brings us to the most general cached function -variant provided is a cached `Function` which, for example, can make sure `Logger::getLogger` in one of the first examples +As can be seen, manually mapping numbers to strings is a bit tedious. This brings us to the most general caching function +variant provided is a caching `Function` which, for example, can make sure `Logger::getLogger` in one of the first examples above is invoked at most once per input value (provided it executes successfully) in a multithreaded environment. Such a -cached `Function` is almost always faster and more resource efficient than a `ConcurrentHashMap`. +caching `Function` is almost always faster and more resource efficient than a `ConcurrentHashMap`. Here is what a caching `Function` lazily holding two loggers could look like: ``` class Cahced { - // 1. Centrally declare a cached function backed by a map of stable values + // 1. Centrally declare a caching function backed by a map of stable values private static final Function LOGGERS = StableValue.newCachingFunction(Set.of("com.company.Foo", "com.company.Bar"), Logger::getLogger, null); @@ -437,24 +437,24 @@ class Cahced { ``` It should be noted that the enumerated set of valid inputs given at creation time constitutes the only valid -input keys for the cached function. Providing a non-valid input for a cached `Function` (or a cached `IntFunction`) +input keys for the caching function. Providing a non-valid input for a caching `Function` (or a caching `IntFunction`) would incur an `IllegalArgumentException`. -An advantage with cached functions, compared to working directly with `StableValue` instances, is that the +An advantage with caching functions, compared to working directly with `StableValue` instances, is that the initialization logic can be centralized and maintained in a single place, usually at the same place where -the cached function is defined. +the caching function is defined. -Additional cached function types, such a cached `Predicate` (backed by a lazily computed -`Map>`) or a cached `BiFunction` can be custom-made. An astute reader will be able +Additional caching function types, such a caching `Predicate` (backed by a lazily computed +`Map>`) or a caching `BiFunction` can be custom-made. An astute reader will be able to write such constructs in a few lines. #### Background threads -As noted above, the cached-returning factories in the Stable Values API offers an optional +As noted above, the caching-returning factories in the Stable Values API offers an optional tailing thread factory parameter from which new value-computing background threads will be created: ``` -// 1. Centrally declare a cached function backed by a map of stable values +// 1. Centrally declare a caching function backed by a map of stable values // computed in the background by two distinct virtual threads. private static final Function LOGGERS = StableValue.newCachingFunction(Set.of("com.company.Foo", "com.company.Bar"), @@ -473,7 +473,7 @@ Logger logger = LOGGERS.apply(NAME); logger.log(Level.DEBUG, ...); ``` -This can provide a best-of-several-worlds situation where the cached function can be quickly defined (as no +This can provide a best-of-several-worlds situation where the caching function can be quickly defined (as no computation is made by the defining thread), the holder value is computed in background threads (thus neither interfering significantly with the critical startup path nor with future accessing threads), and the threads actually accessing the holder value can directly access the holder value with as-if-final performance and without having to compute @@ -540,7 +540,7 @@ Analogue to a lazy list, the lazy map guarantees the function provided at map cr (used to lazily compute the map values) is invoked at most once per key (absent any Exceptions), even though used from several threads. -Even though a cached `IntFunction` may sometimes replace a lazy `List` and a cached `Function` may be +Even though a caching `IntFunction` may sometimes replace a lazy `List` and a caching `Function` may be used in place of a lazy `Map`, a lazy `List` or `Map` oftentimes provides better interoperability with existing libraries. For example, if provided as a method parameter. From 7737c2985f4c8ca25aff6c6bf4a25655cebe5a50 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Fri, 2 Aug 2024 15:00:31 +0200 Subject: [PATCH 095/327] Rework the Motivation section in the JEP --- test/jdk/java/lang/StableValue/JEP.md | 227 +++--------------- .../lang/stable/CustomClassBenchmark.java | 189 +++++++++++++++ 2 files changed, 229 insertions(+), 187 deletions(-) create mode 100644 test/micro/org/openjdk/bench/java/lang/stable/CustomClassBenchmark.java diff --git a/test/jdk/java/lang/StableValue/JEP.md b/test/jdk/java/lang/StableValue/JEP.md index da1dd23b67134..c772be9f01bd0 100644 --- a/test/jdk/java/lang/StableValue/JEP.md +++ b/test/jdk/java/lang/StableValue/JEP.md @@ -21,138 +21,52 @@ This might be the subject of a future JEP. ## Motivation -Java allows developers to control whether fields are mutable or not. +Java allows developers to control whether fields should be mutable or not. Mutable fields can be updated multiple times, and from any arbitrary position in the code and by any thread. As such, mutable fields are often used to model complex objects whose state can be updated several times throughout their lifetimes, such as the contents of a text field in a UI component. Conversely, immutable fields (i.e. `final` fields), must be updated *exactly once*, and only in very specific places: the class initializer (for a static immutable field) or the class constructor(for an instance immutable field). As such, `final` fields are typically used to model values that act as *constants* (albeit shallowly so) throughout the lifetime of a class (in the case of `static` fields) or of an instance (in the case of an instance field). -* Mutable fields can be updated multiple times, and from any arbitrary position in the code and by any thread. -* Immutable fields (i.e. `final` fields), can only be updated _once_, and only in very specific places: the - class initializer (for a static immutable field) or the class constructor(for an instance immutable field). - Only the thread that creates an instance or first reference a class can update values. - -Unfortunately, in Java there is no way to define a field that can be updated _at most once_ (i.e. fields that are -either not updated at all or are updated exactly once) and from _any_ arbitrary position and thread in the code. +Most of the time deciding whether an object should feature mutable or immutable state is straightforward enough. There are however cases where a field is subject to *constrained mutation*. That is, field's value are neither constant, nor can they it mutated at will. Consider a program that might want to mutate a password field at most three times before it becomes immutable, in order to reflect the three login attempts allowed for a user; further mutation would result in some exception. Expressing this kind of constrained mutation is hard, and cannot be achieved without the help of advanced type-system [calculi](https://en.wikipedia.org/wiki/Dependent_type). However, one important and simpler case of constrained mutation is that of a field whose updated *at most once*. As we shall see, the lack of a mechanism to capture this specific kind of constrained mutation in the Java platform comes at a considerable cost of performance and expressiveness. -Here is how the different kinds of fields compare: +### An example: memoization -| Field kind | #Updates | Code update location | Constant folding | Update thread | -|--------------------|----------|-----------------------------------|------------------|---------------| -| Mutable | [0, ∞) | Anywhere | no | any | -| `final` | 1 | Constructor or static initializer | yes | creating/init | -| at-most-once (N/A) | [0, 1] | Anywhere | only if updated | any | +Constrained mutation is essential to reliably cache the result of an expensive method call, so that it can be reused several times throughout the lifetime of an application (this technique is also known as [memoization](https://en.wikipedia.org/wiki/Memoization)). A nearly ubiquitous example of such an expensive method call is that to obtain a logger object through which an application's events can be reported. Obtaining a logger often entails expensive operations, such as reading and parsing configuration data, or prepare the backing storage where logging events will be recorded. Since these operations are expensive, an application will typically want to move them as much *forward in time* as possible: after all, an application might never need to log an event, so why paying the cost for this expensive initialization? Moreover, as some of these operation results in side effects - such as the creation of files and folders - it is crucial that they are executed _at most once_. -_Table 1, showing properties of mutable, immutable, and at-most-once (currently not available) fields._ - -"At-most-once fields" would be essential to expensive cache computations associated with method calls, so that they can -be reused across multiple calls. For instance, creating a logger or reading application configurations from an external -database. Furthermore, if the VM is made aware, a field is an "at-most-once field" and it is set, it may -[constant-fold](https://en.wikipedia.org/wiki/Constant_folding) the field value, thereby providing crucial performance -and energy efficiency gains. - -It is also important to stress a method called to compute an "at-most-once field" might have intended or -unintended side effects and therefore, it would be vital to also guarantee the method is invoked at most once, even in -a multithreaded environment. - -Another property of "at-most-once fields" would be, they are written to at most once but -are likely read at many occasions. Hence, updating the field would not be so time-critical whereas every effort -should be made to make reading the field performant. - -Using existing Java semantics, it is possible to devise solutions that _partially_ emulates an "at-most-once field". - -Here is how a naïve `Logger` cache backed by a mutable field could look like: +Combining mutable fields and encapsulation is a common way to approximate at-most-once update semantics. Consider the following example, where a logger object is created in the `Application::getLogger` method: ``` -// A naïve cache. Do not use this solution! -public class Cache { +public class Application { private Logger logger; - public Logger get() { + public Logger getLogger() { if (logger == null) { - logger = Logger.getLogger("com.company.Foo"); + logger = Logger.create("com.company.Application"); } return logger; } } ``` -This solution does not work in a multithreaded environment. One of many problems is, updates made by one thread to the -`logger` may not be visible to other threads, thereby allowing the `logger` variable to be updated several times and -consequently the `Logger::getLogger` method can be called several times. - -Here is how thread safety can be added together with a guarantee, `logger` is only updated at most once -(and thereby `Logger::getLogger` is called at most once): - -``` -// A field protected by synchonization. Do not use this solution! -public class Cache { +As the `logger` field is private, the only way for clients to access it is to call the `getLogger` method. This method first tests whether a logger is already available, and if so that logger is returned. Otherwise, it proceeds to the creation of a *new* logger object, which is then stored in the `logger` field. In this way, we guarantee that the logger object is created at most once: the first time the `Application::getLogger` method is invoked. - private Logger logger; +Unfortunately, the above solution does not work in a multithreaded environment. For instance, updates to the `logger` field made by one thread may not be immediately visible to other threads. This condition might result in multiple concurrent calls to the `Logger::create` method, thereby violating the "at-most-once" update guarantee. - public synchronized Logger get() { - if (logger == null) { - logger = Logger.getLogger("com.company.Foo"); - } - return logger; - } -} -``` +#### Thread safety with double-checked locking -While this works, acquiring the `synhronized` monitor is slow and prevents multiple threads from accessing the cached -`Logger` instance simultaneously once computed. +One possible way to achieve thread-safety would be to serialize access to the `Application::getLogger` method - i.e. by marking that method as `synchronized`. However, doing so has a performance cost, as multiple threads cannot concurrently obtain the application's logger object, even *long after* this object has been computed, and safely stored in the `logger` field. In other words, using `synchronized` amounts at applying a *permanent* performance tax on *all* logger accesses, in the rare event that a race occurs during the initial update of the `logger` field. -The solution above can be modified to use the -[*double-checked locking idiom*](https://en.wikipedia.org/wiki/Double-checked_locking) which would improve the -situation a bit: +In order to achieve thread-safety without compromising performance, developers often resorts to the brittle [double-checked idiom](https://en.wikipedia.org/wiki/Double-checked_locking): ``` -// A field protected by double-checked locking. Do not use this solution! -public class Cache { +class Application { private volatile Logger logger; public Logger logger() { - - // Use a local variable to save a volatile read if `logger` is non-null Logger v = logger; - if (v == null) { synchronized (this) { - // Re-read the cached value under synchronization v = logger; if (v == null) { - // Assign both `logger` and `v` in one line - logger = v = Logger.getLogger("com.company.Foo"); - } - } - } - return v; - } -} -``` -While the solution above is an improvement over the previous one, the double-checked locking idiom is brittle and easy -to get subtly wrong (see *Java Concurrency in Practice*, 16.2.4, by Brian Goetz). For example, a common error is forgetting -to declare the field `volatile` resulting in the risk of observing incomplete objects. Another issue is that synchronization -is made on the `Cache` instance itself, potentially opening up for deadlock situations. Furthermore, every access to the -cached value is made using `volatile` semantics which may be slow on some platforms. - -Now, further imagine a situation where there are several loggers to be cached and where we want to reference the cached -values using an `int` index (i.e. 0 -> "com.company.Foo0", 1 -> "com.company.Foo1", etc.): - -``` -// An array of values protected by double-checked locking. Do not use this solution! -public class Cache { - - private static final VarHandle ARRAY_HANDLE = MethodHandles.arrayElementVarHandle(Logger[].class); - - private static final int SIZE = 10; - private final Logger[] loggers = new Logger[SIZE]; - - public Logger logger(int i) { - Logger v = (Logger) ARRAY_HANDLE.getVolatile(loggers, i); - if (v == null) { - synchronized (this) { - v = loggers[i]; - if (v == null) { - ARRAY_HANDLE.setVolatile(loggers, i, v = Logger.getLogger("com.company.Foo" + i)); + logger = v = Logger.create("com.company.Application"); } } } @@ -161,91 +75,20 @@ public class Cache { } ``` -There is no built-in semantics for declaring an array's _elements_ should be accessed using `volatile` semantics in -Java and so, volatile access has to be made explicit in the user code, for example via the supported API of -`VarHandle`. This solution is also plagued with the problem that it synchronizes on `this` and, in addition to exposing -itself for deadlocks, prevents any of the cached values from being computed simultaneously by distinct threads. +The basic idea behind double-checked locking is to reduce the chances for callers to enter a `synchronized` block. After all, in the common case, we expect the `logger` field to already contain a logger object, in which case we can just return that object, without any performance hit. In the rare event where `logger` is not set, we must enter a `synchronized` block, and check its value again (as such value might have changed upon entering the block). For the double-checked idiom to work correctly, it is necessary for the `logger` field is marked as `volatile`. This ensures that reading that field across multiple threads can result in one of two outcomes: the field either appears to be uninitialized (its value set to `null`), or initialized (its value set to the final logger object). That is, no *dirty reads* are possible. -While some of the problems described above might be solved (e.g. by synchronizing on internal mutex fields) they -become increasingly complex, error-prone, and hard to maintain. +#### Problems with double-checked locking -A more fundamental problem with all the solutions above is that access to the cached values cannot be adequately -optimized by just-in-time compilers, as they cannot reliably assume that field values will, in fact, change at most -once. Alas, the solutions do not express the intent of the programmer; neither to just-in-time compilers nor to -readers of the code. +Unfortunately, double-checked locking has several inherent design flaws: -What we are missing -- in all cases -- is a *safe* way to *promise* that a constant will be initialized by the -time it is used, with a value that is computed at most once. Such a mechanism would give the Java runtime maximum -opportunity to stage and optimize its computation, thus avoiding the penalties (static footprint, loss of runtime -optimizations, and brittleness) that plague the workarounds shown above and below. Moreover, such a mechanism -should gracefully scale to handle collections of constant values, while retaining efficient computer resource -management. +* *brittleness* - the convoluted nature of the code required to write a correct double-checked locking makes it all too easy for developers to make subtle mistakes. A very common one is forgetting to add the `volatile` keyword to the `logger` field. +* *lack of expressiveness* - even when written correctly, the double-checked idiom leaves a lot to be desired. The "at-most-once" mutation guarantee is not explicitly manifest in the code: after all the `logger` field is just a plain mutable field. This leaves important semantics gaps that impossible to plug. For example, the `logger` field can be accidentally mutated in another method of the `Application` class. In another example, the field might be reflectively mutated using `setAccessible`. Avoiding these pitfalls is ultimately left to developers. +* *lack of optimizations* - as the `logger` field is updated at most once, one might expect the JVM to optimize access to this field accordingly, e.g. by [constant-folding](https://en.wikipedia.org/wiki/Constant_folding) access to an already-initialized `logger` field. Unfortunately, since `logger` is just a plan mutable field, the JVM cannot trust the field to never be updated again. As such, access to at-most-once fields, when realized with double-checked locking is not as efficient as it could be. +* *limited applicability* - double-checked locking fails to scale to more complex use cases where e.g. the client might need an *array* of values where each element can be updated at most once. In this case, marking the array field as `volatile` is not enough, as the `volatile` modifier doesn't apply to the array *elements* but to the array as a whole. Instead, clients would have to resort to an even more complex solutions using wher at-most-once array elements are accessed using `VarHandles`. Needless to say, such solutions are even more brittle and error prone, and should be avoided at all costs. -It would be advantageous if "compute-at-most-once fields" could be expresses something along these lines where -`Foo`, `Bar`, and `Baz` indicates some Java class and the methods `Foo::xxxx`, `Bar::yyyy`, `Baz::zzzz` represents -some method with a yet-to-determine name: +#### Thread safety with class initialization -``` -// Declare a cache that can hold a single Logger instance -Foo cache = ... Logger.getLogger("com.company.Foo") ...; -... -// Just returns the value if set. Otherwise computes, sets, and returns the value. -// If another thread is computing a value, wait until it has completed. -Logger logger = cache.xxxx(); -``` - -and for several compute-at-most-once values indexed by an `int`: - -``` -// Declare a cache that can hold 10 Logger instance -Bar cache = ... 10 ... i -> Logger.getLogger("com.company.Foo" + i) ... -... -// Just returns the value at the provided index if set. Otherwise computes, sets, and returns the value. -// If another thread is computing a value at the provided index, wait until it has completed. -// Values can be computed in parallel by distinct threads. -Logger logger = cache.yyyy(7); -``` - -and even for several compute-at-most-once values associated with some key of arbitrary type `K` (where we use Strings -as the key type in the example below): - -``` -// Declare a cache that can hold a finite number of Logger instance -// associated with the same finite number of `keys` Strings. -Baz cache = ... keys ... Logger::getLogger... -... -// Just returns the value associated with the provided string if set. Otherwise computes, sets, and returns the value. -// If another thread is computing a value for the provided String, wait until it has completed. -// Values can be computed in parallel by distinct threads. -Logger logger = cache.zzzz("com.company.Foo"); -``` - -For interoperability with legacy code, it would also be desirable if some of the standard collection types could be -expressed as compute-at-most-once constructs in a similar fashion: - -``` -// Declare a List whose elements are lazily computed upon being first accessed via List::get -List lazyList = ... 10 ... i -> Logger.getLogger("com.company.Foo" + i) ... - -// Declare a Map whose values are lazily computed upon being first accessed via Map::get -Map lazyMap = ... keys ... Logger::getLogger... -``` - -A final note should be made about static fields. Initialization of `static` and `final` fields can be broken up by -leveraging the laziness already built into class loading. Often referred to as the -[*initialization-on-demand_holder_idiom*](https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom), this -technique moves lazily initialized state into a helper class which is then loaded on-demand, so its initialization is -only performed when the data is actually needed, rather than unconditionally initializing constants when a class is -first referenced: - -``` -// Ordinary static initialization -private static final Logger LOGGER = Logger.getLogger("com.company.Foo"); -... -LOGGER.log(Level.DEBUG, ...); -``` - -we can defer initialization until we actually need it, like so: +Initialization of `static` and `final` fields can be broken up by leveraging the laziness already built into class loading. Often referred to as the [*initialization-on-demand_holder_idiom*](https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom), this technique moves lazily initialized state into a helper class which is then loaded on-demand, so its initialization is only performed when the data is actually needed, rather than unconditionally initializing constants when a class is first referenced: ``` // Initialization-on-demand holder idiom @@ -259,12 +102,22 @@ Logger logger() { logger().log(Level.DEBUG, ...); ``` -The code above ensures that the `Logger` object is created only when actually required. The (possibly expensive) -initializer for the logger lives in the nested `Holder` class, which will only be initialized when the `logger` -method accesses the `LOGGER` field. While this idiom works well, its reliance on the class loading process comes -with significant drawbacks. First, each constant whose computation needs to be deferred generally requires its own -holder class, thus introducing a significant static footprint cost. Second, this idiom is only really applicable -if the field initialization is suitably isolated, not relying on any other parts of the object state. +The code above ensures that the `Logger` object is created only when actually required. The (possibly expensive) initializer for the logger lives in the nested `Holder` class, which will only be initialized when the `logger` method accesses the `LOGGER` field. While this idiom works well, its reliance on the class loading process comes with significant drawbacks. First, instance fields cannot be modeled, only static fields. Second, each constant whose computation needs to be deferred generally requires its own holder class, thus introducing a significant static footprint cost. Third, this idiom is only really applicable if the field initialization is suitably isolated, not relying on any other parts of the object state. + +### At-most-once as a first class concept + +What we are missing -- in all cases -- is a way to *promise* that a variable will be initialized by the time it is used, with a value that is computed at most once, and *safely* across multiple threads. Such a mechanism would give the Java runtime maximum opportunity to stage and optimize its computation, thus avoiding the penalties that plague the workarounds shown above. Moreover, such a mechanism should gracefully scale to handle *collections* of "at-most-once" variables, while retaining efficient computer resource management: + +| Storage kind | #Updates | Code update location | Constant folding | Concurrent updates | +|-----------------|----------|-----------------------------------|-------------------|-------------------------| +| non-`final` | [0, ∞) | Anywhere | no | yes | +| `final` | 1 | Constructor or static initializer | yes | no [1] | +| "at-most-once" | [0, 1] | Anywhere | yes, after update | yes, but only one "win" | + +[1] Thread-safe initialization of instance and `static final` fields is covered in JLS 17.5 and JLS 5.5 respectively. + +_Table 1, showing properties of mutable, immutable, and at-most-once (currently not available) fields._ + ## Description diff --git a/test/micro/org/openjdk/bench/java/lang/stable/CustomClassBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/CustomClassBenchmark.java new file mode 100644 index 0000000000000..4051bf81f62f0 --- /dev/null +++ b/test/micro/org/openjdk/bench/java/lang/stable/CustomClassBenchmark.java @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2024, 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 org.openjdk.jmh.annotations.*; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; + +/** + * 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, jvmArgsAppend = { + "--enable-preview", + // Prevent the use of uncommon traps + "-XX:PerMethodTrapLimit=0"}) +@Threads(Threads.MAX) // Benchmark under contention +@OperationsPerInvocation(2) +public class StableValueBenchmark { + + private static final int VALUE = 42; + private static final int VALUE2 = 23; + + private static final StableValue STABLE = init(StableValue.newInstance(), VALUE); + private static final StableValue STABLE2 = init(StableValue.newInstance(), VALUE2); + private static final StableValue DCL = init(StableValue.newInstance(), VALUE); + private static final StableValue DCL2 = init(StableValue.newInstance(), VALUE2); + private static final AtomicReference ATOMIC = new AtomicReference<>(VALUE); + private static final AtomicReference ATOMIC2 = new AtomicReference<>(VALUE2); + private static final Holder HOLDER = new Holder(VALUE); + private static final Holder HOLDER2 = new Holder(VALUE2); + + private final StableValue stable = init(StableValue.newInstance(), VALUE); + private final StableValue stable2 = init(StableValue.newInstance(), VALUE2); + private final StableValue stableNull = StableValue.newInstance(); + private final StableValue stableNull2 = StableValue.newInstance(); + private final Supplier dcl = new Dcl<>(() -> VALUE); + private final Supplier dcl2 = new Dcl<>(() -> VALUE2); + private final AtomicReference atomic = new AtomicReference<>(VALUE); + private final AtomicReference atomic2 = new AtomicReference<>(VALUE2); + private final Supplier supplier = () -> VALUE; + private final Supplier supplier2 = () -> VALUE2; + + + @Setup + public void setup() { + stableNull.trySet(null); + stableNull2.trySet(VALUE2); + // Create pollution + int sum = 0; + for (int i = 0; i < 500_000; i++) { + final int v = i; + Dcl dclX = new Dcl<>(() -> v); + sum += dclX.get(); + StableValue stableX = StableValue.newInstance(); + stableX.trySet(i); + sum += stableX.orElseThrow(); + } + System.out.println("sum = " + sum); + } + + @Benchmark + public int atomic() { + return atomic.get() + atomic2.get(); + } + + @Benchmark + public int dcl() { + return dcl.get() + dcl2.get(); + } + + @Benchmark + public int stable() { + return stable.orElseThrow() + stable2.orElseThrow(); + } + + @Benchmark + public int stableNull() { + return (stableNull.orElseThrow() == null ? VALUE : VALUE2) + (stableNull2.orElseThrow() == null ? VALUE : VALUE2); + } + + // Reference case + @Benchmark + public int refSupplier() { + return supplier.get() + supplier2.get(); + } + + @Benchmark + public int staticAtomic() { + return ATOMIC.get() + ATOMIC2.get(); + } + + @Benchmark + public int staticDcl() { + return DCL.orElseThrow() + DCL2.orElseThrow(); + } + + @Benchmark + public int staticHolder() { + return HOLDER.get() + HOLDER2.get(); + } + + @Benchmark + public int staticStable() { + return STABLE.orElseThrow() + STABLE2.orElseThrow(); + } + + + private static StableValue init(StableValue m, Integer value) { + m.trySet(value); + return m; + } + + // The VM should be able to constant-fold the value given in the constructor + // because StableValue fields have a special meaning. + private static final class Holder { + + private final StableValue delegate = StableValue.newInstance(); + + Holder(int value) { + delegate.setOrThrow(value); + } + + int get() { + return delegate.orElseThrow(); + } + + } + + // Handles null values + private static class Dcl implements Supplier { + + private final Supplier supplier; + + private volatile V value; + private boolean bound; + + public Dcl(Supplier supplier) { + this.supplier = supplier; + } + + @Override + public V get() { + V v = value; + if (v == null) { + if (!bound) { + synchronized (this) { + v = value; + if (v == null) { + if (!bound) { + value = v = supplier.get(); + bound = true; + } + } + } + } + } + return v; + } + } + +} From 5eaa51a16f27298d6cd25f4ca57710dd0971240c Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Fri, 2 Aug 2024 15:43:04 +0200 Subject: [PATCH 096/327] Fix typos --- test/jdk/java/lang/StableValue/JEP.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/test/jdk/java/lang/StableValue/JEP.md b/test/jdk/java/lang/StableValue/JEP.md index c772be9f01bd0..09ba93c4ffb0c 100644 --- a/test/jdk/java/lang/StableValue/JEP.md +++ b/test/jdk/java/lang/StableValue/JEP.md @@ -23,7 +23,9 @@ This might be the subject of a future JEP. Java allows developers to control whether fields should be mutable or not. Mutable fields can be updated multiple times, and from any arbitrary position in the code and by any thread. As such, mutable fields are often used to model complex objects whose state can be updated several times throughout their lifetimes, such as the contents of a text field in a UI component. Conversely, immutable fields (i.e. `final` fields), must be updated *exactly once*, and only in very specific places: the class initializer (for a static immutable field) or the class constructor(for an instance immutable field). As such, `final` fields are typically used to model values that act as *constants* (albeit shallowly so) throughout the lifetime of a class (in the case of `static` fields) or of an instance (in the case of an instance field). -Most of the time deciding whether an object should feature mutable or immutable state is straightforward enough. There are however cases where a field is subject to *constrained mutation*. That is, field's value are neither constant, nor can they it mutated at will. Consider a program that might want to mutate a password field at most three times before it becomes immutable, in order to reflect the three login attempts allowed for a user; further mutation would result in some exception. Expressing this kind of constrained mutation is hard, and cannot be achieved without the help of advanced type-system [calculi](https://en.wikipedia.org/wiki/Dependent_type). However, one important and simpler case of constrained mutation is that of a field whose updated *at most once*. As we shall see, the lack of a mechanism to capture this specific kind of constrained mutation in the Java platform comes at a considerable cost of performance and expressiveness. +Most of the time deciding whether an object should feature mutable or immutable state is straightforward enough. However, there are cases where a field is subject to *constrained mutation*. That is, a field's value is neither constant, nor can can be mutated at will. Consider a program that might want to mutate a password field at most three times before it becomes immutable, in order to reflect the three login attempts allowed for a user; further mutation would result in some kind of exception. Expressing this kind of constrained mutation is hard, and cannot be achieved without the help of advanced type-system [calculi](https://en.wikipedia.org/wiki/Dependent_type). + +However, one important and simpler case of constrained mutation is that of a field whose updated *at most once*. As we shall see, the lack of a mechanism to capture this specific kind of constrained mutation in the Java platform comes at a considerable cost of performance and expressiveness. ### An example: memoization @@ -47,7 +49,7 @@ public class Application { As the `logger` field is private, the only way for clients to access it is to call the `getLogger` method. This method first tests whether a logger is already available, and if so that logger is returned. Otherwise, it proceeds to the creation of a *new* logger object, which is then stored in the `logger` field. In this way, we guarantee that the logger object is created at most once: the first time the `Application::getLogger` method is invoked. -Unfortunately, the above solution does not work in a multithreaded environment. For instance, updates to the `logger` field made by one thread may not be immediately visible to other threads. This condition might result in multiple concurrent calls to the `Logger::create` method, thereby violating the "at-most-once" update guarantee. +Unfortunately, the above solution does not work in a multithreaded environment. For instance, updates to the `logger` field made by one thread may not be immediately visible to other threads. This condition might result in multiple concurrent calls to the `Logger::create` method, thereby violating the "at-most-once" update guarantee. #### Thread safety with double-checked locking @@ -75,16 +77,16 @@ class Application { } ``` -The basic idea behind double-checked locking is to reduce the chances for callers to enter a `synchronized` block. After all, in the common case, we expect the `logger` field to already contain a logger object, in which case we can just return that object, without any performance hit. In the rare event where `logger` is not set, we must enter a `synchronized` block, and check its value again (as such value might have changed upon entering the block). For the double-checked idiom to work correctly, it is necessary for the `logger` field is marked as `volatile`. This ensures that reading that field across multiple threads can result in one of two outcomes: the field either appears to be uninitialized (its value set to `null`), or initialized (its value set to the final logger object). That is, no *dirty reads* are possible. +The basic idea behind double-checked locking is to reduce the chances for callers to enter a `synchronized` block. After all, in the common case, we expect the `logger` field to already contain a logger object, in which case we can just return that object, without any performance hit. In the rare event where `logger` is not set, we must enter a `synchronized` block, and check its value again (as such value might have changed upon entering the block). For the double-checked idiom to work correctly, it is necessary for the `logger` field is marked as `volatile`. This ensures that reading that field across multiple threads can result in one of two outcomes: the field either appears to be uninitialized (its value set to `null`), or the returned Logger is fully initialized (all its fields set properly). That is, no *partial reads* are possible. #### Problems with double-checked locking Unfortunately, double-checked locking has several inherent design flaws: * *brittleness* - the convoluted nature of the code required to write a correct double-checked locking makes it all too easy for developers to make subtle mistakes. A very common one is forgetting to add the `volatile` keyword to the `logger` field. -* *lack of expressiveness* - even when written correctly, the double-checked idiom leaves a lot to be desired. The "at-most-once" mutation guarantee is not explicitly manifest in the code: after all the `logger` field is just a plain mutable field. This leaves important semantics gaps that impossible to plug. For example, the `logger` field can be accidentally mutated in another method of the `Application` class. In another example, the field might be reflectively mutated using `setAccessible`. Avoiding these pitfalls is ultimately left to developers. +* *lack of expressiveness* - even when written correctly, the double-checked idiom leaves a lot to be desired. The "at-most-once" mutation guarantee is not explicitly manifest in the code: after all the `logger` field is just a plain mutable field. This leaves important semantics gaps that are impossible to plug. For example, the `logger` field can be accidentally mutated in another method of the `Application` class. In another example, the field might be reflectively mutated using `setAccessible`. Avoiding these pitfalls is ultimately left to developers. * *lack of optimizations* - as the `logger` field is updated at most once, one might expect the JVM to optimize access to this field accordingly, e.g. by [constant-folding](https://en.wikipedia.org/wiki/Constant_folding) access to an already-initialized `logger` field. Unfortunately, since `logger` is just a plan mutable field, the JVM cannot trust the field to never be updated again. As such, access to at-most-once fields, when realized with double-checked locking is not as efficient as it could be. -* *limited applicability* - double-checked locking fails to scale to more complex use cases where e.g. the client might need an *array* of values where each element can be updated at most once. In this case, marking the array field as `volatile` is not enough, as the `volatile` modifier doesn't apply to the array *elements* but to the array as a whole. Instead, clients would have to resort to an even more complex solutions using wher at-most-once array elements are accessed using `VarHandles`. Needless to say, such solutions are even more brittle and error prone, and should be avoided at all costs. +* *limited applicability* - double-checked locking fails to scale to more complex use cases where e.g. the client might need an *array* of values where each element can be updated at most once. In this case, marking the array field as `volatile` is not enough, as the `volatile` modifier doesn't apply to the array *elements* but to the array as a whole. Instead, clients would have to resort to an even more complex solutions using where at-most-once array elements are accessed using `VarHandles`. Needless to say, such solutions are even more brittle and error-prone, and should be avoided at all costs. #### Thread safety with class initialization @@ -114,7 +116,7 @@ What we are missing -- in all cases -- is a way to *promise* that a variable wil | `final` | 1 | Constructor or static initializer | yes | no [1] | | "at-most-once" | [0, 1] | Anywhere | yes, after update | yes, but only one "win" | -[1] Thread-safe initialization of instance and `static final` fields is covered in JLS 17.5 and JLS 5.5 respectively. +[1] Thread-safe initialization of instance and `static final` fields is covered in JLS 17.5. _Table 1, showing properties of mutable, immutable, and at-most-once (currently not available) fields._ From d12fcc1da00e4d19fd9d8665c158111d85b9e14f Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Fri, 2 Aug 2024 15:56:12 +0200 Subject: [PATCH 097/327] Update JEP --- test/jdk/java/lang/StableValue/JEP.md | 71 ++++++++++----------------- 1 file changed, 27 insertions(+), 44 deletions(-) diff --git a/test/jdk/java/lang/StableValue/JEP.md b/test/jdk/java/lang/StableValue/JEP.md index 09ba93c4ffb0c..85d0a696193fc 100644 --- a/test/jdk/java/lang/StableValue/JEP.md +++ b/test/jdk/java/lang/StableValue/JEP.md @@ -21,13 +21,11 @@ This might be the subject of a future JEP. ## Motivation -Java allows developers to control whether fields should be mutable or not. Mutable fields can be updated multiple times, and from any arbitrary position in the code and by any thread. As such, mutable fields are often used to model complex objects whose state can be updated several times throughout their lifetimes, such as the contents of a text field in a UI component. Conversely, immutable fields (i.e. `final` fields), must be updated *exactly once*, and only in very specific places: the class initializer (for a static immutable field) or the class constructor(for an instance immutable field). As such, `final` fields are typically used to model values that act as *constants* (albeit shallowly so) throughout the lifetime of a class (in the case of `static` fields) or of an instance (in the case of an instance field). +Java allows developers to control whether fields should be mutable or not. Mutable fields can be updated multiple times, and from any arbitrary position in the code and by any thread. As such, mutable fields are often used to model complex objects whose state can be updated several times throughout their lifetimes, such as the contents of a text field in a UI component. Conversely, immutable fields (i.e. `final` fields), must be updated exactly *once*, and only in very specific places: the class initializer (for a static immutable field) or the class constructor(for an instance immutable field). As such, `final` fields are typically used to model values that act as *constants* (albeit shallowly so) throughout the lifetime of a class (in the case of `static` fields) or of an instance (in the case of an instance field). -Most of the time deciding whether an object should feature mutable or immutable state is straightforward enough. However, there are cases where a field is subject to *constrained mutation*. That is, a field's value is neither constant, nor can can be mutated at will. Consider a program that might want to mutate a password field at most three times before it becomes immutable, in order to reflect the three login attempts allowed for a user; further mutation would result in some kind of exception. Expressing this kind of constrained mutation is hard, and cannot be achieved without the help of advanced type-system [calculi](https://en.wikipedia.org/wiki/Dependent_type). +Most of the time deciding whether an object should feature mutable or immutable state is straightforward enough. There are however cases where a field is subject to *constrained mutation*. That is, a field's value is neither constant, nor can it be mutated at will. Consider a program that might want to mutate a password field at most three times before it becomes immutable, in order to reflect the three login attempts allowed for a user; further mutation would result in some kind of exception. Expressing this kind of constrained mutation is hard, and cannot be achieved without the help of advanced type-system [calculi](https://en.wikipedia.org/wiki/Dependent_type). However, one important and simpler case of constrained mutation is that of a field whose updated *at most once*. As we shall see, the lack of a mechanism to capture this specific kind of constrained mutation in the Java platform comes at a considerable cost of performance and expressiveness. -However, one important and simpler case of constrained mutation is that of a field whose updated *at most once*. As we shall see, the lack of a mechanism to capture this specific kind of constrained mutation in the Java platform comes at a considerable cost of performance and expressiveness. - -### An example: memoization +#### An example: memoization Constrained mutation is essential to reliably cache the result of an expensive method call, so that it can be reused several times throughout the lifetime of an application (this technique is also known as [memoization](https://en.wikipedia.org/wiki/Memoization)). A nearly ubiquitous example of such an expensive method call is that to obtain a logger object through which an application's events can be reported. Obtaining a logger often entails expensive operations, such as reading and parsing configuration data, or prepare the backing storage where logging events will be recorded. Since these operations are expensive, an application will typically want to move them as much *forward in time* as possible: after all, an application might never need to log an event, so why paying the cost for this expensive initialization? Moreover, as some of these operation results in side effects - such as the creation of files and folders - it is crucial that they are executed _at most once_. @@ -36,14 +34,14 @@ Combining mutable fields and encapsulation is a common way to approximate at-mos ``` public class Application { - private Logger logger; + private Logger logger; - public Logger getLogger() { - if (logger == null) { - logger = Logger.create("com.company.Application"); - } - return logger; + public Logger getLogger() { + if (logger == null) { + logger = Logger.create("com.company.Application"); } + return logger; + } } ``` @@ -51,9 +49,11 @@ As the `logger` field is private, the only way for clients to access it is to ca Unfortunately, the above solution does not work in a multithreaded environment. For instance, updates to the `logger` field made by one thread may not be immediately visible to other threads. This condition might result in multiple concurrent calls to the `Logger::create` method, thereby violating the "at-most-once" update guarantee. -#### Thread safety with double-checked locking +##### Thread-safety with double-checked locking + +One possible way to achieve thread safety would be to serialize access to the `Application::getLogger` method - i.e. by marking that method as `synchronized`. However, doing so has a performance cost, as multiple threads cannot concurrently obtain the application's logger object, even *long after* this object has been computed, and safely stored in the `logger` field. In other words, using `synchronized` amounts at applying a *permanent* performance tax on *all* logger accesses, in the rare event that a race occurs during the initial update of the `logger` field. -One possible way to achieve thread-safety would be to serialize access to the `Application::getLogger` method - i.e. by marking that method as `synchronized`. However, doing so has a performance cost, as multiple threads cannot concurrently obtain the application's logger object, even *long after* this object has been computed, and safely stored in the `logger` field. In other words, using `synchronized` amounts at applying a *permanent* performance tax on *all* logger accesses, in the rare event that a race occurs during the initial update of the `logger` field. +A more efficient solution is the so-called [class holder idiom](https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom), which achieves thread-safe "at-most-once" update guarantees by leaning on the lazy semantics of class initialization. However, for this approach to work correctly, the memoized data should be `static`, which is not the case here. In order to achieve thread-safety without compromising performance, developers often resorts to the brittle [double-checked idiom](https://en.wikipedia.org/wiki/Double-checked_locking): @@ -77,49 +77,32 @@ class Application { } ``` -The basic idea behind double-checked locking is to reduce the chances for callers to enter a `synchronized` block. After all, in the common case, we expect the `logger` field to already contain a logger object, in which case we can just return that object, without any performance hit. In the rare event where `logger` is not set, we must enter a `synchronized` block, and check its value again (as such value might have changed upon entering the block). For the double-checked idiom to work correctly, it is necessary for the `logger` field is marked as `volatile`. This ensures that reading that field across multiple threads can result in one of two outcomes: the field either appears to be uninitialized (its value set to `null`), or the returned Logger is fully initialized (all its fields set properly). That is, no *partial reads* are possible. +The basic idea behind double-checked locking is to reduce the chances for callers to enter a `synchronized` block. After all, in the common case, we expect the `logger` field to already contain a logger object, in which case we can just return that object, without any performance hit. In the rare event where `logger` is not set, we must enter a `synchronized` block, and check its value again (as such value might have changed upon entering the block). For the double-checked idiom to work correctly, it is necessary for the `logger` field is marked as `volatile`. This ensures that reading that field across multiple threads can result in one of two outcomes: the field either appears to be uninitialized (its value set to `null`), or initialized (its value set to the final logger object). That is, no *dirty reads* are possible. -#### Problems with double-checked locking +##### Problems with double-checked locking Unfortunately, double-checked locking has several inherent design flaws: * *brittleness* - the convoluted nature of the code required to write a correct double-checked locking makes it all too easy for developers to make subtle mistakes. A very common one is forgetting to add the `volatile` keyword to the `logger` field. -* *lack of expressiveness* - even when written correctly, the double-checked idiom leaves a lot to be desired. The "at-most-once" mutation guarantee is not explicitly manifest in the code: after all the `logger` field is just a plain mutable field. This leaves important semantics gaps that are impossible to plug. For example, the `logger` field can be accidentally mutated in another method of the `Application` class. In another example, the field might be reflectively mutated using `setAccessible`. Avoiding these pitfalls is ultimately left to developers. +* *lack of expressiveness* - even when written correctly, the double-checked idiom leaves a lot to be desired. The "at-most-once" mutation guarantee is not explicitly manifest in the code: after all the `logger` field is just a plain mutable field. This leaves important semantics gaps that is impossible to plug. For example, the `logger` field can be accidentally mutated in another method of the `Application` class. In another example, the field might be reflectively mutated using `setAccessible`. Avoiding these pitfalls is ultimately left to developers. * *lack of optimizations* - as the `logger` field is updated at most once, one might expect the JVM to optimize access to this field accordingly, e.g. by [constant-folding](https://en.wikipedia.org/wiki/Constant_folding) access to an already-initialized `logger` field. Unfortunately, since `logger` is just a plan mutable field, the JVM cannot trust the field to never be updated again. As such, access to at-most-once fields, when realized with double-checked locking is not as efficient as it could be. -* *limited applicability* - double-checked locking fails to scale to more complex use cases where e.g. the client might need an *array* of values where each element can be updated at most once. In this case, marking the array field as `volatile` is not enough, as the `volatile` modifier doesn't apply to the array *elements* but to the array as a whole. Instead, clients would have to resort to an even more complex solutions using where at-most-once array elements are accessed using `VarHandles`. Needless to say, such solutions are even more brittle and error-prone, and should be avoided at all costs. - -#### Thread safety with class initialization - -Initialization of `static` and `final` fields can be broken up by leveraging the laziness already built into class loading. Often referred to as the [*initialization-on-demand_holder_idiom*](https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom), this technique moves lazily initialized state into a helper class which is then loaded on-demand, so its initialization is only performed when the data is actually needed, rather than unconditionally initializing constants when a class is first referenced: - -``` -// Initialization-on-demand holder idiom -Logger logger() { - class Holder { - static final Logger LOGGER = Logger.getLogger("com.company.Foo"); - } - return Holder.LOGGER; -} -... -logger().log(Level.DEBUG, ...); -``` - -The code above ensures that the `Logger` object is created only when actually required. The (possibly expensive) initializer for the logger lives in the nested `Holder` class, which will only be initialized when the `logger` method accesses the `LOGGER` field. While this idiom works well, its reliance on the class loading process comes with significant drawbacks. First, instance fields cannot be modeled, only static fields. Second, each constant whose computation needs to be deferred generally requires its own holder class, thus introducing a significant static footprint cost. Third, this idiom is only really applicable if the field initialization is suitably isolated, not relying on any other parts of the object state. +* *limited applicability* - double-checked locking fails to scale to more complex use cases where e.g. the client might need an *array* of values where each element can be updated at most once. In this case, marking the array field as `volatile` is not enough, as the `volatile` modifier doesn't apply to the array *elements* but to the array as a whole. Instead, clients would have to resort to even more complex solutions using where at-most-once array elements are accessed using `VarHandles`. Needless to say, such solutions are even more brittle and error-prone, and should be avoided at all costs. -### At-most-once as a first class concept +##### At-most-once as a first class concept -What we are missing -- in all cases -- is a way to *promise* that a variable will be initialized by the time it is used, with a value that is computed at most once, and *safely* across multiple threads. Such a mechanism would give the Java runtime maximum opportunity to stage and optimize its computation, thus avoiding the penalties that plague the workarounds shown above. Moreover, such a mechanism should gracefully scale to handle *collections* of "at-most-once" variables, while retaining efficient computer resource management: +At-most-once semantics is unquestionably critical to implement important use cases such as caches and memoized functions. Unfortunately, existing workarounds, such as double-checked locking, cannot be considered adequate replacements for *first-class* "at-most-once" support. What we are missing is a way to *promise* that a variable will be initialized by the time it is used, with a value that is computed at most once, and *safely* across multiple threads. Such a mechanism would give the Java runtime maximum opportunity to stage and optimize its computation, thus avoiding the penalties that plague the workarounds shown above. Moreover, such a mechanism should gracefully scale to handle *collections* of "at-most-once" variables, while retaining efficient computer resource management. -| Storage kind | #Updates | Code update location | Constant folding | Concurrent updates | -|-----------------|----------|-----------------------------------|-------------------|-------------------------| -| non-`final` | [0, ∞) | Anywhere | no | yes | -| `final` | 1 | Constructor or static initializer | yes | no [1] | -| "at-most-once" | [0, 1] | Anywhere | yes, after update | yes, but only one "win" | +When fully realized, first-class support for "at-most-once" sematics would fill an important gap between mutable and immutable fields, as shown in the table below: -[1] Thread-safe initialization of instance and `static final` fields is covered in JLS 17.5. -_Table 1, showing properties of mutable, immutable, and at-most-once (currently not available) fields._ +| Storage kind | #Updates | Code update location | Constant folding | Concurrent updates | +| -------------- | -------- | --------------------------------- | ----------------- | ------------------------ | +| non-`final` | [0, ∞) | Anywhere | no | yes | +| `final` | 1[^1] | Constructor or static initializer | yes | no[^1] | +| "at-most-once" | [0, 1] | Anywhere | yes, after update | yes, but only one "wins" | +[^1] See [JLS 17.5](https://docs.oracle.com/javase/specs/jls/se11/html/jls-17.html#jls-17.5) +_Table 1: properties of mutable, immutable, and at-most-once variables_ ## Description From e79eb01fdd35de55b0cb2820d0e41b2ffa1ce9db Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Fri, 2 Aug 2024 15:56:58 +0200 Subject: [PATCH 098/327] Fix typo in JEP --- test/jdk/java/lang/StableValue/JEP.md | 1 + 1 file changed, 1 insertion(+) diff --git a/test/jdk/java/lang/StableValue/JEP.md b/test/jdk/java/lang/StableValue/JEP.md index 85d0a696193fc..7156d27faf7eb 100644 --- a/test/jdk/java/lang/StableValue/JEP.md +++ b/test/jdk/java/lang/StableValue/JEP.md @@ -100,6 +100,7 @@ When fully realized, first-class support for "at-most-once" sematics would fill | non-`final` | [0, ∞) | Anywhere | no | yes | | `final` | 1[^1] | Constructor or static initializer | yes | no[^1] | | "at-most-once" | [0, 1] | Anywhere | yes, after update | yes, but only one "wins" | + [^1] See [JLS 17.5](https://docs.oracle.com/javase/specs/jls/se11/html/jls-17.html#jls-17.5) _Table 1: properties of mutable, immutable, and at-most-once variables_ From bf000bb15db4fd94118a9906d4f84f814a9e71a3 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Fri, 2 Aug 2024 15:59:42 +0200 Subject: [PATCH 099/327] Try to fix a reference in the JEP --- test/jdk/java/lang/StableValue/JEP.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/jdk/java/lang/StableValue/JEP.md b/test/jdk/java/lang/StableValue/JEP.md index 7156d27faf7eb..4a821156dd5bc 100644 --- a/test/jdk/java/lang/StableValue/JEP.md +++ b/test/jdk/java/lang/StableValue/JEP.md @@ -96,12 +96,12 @@ When fully realized, first-class support for "at-most-once" sematics would fill | Storage kind | #Updates | Code update location | Constant folding | Concurrent updates | -| -------------- | -------- | --------------------------------- | ----------------- | ------------------------ | +| -------------- |----------| --------------------------------- | ----------------- |--------------------------| | non-`final` | [0, ∞) | Anywhere | no | yes | -| `final` | 1[^1] | Constructor or static initializer | yes | no[^1] | +| `final` | 1 [1] | Constructor or static initializer | yes | no [1] | | "at-most-once" | [0, 1] | Anywhere | yes, after update | yes, but only one "wins" | -[^1] See [JLS 17.5](https://docs.oracle.com/javase/specs/jls/se11/html/jls-17.html#jls-17.5) +[1] See [JLS 17.5](https://docs.oracle.com/javase/specs/jls/se11/html/jls-17.html#jls-17.5) _Table 1: properties of mutable, immutable, and at-most-once variables_ From 56d0a24e15299da5396aba479eec1128977e92f7 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Fri, 2 Aug 2024 16:04:53 +0200 Subject: [PATCH 100/327] Try to fix a reference in the JEP again --- test/jdk/java/lang/StableValue/JEP.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/jdk/java/lang/StableValue/JEP.md b/test/jdk/java/lang/StableValue/JEP.md index 4a821156dd5bc..1efc7b2310456 100644 --- a/test/jdk/java/lang/StableValue/JEP.md +++ b/test/jdk/java/lang/StableValue/JEP.md @@ -101,7 +101,7 @@ When fully realized, first-class support for "at-most-once" sematics would fill | `final` | 1 [1] | Constructor or static initializer | yes | no [1] | | "at-most-once" | [0, 1] | Anywhere | yes, after update | yes, but only one "wins" | -[1] See [JLS 17.5](https://docs.oracle.com/javase/specs/jls/se11/html/jls-17.html#jls-17.5) +[1]: https://docs.oracle.com/javase/specs/jls/se11/html/jls-17.html#jls-17.5 (JSL 17.5) _Table 1: properties of mutable, immutable, and at-most-once variables_ From 8652a44eea05c50042d3b839353d5906468ba4ca Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Fri, 2 Aug 2024 17:07:24 +0200 Subject: [PATCH 101/327] Fix line break in JEP --- test/jdk/java/lang/StableValue/JEP.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/jdk/java/lang/StableValue/JEP.md b/test/jdk/java/lang/StableValue/JEP.md index 1efc7b2310456..ddc99882bd945 100644 --- a/test/jdk/java/lang/StableValue/JEP.md +++ b/test/jdk/java/lang/StableValue/JEP.md @@ -23,7 +23,9 @@ This might be the subject of a future JEP. Java allows developers to control whether fields should be mutable or not. Mutable fields can be updated multiple times, and from any arbitrary position in the code and by any thread. As such, mutable fields are often used to model complex objects whose state can be updated several times throughout their lifetimes, such as the contents of a text field in a UI component. Conversely, immutable fields (i.e. `final` fields), must be updated exactly *once*, and only in very specific places: the class initializer (for a static immutable field) or the class constructor(for an instance immutable field). As such, `final` fields are typically used to model values that act as *constants* (albeit shallowly so) throughout the lifetime of a class (in the case of `static` fields) or of an instance (in the case of an instance field). -Most of the time deciding whether an object should feature mutable or immutable state is straightforward enough. There are however cases where a field is subject to *constrained mutation*. That is, a field's value is neither constant, nor can it be mutated at will. Consider a program that might want to mutate a password field at most three times before it becomes immutable, in order to reflect the three login attempts allowed for a user; further mutation would result in some kind of exception. Expressing this kind of constrained mutation is hard, and cannot be achieved without the help of advanced type-system [calculi](https://en.wikipedia.org/wiki/Dependent_type). However, one important and simpler case of constrained mutation is that of a field whose updated *at most once*. As we shall see, the lack of a mechanism to capture this specific kind of constrained mutation in the Java platform comes at a considerable cost of performance and expressiveness. +Most of the time deciding whether an object should feature mutable or immutable state is straightforward enough. There are however cases where a field is subject to *constrained mutation*. That is, a field's value is neither constant, nor can it be mutated at will. Consider a program that might want to mutate a password field at most three times before it becomes immutable, in order to reflect the three login attempts allowed for a user; further mutation would result in some kind of exception. Expressing this kind of constrained mutation is hard, and cannot be achieved without the help of advanced type-system [calculi](https://en.wikipedia.org/wiki/Dependent_type). + +However, one important and simpler case of constrained mutation is that of a field whose updated *at most once*. As we shall see, the lack of a mechanism to capture this specific kind of constrained mutation in the Java platform incurs a considerable cost of performance and expressiveness. #### An example: memoization From ce2c404c1063a4bb1ea4f8572a1fa05a0b2e1e7c Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 5 Aug 2024 09:27:55 +0200 Subject: [PATCH 102/327] Fix typo in JEP --- test/jdk/java/lang/StableValue/JEP.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/jdk/java/lang/StableValue/JEP.md b/test/jdk/java/lang/StableValue/JEP.md index ddc99882bd945..b785460992ae1 100644 --- a/test/jdk/java/lang/StableValue/JEP.md +++ b/test/jdk/java/lang/StableValue/JEP.md @@ -178,7 +178,7 @@ called *only once*. This brings us to the introduction of _cached functions_. ### Caching functions -So far, we have talked about the fundamental features of StableValue as s securely +So far, we have talked about the fundamental features of StableValue as a securely wrapped stable value holder. However, it has become apparent, stable primitives are amenable to composition with other constructs in order to create more high-level and powerful features. From b8156a00bac040d96ab0859837ffa6873e227451 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 5 Aug 2024 09:55:49 +0200 Subject: [PATCH 103/327] Fix issues from comments and add benchmark --- .../share/classes/java/lang/StableValue.java | 22 +- .../internal/lang/stable/CachingFunction.java | 4 +- .../internal/lang/stable/StableValueUtil.java | 2 +- test/jdk/java/lang/StableValue/JepTest.java | 33 +++ .../lang/stable/CustomClassBenchmark.java | 192 +++++++----------- 5 files changed, 120 insertions(+), 133 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 66d89182b0243..18767faf751f7 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -61,41 +61,41 @@ * involving stable values: *

    *
  • - * A cached (also called "memoized") Supplier, where a given {@code original} + * A caching (also called "memoized") Supplier, where a given {@code original} * Supplier is guaranteed to be successfully invoked at most once even in a multithreaded * environment, can be created like this: * {@snippet lang = java : - * Supplier cached = StableValue.newCachingSupplier(original, null); + * Supplier cache = StableValue.newCachingSupplier(original, null); * } - * The cached supplier can also be computed by a fresh background thread if a + * The caching supplier can also be computed by a fresh background thread if a * thread factory is provided as a second parameter as shown here: * {@snippet lang = java : - * Supplier cached = StableValue.newCachingSupplier(original, Thread.ofVirtual().factory()); + * Supplier cache = StableValue.newCachingSupplier(original, Thread.ofVirtual().factory()); * } *
  • * *
  • - * A cached (also called "memoized") IntFunction, for the allowed given {@code size} + * A caching (also called "memoized") IntFunction, for the allowed given {@code size} * input values {@code [0, size)} and where the given {@code original} IntFunction is * guaranteed to be successfully invoked at most once per inout index even in a * multithreaded environment, can be created like this: * {@snippet lang = java: - * IntFunction cached = StableValue.newCachingIntFunction(size, original, null); + * IntFunction cache = StableValue.newCachingIntFunction(size, original, null); *} - * Just like a cached supplier, a thread factory can be provided as a second parameter + * Just like a caching supplier, a thread factory can be provided as a second parameter * allowing all the values for the allowed input values to be computed by distinct * background threads. *
  • * *
  • - * A cached (also called "memoized") Function, for the given set of allowed {@code inputs} + * A caching (also called "memoized") Function, for the given set of allowed {@code inputs} * and where the given {@code original} function is guaranteed to be successfully invoked * at most once per input value even in a multithreaded environment, can be created like * this: * {@snippet lang = java : - * Function cached = StableValue.newCachingFunction(inputs, original, null); + * Function cache = StableValue.newCachingFunction(inputs, original, null); * } - * Just like a cached supplier, a thread factory can be provided as a second parameter + * Just like a caching supplier, a thread factory can be provided as a second parameter * allowing all the values for the allowed input values to be computed by distinct * background threads. *
  • @@ -329,7 +329,7 @@ static IntFunction newCachingIntFunction(int size, * @param the type of the input to the returned Function * @param the type of results delivered by the returned Function */ - static Function newCachingFunction(Set inputs, + static Function newCachingFunction(Set inputs, Function original, ThreadFactory factory) { Objects.requireNonNull(inputs); diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/CachingFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/CachingFunction.java index 92d634e5c724d..5b5a0ecf964e9 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/CachingFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/CachingFunction.java @@ -34,7 +34,7 @@ // Note: It would be possible to just use `LazyMap::get` with some additional logic // instead of this class but explicitly providing a class like this provides better // debug capability, exception handling, and may provide better performance. -public record CachingFunction(Map> values, +public record CachingFunction(Map> values, Function original) implements Function { @ForceInline @Override @@ -91,7 +91,7 @@ private String renderValues() { return sb.toString(); } - public static CachingFunction of(Set inputs, + public static CachingFunction of(Set inputs, Function original) { return new CachingFunction<>(StableValueUtil.ofMap(inputs), original); } diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableValueUtil.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableValueUtil.java index b16949aacc6f1..b595d1cbb50c8 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableValueUtil.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableValueUtil.java @@ -128,7 +128,7 @@ public static IntFunction newCachingIntFunction(int size, return memoized; } - public static Function newCachingFunction(Set inputs, + public static Function newCachingFunction(Set inputs, Function original, ThreadFactory factory) { diff --git a/test/jdk/java/lang/StableValue/JepTest.java b/test/jdk/java/lang/StableValue/JepTest.java index 7f4e1d91a8908..dd613c7042642 100644 --- a/test/jdk/java/lang/StableValue/JepTest.java +++ b/test/jdk/java/lang/StableValue/JepTest.java @@ -37,8 +37,10 @@ import java.util.Set; import java.util.function.Function; import java.util.function.IntFunction; +import java.util.function.Predicate; import java.util.function.Supplier; import java.util.logging.Logger; +import java.util.stream.Collectors; final class JepTest { @@ -246,4 +248,35 @@ private static String readFromFile(int messageNumber) { } } + record CachingPredicate(Map> delegate, + Predicate original) implements Predicate { + + public CachingPredicate(Set inputs, Predicate original) { + this(inputs.stream() + .collect(Collectors.toMap(Function.identity(), _ -> StableValue.newInstance())), + original + ); + } + + @Override + public boolean test(T t) { + final StableValue stable = delegate.get(t); + if (stable == null) { + throw new IllegalArgumentException(t.toString()); + + } + if (stable.isSet()) { + return stable.isSet(); + } + synchronized (this) { + if (stable.isSet()) { + return stable.isSet(); + } + final Boolean r = (Boolean) original.test(t); + stable.setOrThrow(r); + return r; + } + } + } + } diff --git a/test/micro/org/openjdk/bench/java/lang/stable/CustomClassBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/CustomClassBenchmark.java index 4051bf81f62f0..005f2c06925d6 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/CustomClassBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/CustomClassBenchmark.java @@ -23,14 +23,28 @@ package org.openjdk.bench.java.lang.stable; -import org.openjdk.jmh.annotations.*; - +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.OperationsPerInvocation; +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.Map; +import java.util.Set; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Supplier; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.IntStream; /** - * Benchmark measuring StableValue performance + * Benchmark measuring custom stable value types */ @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.NANOSECONDS) @@ -43,146 +57,86 @@ "-XX:PerMethodTrapLimit=0"}) @Threads(Threads.MAX) // Benchmark under contention @OperationsPerInvocation(2) -public class StableValueBenchmark { - - private static final int VALUE = 42; - private static final int VALUE2 = 23; - - private static final StableValue STABLE = init(StableValue.newInstance(), VALUE); - private static final StableValue STABLE2 = init(StableValue.newInstance(), VALUE2); - private static final StableValue DCL = init(StableValue.newInstance(), VALUE); - private static final StableValue DCL2 = init(StableValue.newInstance(), VALUE2); - private static final AtomicReference ATOMIC = new AtomicReference<>(VALUE); - private static final AtomicReference ATOMIC2 = new AtomicReference<>(VALUE2); - private static final Holder HOLDER = new Holder(VALUE); - private static final Holder HOLDER2 = new Holder(VALUE2); - - private final StableValue stable = init(StableValue.newInstance(), VALUE); - private final StableValue stable2 = init(StableValue.newInstance(), VALUE2); - private final StableValue stableNull = StableValue.newInstance(); - private final StableValue stableNull2 = StableValue.newInstance(); - private final Supplier dcl = new Dcl<>(() -> VALUE); - private final Supplier dcl2 = new Dcl<>(() -> VALUE2); - private final AtomicReference atomic = new AtomicReference<>(VALUE); - private final AtomicReference atomic2 = new AtomicReference<>(VALUE2); - private final Supplier supplier = () -> VALUE; - private final Supplier supplier2 = () -> VALUE2; - - - @Setup - public void setup() { - stableNull.trySet(null); - stableNull2.trySet(VALUE2); - // Create pollution - int sum = 0; - for (int i = 0; i < 500_000; i++) { - final int v = i; - Dcl dclX = new Dcl<>(() -> v); - sum += dclX.get(); - StableValue stableX = StableValue.newInstance(); - stableX.trySet(i); - sum += stableX.orElseThrow(); - } - System.out.println("sum = " + sum); - } - - @Benchmark - public int atomic() { - return atomic.get() + atomic2.get(); - } - - @Benchmark - public int dcl() { - return dcl.get() + dcl2.get(); - } - - @Benchmark - public int stable() { - return stable.orElseThrow() + stable2.orElseThrow(); - } - - @Benchmark - public int stableNull() { - return (stableNull.orElseThrow() == null ? VALUE : VALUE2) + (stableNull2.orElseThrow() == null ? VALUE : VALUE2); - } +public class CustomClassBenchmark { - // Reference case - @Benchmark - public int refSupplier() { - return supplier.get() + supplier2.get(); - } + private static final Set SET = IntStream.range(0, 1024).boxed().collect(Collectors.toSet()); + private static final Predicate EVEN = i -> i % 2 == 0; + private static final Integer VALUE = (Integer) 42; + private static final Integer VALUE2 = (Integer) 13; - @Benchmark - public int staticAtomic() { - return ATOMIC.get() + ATOMIC2.get(); - } + private static final Predicate PREDICATE = cachingPredicate(SET, EVEN); + private static final Predicate PREDICATE2 = cachingPredicate(SET, EVEN); - @Benchmark - public int staticDcl() { - return DCL.orElseThrow() + DCL2.orElseThrow(); - } + private final Predicate predicate = cachingPredicate(SET, EVEN); + private final Predicate predicate2 = cachingPredicate(SET, EVEN); @Benchmark - public int staticHolder() { - return HOLDER.get() + HOLDER2.get(); + public boolean predicate() { + return predicate.test(VALUE) ^ predicate2.test(VALUE2); } @Benchmark - public int staticStable() { - return STABLE.orElseThrow() + STABLE2.orElseThrow(); + public boolean staticPredicate() { + return PREDICATE.test(VALUE) ^ PREDICATE2.test(VALUE2); } - - private static StableValue init(StableValue m, Integer value) { - m.trySet(value); - return m; + //Benchmark Mode Cnt Score Error Units + //CustomClassBenchmark.predicate avgt 10 7.470 ? 0.432 ns/op + //CustomClassBenchmark.staticPredicate avgt 10 6.650 ? 0.453 ns/op + static Predicate cachingPredicate(Set inputs, Predicate original) { + Function delegate = StableValue.newCachingFunction(inputs, (T t) -> Boolean.valueOf(original.test(t)), null); + return delegate::apply; } - // The VM should be able to constant-fold the value given in the constructor - // because StableValue fields have a special meaning. - private static final class Holder { - - private final StableValue delegate = StableValue.newInstance(); + // This is slow for some reason + //Benchmark Mode Cnt Score Error Units + //CustomClassBenchmark.predicate avgt 10 7.732 ? 0.793 ns/op + //CustomClassBenchmark.staticPredicate avgt 10 6.485 ? 0.325 ns/op + record CachingPredicate2(Function delegate) implements Predicate { - Holder(int value) { - delegate.setOrThrow(value); + public CachingPredicate2(Set inputs, Predicate original) { + this(StableValue.newCachingFunction(inputs, (T t) -> Boolean.valueOf(original.test(t)), null)); } - int get() { - return delegate.orElseThrow(); + @Override + public boolean test(T t) { + return delegate.apply(t); } } - // Handles null values - private static class Dcl implements Supplier { - - private final Supplier supplier; - private volatile V value; - private boolean bound; + //Benchmark Mode Cnt Score Error Units + //CustomClassBenchmark.predicate avgt 10 3.079 ? 0.037 ns/op + //CustomClassBenchmark.staticPredicate avgt 10 2.770 ? 0.508 ns/op + record CachingPredicate(Map> delegate, + Predicate original) implements Predicate { - public Dcl(Supplier supplier) { - this.supplier = supplier; + public CachingPredicate(Set inputs, Predicate original) { + this(inputs.stream() + .collect(Collectors.toMap(Function.identity(), _ -> StableValue.newInstance())), + original + ); } @Override - public V get() { - V v = value; - if (v == null) { - if (!bound) { - synchronized (this) { - v = value; - if (v == null) { - if (!bound) { - value = v = supplier.get(); - bound = true; - } - } - } + public boolean test(T t) { + final StableValue stable = delegate.get(t); + if (stable == null) { + throw new IllegalArgumentException(t.toString()); + + } + if (stable.isSet()) { + return stable.isSet(); + } + synchronized (this) { + if (stable.isSet()) { + return stable.isSet(); } + final Boolean r = (Boolean) original.test(t); + stable.setOrThrow(r); + return r; } - return v; } } From f747fff6978f06d615b16fcd24bb825806f86419 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 5 Aug 2024 15:32:32 +0200 Subject: [PATCH 104/327] Make language improvements in the JEP --- test/jdk/java/lang/StableValue/JEP.md | 22 +-- .../CustomCachingBiFunctionBenchmark.java | 143 ++++++++++++++++++ ...a => CustomCachingPredicateBenchmark.java} | 2 +- 3 files changed, 155 insertions(+), 12 deletions(-) create mode 100644 test/micro/org/openjdk/bench/java/lang/stable/CustomCachingBiFunctionBenchmark.java rename test/micro/org/openjdk/bench/java/lang/stable/{CustomClassBenchmark.java => CustomCachingPredicateBenchmark.java} (98%) diff --git a/test/jdk/java/lang/StableValue/JEP.md b/test/jdk/java/lang/StableValue/JEP.md index b785460992ae1..3e101d72d3f22 100644 --- a/test/jdk/java/lang/StableValue/JEP.md +++ b/test/jdk/java/lang/StableValue/JEP.md @@ -29,7 +29,7 @@ However, one important and simpler case of constrained mutation is that of a fie #### An example: memoization -Constrained mutation is essential to reliably cache the result of an expensive method call, so that it can be reused several times throughout the lifetime of an application (this technique is also known as [memoization](https://en.wikipedia.org/wiki/Memoization)). A nearly ubiquitous example of such an expensive method call is that to obtain a logger object through which an application's events can be reported. Obtaining a logger often entails expensive operations, such as reading and parsing configuration data, or prepare the backing storage where logging events will be recorded. Since these operations are expensive, an application will typically want to move them as much *forward in time* as possible: after all, an application might never need to log an event, so why paying the cost for this expensive initialization? Moreover, as some of these operation results in side effects - such as the creation of files and folders - it is crucial that they are executed _at most once_. +Constrained mutation is essential to reliably cache the result of an expensive method call, so that it can be reused several times throughout the lifetime of an application (this technique is also known as [memoization](https://en.wikipedia.org/wiki/Memoization)). A nearly ubiquitous example of such an expensive method call is that to obtain a logger object through which an application's events can be reported. Obtaining a logger often entails expensive operations, such as reading and parsing configuration data, or preparing the backing storage where logging events will be recorded. Since these operations are expensive, an application will typically want to move them as much *forward in time* as possible: after all, an application might never need to log an event, so why pay the cost for this expensive initialization? Moreover, as some of these operations result in side effects - such as the creation of files and folders - it is crucial that they are executed _at most once_. Combining mutable fields and encapsulation is a common way to approximate at-most-once update semantics. Consider the following example, where a logger object is created in the `Application::getLogger` method: @@ -51,13 +51,13 @@ As the `logger` field is private, the only way for clients to access it is to ca Unfortunately, the above solution does not work in a multithreaded environment. For instance, updates to the `logger` field made by one thread may not be immediately visible to other threads. This condition might result in multiple concurrent calls to the `Logger::create` method, thereby violating the "at-most-once" update guarantee. -##### Thread-safety with double-checked locking +##### Thread safety with double-checked locking One possible way to achieve thread safety would be to serialize access to the `Application::getLogger` method - i.e. by marking that method as `synchronized`. However, doing so has a performance cost, as multiple threads cannot concurrently obtain the application's logger object, even *long after* this object has been computed, and safely stored in the `logger` field. In other words, using `synchronized` amounts at applying a *permanent* performance tax on *all* logger accesses, in the rare event that a race occurs during the initial update of the `logger` field. -A more efficient solution is the so-called [class holder idiom](https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom), which achieves thread-safe "at-most-once" update guarantees by leaning on the lazy semantics of class initialization. However, for this approach to work correctly, the memoized data should be `static`, which is not the case here. +A more efficient solution is the so-called [class holder idiom](https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom), which achieves thread safe "at-most-once" update guarantees by leaning on the lazy semantics of class initialization. However, for this approach to work correctly, the memoized data should be `static`, which is not the case here. -In order to achieve thread-safety without compromising performance, developers often resorts to the brittle [double-checked idiom](https://en.wikipedia.org/wiki/Double-checked_locking): +In order to achieve thread safety without compromising performance, developers often resort to the brittle [double-checked idiom](https://en.wikipedia.org/wiki/Double-checked_locking): ``` class Application { @@ -90,11 +90,11 @@ Unfortunately, double-checked locking has several inherent design flaws: * *lack of optimizations* - as the `logger` field is updated at most once, one might expect the JVM to optimize access to this field accordingly, e.g. by [constant-folding](https://en.wikipedia.org/wiki/Constant_folding) access to an already-initialized `logger` field. Unfortunately, since `logger` is just a plan mutable field, the JVM cannot trust the field to never be updated again. As such, access to at-most-once fields, when realized with double-checked locking is not as efficient as it could be. * *limited applicability* - double-checked locking fails to scale to more complex use cases where e.g. the client might need an *array* of values where each element can be updated at most once. In this case, marking the array field as `volatile` is not enough, as the `volatile` modifier doesn't apply to the array *elements* but to the array as a whole. Instead, clients would have to resort to even more complex solutions using where at-most-once array elements are accessed using `VarHandles`. Needless to say, such solutions are even more brittle and error-prone, and should be avoided at all costs. -##### At-most-once as a first class concept +##### At-most-once as a first-class concept At-most-once semantics is unquestionably critical to implement important use cases such as caches and memoized functions. Unfortunately, existing workarounds, such as double-checked locking, cannot be considered adequate replacements for *first-class* "at-most-once" support. What we are missing is a way to *promise* that a variable will be initialized by the time it is used, with a value that is computed at most once, and *safely* across multiple threads. Such a mechanism would give the Java runtime maximum opportunity to stage and optimize its computation, thus avoiding the penalties that plague the workarounds shown above. Moreover, such a mechanism should gracefully scale to handle *collections* of "at-most-once" variables, while retaining efficient computer resource management. -When fully realized, first-class support for "at-most-once" sematics would fill an important gap between mutable and immutable fields, as shown in the table below: +When fully realized, first-class support for "at-most-once" semantics would fill an important gap between mutable and immutable fields, as shown in the table below: | Storage kind | #Updates | Code update location | Constant folding | Concurrent updates | @@ -105,7 +105,7 @@ When fully realized, first-class support for "at-most-once" sematics would fill [1]: https://docs.oracle.com/javase/specs/jls/se11/html/jls-17.html#jls-17.5 (JSL 17.5) -_Table 1: properties of mutable, immutable, and at-most-once variables_ +_Table 1: Shows the properties of mutable, immutable, and at-most-once variables_ ## Description @@ -162,7 +162,7 @@ Null-averse applications can also use `StableValue>`. When retrieving values, `StableValue` instances holding reference values can be faster than reference values managed via double-checked-idiom constructs as stable values rely -on explicit memory barriers needed only during the single store operation rather than performing +on explicit memory barriers needed only during the single, store operation rather than performing volatile access on each retrieval operation. In addition, stable values are eligible for constant folding optimizations by the JVM. In many @@ -179,7 +179,7 @@ called *only once*. This brings us to the introduction of _cached functions_. ### Caching functions So far, we have talked about the fundamental features of StableValue as a securely -wrapped stable value holder. However, it has become apparent, stable primitives are amenable +wrapped stable value holder. However, it has become apparent, that stable primitives are amenable to composition with other constructs in order to create more high-level and powerful features. [Caching (or Memoized) functions](https://en.wikipedia.org/wiki/Memoization) are functions where the output for a @@ -373,11 +373,11 @@ logger("com.company.Foo").log(Level.DEBUG, ...); ``` In the example above, only two input values were used. However, this concept allows declaring a -large number of stable values which can be easily retrieved using arbitrarily, but pre-specified, +large number of stable values that can be easily retrieved using arbitrarily, but pre-specified, keys in a resource-efficient and performant way. For example, high-performance, non-evicting caches may now be easily and reliably realized. -Analogue to a lazy list, the lazy map guarantees the function provided at map creation +Analog to a lazy list, the lazy map guarantees the function provided at map creation (used to lazily compute the map values) is invoked at most once per key (absent any Exceptions), even though used from several threads. diff --git a/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingBiFunctionBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingBiFunctionBenchmark.java new file mode 100644 index 0000000000000..d7cc64c67e40c --- /dev/null +++ b/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingBiFunctionBenchmark.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2024, 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 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.OperationsPerInvocation; +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.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +/** + * Benchmark measuring custom stable value types + */ +@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, jvmArgsAppend = { + "--enable-preview", + // Prevent the use of uncommon traps + "-XX:PerMethodTrapLimit=0"}) +@Threads(Threads.MAX) // Benchmark under contention +@OperationsPerInvocation(2) +public class CustomCachingPredicateBenchmark { + + private static final Set SET = IntStream.range(0, 64).boxed().collect(Collectors.toSet()); + private static final Predicate EVEN = i -> i % 2 == 0; + private static final Integer VALUE = (Integer) 42; + private static final Integer VALUE2 = (Integer) 13; + + private static final Predicate PREDICATE = cachingPredicate(SET, EVEN); + private static final Predicate PREDICATE2 = cachingPredicate(SET, EVEN); + + private final Predicate predicate = cachingPredicate(SET, EVEN); + private final Predicate predicate2 = cachingPredicate(SET, EVEN); + + @Benchmark + public boolean predicate() { + return predicate.test(VALUE) ^ predicate2.test(VALUE2); + } + + @Benchmark + public boolean staticPredicate() { + return PREDICATE.test(VALUE) ^ PREDICATE2.test(VALUE2); + } + + //Benchmark Mode Cnt Score Error Units + //CustomClassBenchmark.predicate avgt 10 7.470 ? 0.432 ns/op + //CustomClassBenchmark.staticPredicate avgt 10 6.650 ? 0.453 ns/op + static Predicate cachingPredicate(Set inputs, Predicate original) { + Function delegate = StableValue.newCachingFunction(inputs, (T t) -> Boolean.valueOf(original.test(t)), null); + return delegate::apply; + } + + // This is slow for some reason + //Benchmark Mode Cnt Score Error Units + //CustomClassBenchmark.predicate avgt 10 7.732 ? 0.793 ns/op + //CustomClassBenchmark.staticPredicate avgt 10 6.485 ? 0.325 ns/op + record CachingPredicate2(Function delegate) implements Predicate { + + public CachingPredicate2(Set inputs, Predicate original) { + this(StableValue.newCachingFunction(inputs, (T t) -> Boolean.valueOf(original.test(t)), null)); + } + + @Override + public boolean test(T t) { + return delegate.apply(t); + } + + } + + + //Benchmark Mode Cnt Score Error Units + //CustomClassBenchmark.predicate avgt 10 3.079 ? 0.037 ns/op + //CustomClassBenchmark.staticPredicate avgt 10 2.770 ? 0.508 ns/op + record CachingPredicate(Map> delegate, + Predicate original) implements Predicate { + + public CachingPredicate(Set inputs, Predicate original) { + this(inputs.stream() + .collect(Collectors.toMap(Function.identity(), _ -> StableValue.newInstance())), + original + ); + } + + @Override + public boolean test(T t) { + final StableValue stable = delegate.get(t); + if (stable == null) { + throw new IllegalArgumentException(t.toString()); + + } + if (stable.isSet()) { + return stable.isSet(); + } + synchronized (this) { + if (stable.isSet()) { + return stable.isSet(); + } + final Boolean r = (Boolean) original.test(t); + stable.setOrThrow(r); + return r; + } + } + } + +} diff --git a/test/micro/org/openjdk/bench/java/lang/stable/CustomClassBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingPredicateBenchmark.java similarity index 98% rename from test/micro/org/openjdk/bench/java/lang/stable/CustomClassBenchmark.java rename to test/micro/org/openjdk/bench/java/lang/stable/CustomCachingPredicateBenchmark.java index 005f2c06925d6..a5c339a8af344 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/CustomClassBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingPredicateBenchmark.java @@ -59,7 +59,7 @@ @OperationsPerInvocation(2) public class CustomClassBenchmark { - private static final Set SET = IntStream.range(0, 1024).boxed().collect(Collectors.toSet()); + private static final Set SET = IntStream.range(0, 64).boxed().collect(Collectors.toSet()); private static final Predicate EVEN = i -> i % 2 == 0; private static final Integer VALUE = (Integer) 42; private static final Integer VALUE2 = (Integer) 13; From eaf730f852e586ce16c0e9e3cea258abb8af9a2f Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 5 Aug 2024 17:06:52 +0200 Subject: [PATCH 105/327] Add custom function type benchmarks --- .../CustomCachingBiFunctionBenchmark.java | 226 ++++++++++++++---- .../CustomCachingPredicateBenchmark.java | 28 +-- 2 files changed, 197 insertions(+), 57 deletions(-) diff --git a/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingBiFunctionBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingBiFunctionBenchmark.java index d7cc64c67e40c..7e0dc2a4c2e0c 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingBiFunctionBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingBiFunctionBenchmark.java @@ -36,8 +36,10 @@ import org.openjdk.jmh.annotations.Warmup; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.concurrent.TimeUnit; +import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -53,91 +55,229 @@ @Measurement(iterations = 5, time = 2) @Fork(value = 2, jvmArgsAppend = { "--enable-preview", + "-XX:+UnlockDiagnosticVMOptions", + "-XX:+PrintInlining" // Prevent the use of uncommon traps - "-XX:PerMethodTrapLimit=0"}) + , "-XX:PerMethodTrapLimit=0" +}) @Threads(Threads.MAX) // Benchmark under contention @OperationsPerInvocation(2) -public class CustomCachingPredicateBenchmark { +public class CustomCachingBiFunctionBenchmark { - private static final Set SET = IntStream.range(0, 64).boxed().collect(Collectors.toSet()); - private static final Predicate EVEN = i -> i % 2 == 0; - private static final Integer VALUE = (Integer) 42; - private static final Integer VALUE2 = (Integer) 13; + private static final Set> SET = Set.of( + new Pair<>(1, 4), + new Pair<>(1, 6), + new Pair<>(1, 9), + new Pair<>(42, 13), + new Pair<>(13, 42), + new Pair<>(99, 1) + ); - private static final Predicate PREDICATE = cachingPredicate(SET, EVEN); - private static final Predicate PREDICATE2 = cachingPredicate(SET, EVEN); + private static final Integer VALUE = 42; + private static final Integer VALUE2 = 13; + private static final BiFunction ORIGINAL = (l, r) -> (Integer) (l * 2 + r); - private final Predicate predicate = cachingPredicate(SET, EVEN); - private final Predicate predicate2 = cachingPredicate(SET, EVEN); + private static final BiFunction FUNCTION = new CachingBiFunction4<>(SET, ORIGINAL); + private static final BiFunction FUNCTION2 = new CachingBiFunction4<>(SET, ORIGINAL); + + private static final BiFunction function = new CachingBiFunction4<>(SET, ORIGINAL);; + private static final BiFunction function2 = new CachingBiFunction4<>(SET, ORIGINAL);; + + private static final StableValue STABLE_VALUE = StableValue.newInstance(); + private static final StableValue STABLE_VALUE2 = StableValue.newInstance(); + + static { + STABLE_VALUE.trySet(ORIGINAL.apply(VALUE, VALUE2)); + STABLE_VALUE2.trySet(ORIGINAL.apply(VALUE2, VALUE)); + } @Benchmark - public boolean predicate() { - return predicate.test(VALUE) ^ predicate2.test(VALUE2); + public int function() { + return function.apply(VALUE, VALUE2); } @Benchmark - public boolean staticPredicate() { - return PREDICATE.test(VALUE) ^ PREDICATE2.test(VALUE2); + public int staticFunction() { + return FUNCTION.apply(VALUE, VALUE2) + FUNCTION2.apply(VALUE2, VALUE); } - //Benchmark Mode Cnt Score Error Units - //CustomClassBenchmark.predicate avgt 10 7.470 ? 0.432 ns/op - //CustomClassBenchmark.staticPredicate avgt 10 6.650 ? 0.453 ns/op - static Predicate cachingPredicate(Set inputs, Predicate original) { - Function delegate = StableValue.newCachingFunction(inputs, (T t) -> Boolean.valueOf(original.test(t)), null); - return delegate::apply; + @Benchmark + public int staticStableValue() { + return STABLE_VALUE.orElseThrow(); } - // This is slow for some reason - //Benchmark Mode Cnt Score Error Units - //CustomClassBenchmark.predicate avgt 10 7.732 ? 0.793 ns/op - //CustomClassBenchmark.staticPredicate avgt 10 6.485 ? 0.325 ns/op - record CachingPredicate2(Function delegate) implements Predicate { + //Benchmark Mode Cnt Score Error Units + //CustomCachingBiFunctionBenchmark.function avgt 10 0.353 ? 0.030 ns/op + //CustomCachingBiFunctionBenchmark.staticFunction avgt 10 0.344 ? 0.003 ns/op + //CustomCachingBiFunctionBenchmark.staticStableValue avgt 10 0.370 ? 0.062 ns/op + static BiFunction cachingBiFunction(Set> inputs, BiFunction original) { + Function, R> delegate = (Pair p) -> original.apply(p.left, p.right); + return (T t, U u) -> delegate.apply(new Pair<>(t, u)); + } - public CachingPredicate2(Set inputs, Predicate original) { - this(StableValue.newCachingFunction(inputs, (T t) -> Boolean.valueOf(original.test(t)), null)); + // + static BiFunction cachingBiFunction2(Set> inputs, BiFunction original) { + Function, R> delegate = StableValue.newCachingFunction(inputs, (Pair p) -> original.apply(p.left, p.right), null); + return (T t, U u) -> delegate.apply(new Pair<>(t, u)); + } + + //Benchmark Mode Cnt Score Error Units + //CustomCachingBiFunctionBenchmark.function avgt 10 577.307 ? 57.591 ns/op + //CustomCachingBiFunctionBenchmark.staticFunction avgt 10 488.900 ? 126.583 ns/op + //CustomCachingBiFunctionBenchmark.staticStableValue avgt 10 0.343 ? 0.014 ns/op + record CachingBiFunction2(Function, R> delegate) implements BiFunction { + + public CachingBiFunction2(Set> inputs, BiFunction original) { + this(StableValue.newCachingFunction(inputs, (Pair p) -> original.apply(p.left, p.right), null)); } @Override - public boolean test(T t) { - return delegate.apply(t); + public R apply(T t, U u) { + return delegate.apply(new Pair<>(t, u)); } - } + record Pair(T left, U right){} - //Benchmark Mode Cnt Score Error Units - //CustomClassBenchmark.predicate avgt 10 3.079 ? 0.037 ns/op - //CustomClassBenchmark.staticPredicate avgt 10 2.770 ? 0.508 ns/op - record CachingPredicate(Map> delegate, - Predicate original) implements Predicate { + //Benchmark Mode Cnt Score Error Units + //CustomCachingBiFunctionBenchmark.function avgt 10 471.650 ? 119.590 ns/op + //CustomCachingBiFunctionBenchmark.staticFunction avgt 10 560.667 ? 178.996 ns/op + //CustomCachingBiFunctionBenchmark.staticStableValue avgt 10 0.428 ? 0.070 ns/op - public CachingPredicate(Set inputs, Predicate original) { - this(inputs.stream() - .collect(Collectors.toMap(Function.identity(), _ -> StableValue.newInstance())), + // Crucially, use Map.copyOf to get an immutable Map + record CachingBiFunction( + Map, StableValue> delegate, + BiFunction original) implements BiFunction { + + public CachingBiFunction(Set> inputs, BiFunction original) { + this(Map.copyOf(inputs.stream() + .collect(Collectors.toMap(Function.identity(), _ -> StableValue.newInstance()))), original ); } @Override - public boolean test(T t) { - final StableValue stable = delegate.get(t); + public R apply(T t, U u) { + final StableValue stable = delegate.get(new Pair<>(t, u)); if (stable == null) { throw new IllegalArgumentException(t.toString()); } if (stable.isSet()) { - return stable.isSet(); + return stable.orElseThrow(); + } + synchronized (this) { + if (stable.isSet()) { + return stable.orElseThrow(); + } + final R r = original.apply(t, u); + stable.setOrThrow(r); + return r; + } + } + } + + //Benchmark Mode Cnt Score Error Units + //CustomCachingBiFunctionBenchmark.function avgt 10 8.438 ? 0.683 ns/op + //CustomCachingBiFunctionBenchmark.staticFunction avgt 10 8.312 ? 0.394 ns/op + //CustomCachingBiFunctionBenchmark.staticStableValue avgt 10 0.352 ? 0.021 ns/op + // Map-in-map + record CachingBiFunction3( + Map>> delegate, + BiFunction original) implements BiFunction { + + public CachingBiFunction3(Set> inputs, BiFunction original) { + this(delegate(inputs), original); + } + + static Map>> delegate(Set> inputs) { + Map>> map = inputs.stream() + .collect(Collectors.groupingBy(p -> p.left, + Collectors.groupingBy(p -> p.right, + Collectors.mapping((Function, ? extends StableValue>) _ -> StableValue.newInstance(), + Collectors.reducing(StableValue.newInstance(), _ -> StableValue.newInstance(), (StableValue a, StableValue b) -> a))))); + + @SuppressWarnings("unchecked") + Map>> copy = Map.ofEntries(map.entrySet().stream() + .map(e -> Map.entry(e.getKey(), Map.copyOf(e.getValue()))) + .toArray(Map.Entry[]::new)); + + return copy; + } + + @Override + public R apply(T t, U u) { + final StableValue stable; + try { + stable = Objects.requireNonNull(delegate.get(t).get(u)); + } catch (NullPointerException _) { + throw new IllegalArgumentException(t.toString() + ", " + u.toString()); + } + if (stable.isSet()) { + return stable.orElseThrow(); } synchronized (this) { if (stable.isSet()) { - return stable.isSet(); + return stable.orElseThrow(); } - final Boolean r = (Boolean) original.test(t); + final R r = original.apply(t, u); stable.setOrThrow(r); return r; } } } + + //Benchmark Mode Cnt Score Error Units + //CustomCachingBiFunctionBenchmark.function avgt 10 8.123 ? 0.113 ns/op + //CustomCachingBiFunctionBenchmark.staticFunction avgt 10 8.407 ? 0.559 ns/op + //CustomCachingBiFunctionBenchmark.staticStableValue avgt 10 0.354 ? 0.019 ns/op + + // CachingFunction-in-map + record CachingBiFunction4( + Map> delegate) implements BiFunction { + + public CachingBiFunction4(Set> inputs, BiFunction original) { + this(delegate(inputs, original)); + } + + static Map> delegate(Set> inputs, BiFunction original) { + Map>> map = inputs.stream() + .collect(Collectors.groupingBy(p -> p.left, + Collectors.groupingBy(p -> p.right, + Collectors.mapping((Function, ? extends StableValue>) _ -> StableValue.newInstance(), + Collectors.reducing(StableValue.newInstance(), _ -> StableValue.newInstance(), (StableValue a, StableValue b) -> a))))); + + @SuppressWarnings("unchecked") + Map> copy = Map.ofEntries(map.entrySet().stream() + .map(e -> Map.entry(e.getKey(), StableValue.newCachingFunction( e.getValue().keySet(), (U u) -> original.apply(e.getKey(), u) ,null))) + .toArray(Map.Entry[]::new)); + + return copy; + } + + @Override + public R apply(T t, U u) { + return delegate.get(t) + .apply(u); + + + // This prevents constant folding -----V + +/* Function function = delegate.get(t); + if (function == null) { + throw new IllegalArgumentException(t.toString() + ", " + u.toString()); + } + return function.apply(u);*/ + +/* + try { + return delegate.get(t) + .apply(u); + } catch (NullPointerException _) { + throw new IllegalArgumentException(t.toString() + ", " + u.toString()); + }*/ + } + } + } diff --git a/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingPredicateBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingPredicateBenchmark.java index a5c339a8af344..7a6383ca4bd8f 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingPredicateBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingPredicateBenchmark.java @@ -28,7 +28,6 @@ import org.openjdk.jmh.annotations.Fork; import org.openjdk.jmh.annotations.Measurement; import org.openjdk.jmh.annotations.Mode; -import org.openjdk.jmh.annotations.OperationsPerInvocation; import org.openjdk.jmh.annotations.OutputTimeUnit; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.State; @@ -52,32 +51,33 @@ @Warmup(iterations = 5, time = 1) @Measurement(iterations = 5, time = 2) @Fork(value = 2, jvmArgsAppend = { - "--enable-preview", + "--enable-preview" // Prevent the use of uncommon traps - "-XX:PerMethodTrapLimit=0"}) +// ,"-XX:PerMethodTrapLimit=0" +}) @Threads(Threads.MAX) // Benchmark under contention -@OperationsPerInvocation(2) -public class CustomClassBenchmark { +//@OperationsPerInvocation(2) +public class CustomCachingPredicateBenchmark { private static final Set SET = IntStream.range(0, 64).boxed().collect(Collectors.toSet()); private static final Predicate EVEN = i -> i % 2 == 0; - private static final Integer VALUE = (Integer) 42; - private static final Integer VALUE2 = (Integer) 13; + private static final Integer VALUE = 42; + private static final Integer VALUE2 = 13; - private static final Predicate PREDICATE = cachingPredicate(SET, EVEN); - private static final Predicate PREDICATE2 = cachingPredicate(SET, EVEN); + private static final Predicate PREDICATE = new CachingPredicate<>(SET, EVEN); + private static final Predicate PREDICATE2 = new CachingPredicate<>(SET, EVEN); - private final Predicate predicate = cachingPredicate(SET, EVEN); - private final Predicate predicate2 = cachingPredicate(SET, EVEN); + private final Predicate predicate = new CachingPredicate<>(SET, EVEN); + private final Predicate predicate2 = new CachingPredicate<>(SET, EVEN); @Benchmark public boolean predicate() { - return predicate.test(VALUE) ^ predicate2.test(VALUE2); + return predicate.test(VALUE); } @Benchmark public boolean staticPredicate() { - return PREDICATE.test(VALUE) ^ PREDICATE2.test(VALUE2); + return PREDICATE.test(VALUE); } //Benchmark Mode Cnt Score Error Units @@ -133,7 +133,7 @@ public boolean test(T t) { if (stable.isSet()) { return stable.isSet(); } - final Boolean r = (Boolean) original.test(t); + final boolean r = original.test(t); stable.setOrThrow(r); return r; } From bf3411a3fa96eb3972b1c19e21a2d50c64787aa3 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 6 Aug 2024 09:41:08 +0200 Subject: [PATCH 106/327] Switch to HTML table in the JEP --- test/jdk/java/lang/StableValue/JEP.md | 38 +++++++++++++++---- .../lang/stable/CustomCachingFunctions.java | 30 +++++++++++++++ 2 files changed, 60 insertions(+), 8 deletions(-) create mode 100644 test/micro/org/openjdk/bench/java/lang/stable/CustomCachingFunctions.java diff --git a/test/jdk/java/lang/StableValue/JEP.md b/test/jdk/java/lang/StableValue/JEP.md index 3e101d72d3f22..b1a8c52fd4883 100644 --- a/test/jdk/java/lang/StableValue/JEP.md +++ b/test/jdk/java/lang/StableValue/JEP.md @@ -96,14 +96,36 @@ At-most-once semantics is unquestionably critical to implement important use cas When fully realized, first-class support for "at-most-once" semantics would fill an important gap between mutable and immutable fields, as shown in the table below: - -| Storage kind | #Updates | Code update location | Constant folding | Concurrent updates | -| -------------- |----------| --------------------------------- | ----------------- |--------------------------| -| non-`final` | [0, ∞) | Anywhere | no | yes | -| `final` | 1 [1] | Constructor or static initializer | yes | no [1] | -| "at-most-once" | [0, 1] | Anywhere | yes, after update | yes, but only one "wins" | - -[1]: https://docs.oracle.com/javase/specs/jls/se11/html/jls-17.html#jls-17.5 (JSL 17.5) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Storage kind#UpdatesCore update locationConstant foldingConcurrent updates
    non-final[-, ∞)Anywherenoyes
    final1Constructor or static initializeryesno
    "at-most-once"[0, 1]Anywhereyes, after updateyes, but only one "wins"
    _Table 1: Shows the properties of mutable, immutable, and at-most-once variables_ diff --git a/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingFunctions.java b/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingFunctions.java new file mode 100644 index 0000000000000..8895112402af9 --- /dev/null +++ b/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingFunctions.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2024, 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; + +public final class CachingFunctions { + + private CachingFunctions() { + } +} From 057ccb7a878f0b0435e3ddb87dd4bb2fbc9a9ccc Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 6 Aug 2024 09:43:26 +0200 Subject: [PATCH 107/327] Remove trailing space in JEP --- test/jdk/java/lang/StableValue/JEP.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/jdk/java/lang/StableValue/JEP.md b/test/jdk/java/lang/StableValue/JEP.md index b1a8c52fd4883..f421a48d912f6 100644 --- a/test/jdk/java/lang/StableValue/JEP.md +++ b/test/jdk/java/lang/StableValue/JEP.md @@ -23,7 +23,7 @@ This might be the subject of a future JEP. Java allows developers to control whether fields should be mutable or not. Mutable fields can be updated multiple times, and from any arbitrary position in the code and by any thread. As such, mutable fields are often used to model complex objects whose state can be updated several times throughout their lifetimes, such as the contents of a text field in a UI component. Conversely, immutable fields (i.e. `final` fields), must be updated exactly *once*, and only in very specific places: the class initializer (for a static immutable field) or the class constructor(for an instance immutable field). As such, `final` fields are typically used to model values that act as *constants* (albeit shallowly so) throughout the lifetime of a class (in the case of `static` fields) or of an instance (in the case of an instance field). -Most of the time deciding whether an object should feature mutable or immutable state is straightforward enough. There are however cases where a field is subject to *constrained mutation*. That is, a field's value is neither constant, nor can it be mutated at will. Consider a program that might want to mutate a password field at most three times before it becomes immutable, in order to reflect the three login attempts allowed for a user; further mutation would result in some kind of exception. Expressing this kind of constrained mutation is hard, and cannot be achieved without the help of advanced type-system [calculi](https://en.wikipedia.org/wiki/Dependent_type). +Most of the time deciding whether an object should feature mutable or immutable state is straightforward enough. There are however cases where a field is subject to *constrained mutation*. That is, a field's value is neither constant, nor can it be mutated at will. Consider a program that might want to mutate a password field at most three times before it becomes immutable, in order to reflect the three login attempts allowed for a user; further mutation would result in some kind of exception. Expressing this kind of constrained mutation is hard, and cannot be achieved without the help of advanced type-system [calculi](https://en.wikipedia.org/wiki/Dependent_type). However, one important and simpler case of constrained mutation is that of a field whose updated *at most once*. As we shall see, the lack of a mechanism to capture this specific kind of constrained mutation in the Java platform incurs a considerable cost of performance and expressiveness. From 4a76dc1e361ab91c8fb981d770ec9103862be769 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 6 Aug 2024 14:22:50 +0200 Subject: [PATCH 108/327] Fix table alignment in JEP --- test/jdk/java/lang/StableValue/JEP.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/test/jdk/java/lang/StableValue/JEP.md b/test/jdk/java/lang/StableValue/JEP.md index f421a48d912f6..413b65ba1e376 100644 --- a/test/jdk/java/lang/StableValue/JEP.md +++ b/test/jdk/java/lang/StableValue/JEP.md @@ -96,7 +96,16 @@ At-most-once semantics is unquestionably critical to implement important use cas When fully realized, first-class support for "at-most-once" semantics would fill an important gap between mutable and immutable fields, as shown in the table below: - + +
    From ad8433b93086c3142bd46cd907503f135b6d0fa3 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 6 Aug 2024 15:21:24 +0200 Subject: [PATCH 109/327] Fix table in JEP --- test/jdk/java/lang/StableValue/JEP.md | 36 ++++++++++++++++----------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/test/jdk/java/lang/StableValue/JEP.md b/test/jdk/java/lang/StableValue/JEP.md index 413b65ba1e376..afdd712a82a02 100644 --- a/test/jdk/java/lang/StableValue/JEP.md +++ b/test/jdk/java/lang/StableValue/JEP.md @@ -103,6 +103,12 @@ When fully realized, first-class support for "at-most-once" semantics would fill border-collapse: collapse; text-align: center; } + tr:nth-child(3) { + background-color: #f2f2f2; + } + th { + background-color: #f2f2f2; + } }
    Storage kind #Updates
    @@ -114,29 +120,29 @@ When fully realized, first-class support for "at-most-once" semantics would fill - - + + - - - - - - - - - + + - + - - + + + + + + + + +
    Concurrent updates
    non-final[-, ∞)Mutable (non-final)[0, ∞) Anywherenoyes
    final1Constructor or static initializeryesnoNoYes
    "at-most-once""At-most-once" [0, 1] Anywhereyes, after updateyes, but only one "wins"Yes, after updateYes, but only one "wins"
    Immutable (final)1Constructor or static initializerYesNo
    -_Table 1: Shows the properties of mutable, immutable, and at-most-once variables_ +_Table 1: Shows the properties of mutable, at-most-once, and immutable variables._ ## Description From 303f3ce937f5ecc16d4ab1dc46c5abf32678fec0 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 7 Aug 2024 09:38:56 +0200 Subject: [PATCH 110/327] Update JEP table --- test/jdk/java/lang/StableValue/JEP.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/jdk/java/lang/StableValue/JEP.md b/test/jdk/java/lang/StableValue/JEP.md index afdd712a82a02..68dfef7f913a7 100644 --- a/test/jdk/java/lang/StableValue/JEP.md +++ b/test/jdk/java/lang/StableValue/JEP.md @@ -131,7 +131,7 @@ When fully realized, first-class support for "at-most-once" semantics would fill [0, 1] Anywhere Yes, after update - Yes, but only one "wins" + Yes, but only one thread "wins" Immutable (final) From a3a4b2d4f781387187444254590d2927c9dde478 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 7 Aug 2024 09:39:54 +0200 Subject: [PATCH 111/327] Add custom caching function benchmarks --- .../CustomCachingBiFunctionBenchmark.java | 71 ++++++++++--------- .../lang/stable/CustomCachingFunctions.java | 70 +++++++++++++++++- .../CustomCachingPredicateBenchmark.java | 49 ++++--------- 3 files changed, 120 insertions(+), 70 deletions(-) diff --git a/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingBiFunctionBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingBiFunctionBenchmark.java index 7e0dc2a4c2e0c..cfbf6ceacf5e0 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingBiFunctionBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingBiFunctionBenchmark.java @@ -39,11 +39,14 @@ import java.util.Objects; import java.util.Set; import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.LockSupport; import java.util.function.BiFunction; import java.util.function.Function; -import java.util.function.Predicate; import java.util.stream.Collectors; -import java.util.stream.IntStream; + +import org.openjdk.bench.java.lang.stable.CustomCachingFunctions.Pair; + +import static org.openjdk.bench.java.lang.stable.CustomCachingFunctions.cachingBiFunction; /** * Benchmark measuring custom stable value types @@ -54,17 +57,17 @@ @Warmup(iterations = 5, time = 1) @Measurement(iterations = 5, time = 2) @Fork(value = 2, jvmArgsAppend = { - "--enable-preview", - "-XX:+UnlockDiagnosticVMOptions", - "-XX:+PrintInlining" + "--enable-preview" +/* , "-XX:+UnlockDiagnosticVMOptions", + "-XX:+PrintInlining"*/ // Prevent the use of uncommon traps - , "-XX:PerMethodTrapLimit=0" +/* , "-XX:PerMethodTrapLimit=0"*/ }) @Threads(Threads.MAX) // Benchmark under contention @OperationsPerInvocation(2) public class CustomCachingBiFunctionBenchmark { - private static final Set> SET = Set.of( + private static final Set> SET = Set.of( new Pair<>(1, 4), new Pair<>(1, 6), new Pair<>(1, 9), @@ -75,13 +78,17 @@ public class CustomCachingBiFunctionBenchmark { private static final Integer VALUE = 42; private static final Integer VALUE2 = 13; - private static final BiFunction ORIGINAL = (l, r) -> (Integer) (l * 2 + r); + private static final BiFunction ORIGINAL = (l, r) -> { + // Slow down the original + LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1)); + return l * 2 + r; + }; - private static final BiFunction FUNCTION = new CachingBiFunction4<>(SET, ORIGINAL); - private static final BiFunction FUNCTION2 = new CachingBiFunction4<>(SET, ORIGINAL); + private static final BiFunction FUNCTION = cachingBiFunction(SET, ORIGINAL); + private static final BiFunction FUNCTION2 = cachingBiFunction(SET, ORIGINAL); - private static final BiFunction function = new CachingBiFunction4<>(SET, ORIGINAL);; - private static final BiFunction function2 = new CachingBiFunction4<>(SET, ORIGINAL);; + private static final BiFunction function = cachingBiFunction(SET, ORIGINAL);; + private static final BiFunction function2 = cachingBiFunction(SET, ORIGINAL);; private static final StableValue STABLE_VALUE = StableValue.newInstance(); private static final StableValue STABLE_VALUE2 = StableValue.newInstance(); @@ -93,7 +100,7 @@ public class CustomCachingBiFunctionBenchmark { @Benchmark public int function() { - return function.apply(VALUE, VALUE2); + return function.apply(VALUE, VALUE2) + function2.apply(VALUE2, VALUE); } @Benchmark @@ -103,21 +110,18 @@ public int staticFunction() { @Benchmark public int staticStableValue() { - return STABLE_VALUE.orElseThrow(); + return STABLE_VALUE.orElseThrow() + STABLE_VALUE2.orElseThrow(); } - //Benchmark Mode Cnt Score Error Units - //CustomCachingBiFunctionBenchmark.function avgt 10 0.353 ? 0.030 ns/op - //CustomCachingBiFunctionBenchmark.staticFunction avgt 10 0.344 ? 0.003 ns/op - //CustomCachingBiFunctionBenchmark.staticStableValue avgt 10 0.370 ? 0.062 ns/op - static BiFunction cachingBiFunction(Set> inputs, BiFunction original) { - Function, R> delegate = (Pair p) -> original.apply(p.left, p.right); - return (T t, U u) -> delegate.apply(new Pair<>(t, u)); - } - // - static BiFunction cachingBiFunction2(Set> inputs, BiFunction original) { - Function, R> delegate = StableValue.newCachingFunction(inputs, (Pair p) -> original.apply(p.left, p.right), null); + //Benchmark Mode Cnt Score Error Units + //CustomCachingBiFunctionBenchmark.function avgt 10 572.735 ? 27.583 ns/op + //CustomCachingBiFunctionBenchmark.staticFunction avgt 10 489.379 ? 81.296 ns/op + //CustomCachingBiFunctionBenchmark.staticStableValue avgt 10 0.387 ? 0.062 ns/op + + // Pair seams to not work that well... + static BiFunction cachingBiFunction2(Set> inputs, BiFunction original) { + final Function, R> delegate = StableValue.newCachingFunction(inputs, p -> original.apply(p.left(), p.right()), null); return (T t, U u) -> delegate.apply(new Pair<>(t, u)); } @@ -128,7 +132,7 @@ static BiFunction cachingBiFunction2(Set(Function, R> delegate) implements BiFunction { public CachingBiFunction2(Set> inputs, BiFunction original) { - this(StableValue.newCachingFunction(inputs, (Pair p) -> original.apply(p.left, p.right), null)); + this(StableValue.newCachingFunction(inputs, (Pair p) -> original.apply(p.left(), p.right()), null)); } @Override @@ -137,8 +141,6 @@ public R apply(T t, U u) { } } - record Pair(T left, U right){} - //Benchmark Mode Cnt Score Error Units //CustomCachingBiFunctionBenchmark.function avgt 10 471.650 ? 119.590 ns/op //CustomCachingBiFunctionBenchmark.staticFunction avgt 10 560.667 ? 178.996 ns/op @@ -192,8 +194,8 @@ public CachingBiFunction3(Set> inputs, BiFunction static Map>> delegate(Set> inputs) { Map>> map = inputs.stream() - .collect(Collectors.groupingBy(p -> p.left, - Collectors.groupingBy(p -> p.right, + .collect(Collectors.groupingBy(Pair::left, + Collectors.groupingBy(Pair::right, Collectors.mapping((Function, ? extends StableValue>) _ -> StableValue.newInstance(), Collectors.reducing(StableValue.newInstance(), _ -> StableValue.newInstance(), (StableValue a, StableValue b) -> a))))); @@ -241,12 +243,13 @@ public CachingBiFunction4(Set> inputs, BiFunction this(delegate(inputs, original)); } - static Map> delegate(Set> inputs, BiFunction original) { + static Map> delegate(Set> inputs, + BiFunction original) { Map>> map = inputs.stream() - .collect(Collectors.groupingBy(p -> p.left, - Collectors.groupingBy(p -> p.right, + .collect(Collectors.groupingBy(Pair::left, + Collectors.groupingBy(Pair::right, Collectors.mapping((Function, ? extends StableValue>) _ -> StableValue.newInstance(), - Collectors.reducing(StableValue.newInstance(), _ -> StableValue.newInstance(), (StableValue a, StableValue b) -> a))))); + Collectors.reducing(StableValue.newInstance(), _ -> StableValue.newInstance(), (StableValue a, StableValue b) -> b))))); @SuppressWarnings("unchecked") Map> copy = Map.ofEntries(map.entrySet().stream() diff --git a/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingFunctions.java b/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingFunctions.java index 8895112402af9..2b18c355d3638 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingFunctions.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingFunctions.java @@ -23,8 +23,74 @@ package org.openjdk.bench.java.lang.stable; -public final class CachingFunctions { +import java.util.Map; +import java.util.Set; +import java.util.function.BiFunction; +import java.util.function.BinaryOperator; +import java.util.function.Function; +import java.util.function.IntSupplier; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.function.UnaryOperator; +import java.util.stream.Collectors; - private CachingFunctions() { +final class CustomCachingFunctions { + + private CustomCachingFunctions() {} + + record Pair(L left, R right){} + + static Predicate cachingPredicate(Set inputs, + Predicate original) { + + final Function delegate = StableValue.newCachingFunction(inputs, original::test, null); + return delegate::apply; + } + + static BiFunction cachingBiFunction(Set> inputs, + BiFunction original) { + + final Map> tToUs = inputs.stream() + .collect(Collectors.groupingBy(Pair::left, + Collectors.mapping(Pair::right, Collectors.toSet()))); + + // Map::copyOf is crucial! + final Map> map = Map.copyOf(tToUs.entrySet().stream() + .map(e -> Map.entry(e.getKey(), StableValue.newCachingFunction(e.getValue(), (U u) -> original.apply(e.getKey(), u), null))) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))); + + return (T t, U u) -> { + final Function function = map.get(t); + if (function != null) { + try { + return function.apply(u); + } catch (IllegalArgumentException iae) { + // The original function might throw + throw new IllegalArgumentException(t.toString() + ", " + u.toString(), iae); + } + } + throw new IllegalArgumentException(t.toString() + ", " + u.toString()); + }; } + + static BinaryOperator cachingBinaryOperator(Set> inputs, + BinaryOperator original) { + + final BiFunction biFunction = cachingBiFunction(inputs, original); + return biFunction::apply; + } + + static UnaryOperator cachingUnaryOperator(Set inputs, + UnaryOperator original) { + + final Function function = StableValue.newCachingFunction(inputs, original, null); + return function::apply; + + } + + static IntSupplier cachingIntSupplier(IntSupplier original) { + final Supplier delegate = StableValue.newCachingSupplier(original::getAsInt, null); + return delegate::get; + } + } diff --git a/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingPredicateBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingPredicateBenchmark.java index 7a6383ca4bd8f..58e7a5904e952 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingPredicateBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingPredicateBenchmark.java @@ -37,11 +37,14 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.LockSupport; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.IntStream; +import static org.openjdk.bench.java.lang.stable.CustomCachingFunctions.cachingPredicate; + /** * Benchmark measuring custom stable value types */ @@ -60,15 +63,16 @@ public class CustomCachingPredicateBenchmark { private static final Set SET = IntStream.range(0, 64).boxed().collect(Collectors.toSet()); - private static final Predicate EVEN = i -> i % 2 == 0; - private static final Integer VALUE = 42; - private static final Integer VALUE2 = 13; + private static final Predicate EVEN = i -> { + // Slow down the original + LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1)); + return i % 2 == 0; + }; - private static final Predicate PREDICATE = new CachingPredicate<>(SET, EVEN); - private static final Predicate PREDICATE2 = new CachingPredicate<>(SET, EVEN); + private static final Integer VALUE = 42; - private final Predicate predicate = new CachingPredicate<>(SET, EVEN); - private final Predicate predicate2 = new CachingPredicate<>(SET, EVEN); + private static final Predicate PREDICATE = cachingPredicate(SET, EVEN); + private final Predicate predicate = cachingPredicate(SET, EVEN); @Benchmark public boolean predicate() { @@ -80,35 +84,12 @@ public boolean staticPredicate() { return PREDICATE.test(VALUE); } - //Benchmark Mode Cnt Score Error Units - //CustomClassBenchmark.predicate avgt 10 7.470 ? 0.432 ns/op - //CustomClassBenchmark.staticPredicate avgt 10 6.650 ? 0.453 ns/op - static Predicate cachingPredicate(Set inputs, Predicate original) { - Function delegate = StableValue.newCachingFunction(inputs, (T t) -> Boolean.valueOf(original.test(t)), null); - return delegate::apply; - } - - // This is slow for some reason - //Benchmark Mode Cnt Score Error Units - //CustomClassBenchmark.predicate avgt 10 7.732 ? 0.793 ns/op - //CustomClassBenchmark.staticPredicate avgt 10 6.485 ? 0.325 ns/op - record CachingPredicate2(Function delegate) implements Predicate { - - public CachingPredicate2(Set inputs, Predicate original) { - this(StableValue.newCachingFunction(inputs, (T t) -> Boolean.valueOf(original.test(t)), null)); - } - - @Override - public boolean test(T t) { - return delegate.apply(t); - } - - } + //Benchmark Mode Cnt Score Error Units + //CustomCachingPredicateBenchmark.predicate avgt 10 3.054 ? 0.155 ns/op + //CustomCachingPredicateBenchmark.staticPredicate avgt 10 2.205 ? 0.361 ns/op - //Benchmark Mode Cnt Score Error Units - //CustomClassBenchmark.predicate avgt 10 3.079 ? 0.037 ns/op - //CustomClassBenchmark.staticPredicate avgt 10 2.770 ? 0.508 ns/op + // This is not constant foldable record CachingPredicate(Map> delegate, Predicate original) implements Predicate { From 9b386d51cbf01e6b7dab9fab011b573c5ce6ec1b Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 7 Aug 2024 16:12:50 +0200 Subject: [PATCH 112/327] Switch to acquire/release semantics --- .../lang/stable/CachingIntFunction.java | 12 ++++++---- .../internal/lang/stable/CachingSupplier.java | 8 +++---- .../internal/lang/stable/StableValueImpl.java | 16 +++---------- .../internal/lang/stable/StableValueUtil.java | 23 ++++++++----------- test/jdk/java/lang/StableValue/JEP.md | 5 ++-- 5 files changed, 24 insertions(+), 40 deletions(-) diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/CachingIntFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/CachingIntFunction.java index a558ed6cade05..2689d42a71842 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/CachingIntFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/CachingIntFunction.java @@ -29,8 +29,10 @@ import jdk.internal.vm.annotation.Stable; import java.util.Arrays; +import java.util.Objects; import java.util.function.IntFunction; import java.util.stream.Collectors; +import java.util.stream.IntStream; // Note: It would be possible to just use `LazyList::get` instead of this // class but explicitly providing a class like this provides better @@ -64,13 +66,12 @@ public CachingIntFunction(int size, @ForceInline @Override public R apply(int value) { - R r; try { - // Todo: Will the exception handling here impair performance? - r = (R) values[value]; + Objects.checkIndex(value, values.length); } catch (IndexOutOfBoundsException e) { throw new IllegalArgumentException(e); } + R r = StableValueUtil.getAcquire(values, StableValueUtil.arrayOffset(value)); if (r != null) { return StableValueUtil.unwrap(r); } @@ -80,7 +81,7 @@ public R apply(int value) { return StableValueUtil.unwrap(r); } r = original.apply(value); - StableValueUtil.safelyPublish(values, StableValueUtil.arrayOffset(value), r); + StableValueUtil.cas(values, StableValueUtil.arrayOffset(value), r); } return r; } @@ -97,7 +98,8 @@ public String toString() { } private String valuesAsString() { - return Arrays.stream(values, 0, values.length) + return IntStream.range(0, values.length) + .mapToObj(i -> StableValueUtil.getAcquire(values, StableValueUtil.arrayOffset(i))) .map(v -> (v == this) ? "(this CachingIntFunction)" : StableValueUtil.render(v)) .collect(Collectors.joining(", ")); } diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/CachingSupplier.java b/src/java.base/share/classes/jdk/internal/lang/stable/CachingSupplier.java index ec653d238c266..060bc0a76a755 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/CachingSupplier.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/CachingSupplier.java @@ -52,11 +52,10 @@ public CachingSupplier(Supplier original) { this.original = original; } - @SuppressWarnings("unchecked") @ForceInline @Override public T get() { - T t = value; + T t = StableValueUtil.getAcquire(this, VALUE_OFFSET); if (value != null) { return StableValueUtil.unwrap(t); } @@ -66,7 +65,7 @@ public T get() { return StableValueUtil.unwrap(t); } t = original.get(); - StableValueUtil.safelyPublish(this, VALUE_OFFSET, t); + StableValueUtil.cas(this, VALUE_OFFSET, t); } return t; } @@ -77,8 +76,7 @@ public static CachingSupplier of(Supplier original) { @Override public String toString() { - @SuppressWarnings("unchecked") - final T t = (T) StableValueUtil.UNSAFE.getReferenceVolatile(this, VALUE_OFFSET); + final T t = StableValueUtil.getAcquire(this, VALUE_OFFSET); return "CachingSupplier[value=" + (t == this ? "(this CachingSupplier)" : StableValueUtil.render(t)) + ", original=" + original + "]"; } 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 9698d2d1bb238..0a42263ec99f2 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 @@ -60,7 +60,7 @@ public boolean trySet(T value) { if (value() != null) { return false; } - return StableValueUtil.safelyPublish(this, VALUE_OFFSET, value); + return StableValueUtil.cas(this, VALUE_OFFSET, value); } @ForceInline @@ -94,7 +94,7 @@ public int hashCode() { final T t = value(); return t == this ? 1 - : Objects.hashCode(value()); + : Objects.hashCode(t); } @Override @@ -113,19 +113,9 @@ public String toString() { : "StableValue" + StableValueUtil.render(t); } - @SuppressWarnings("unchecked") @ForceInline - // First, try to read the value using plain memory semantics. - // If not set, fall back to `volatile` memory semantics. public T value() { - final T t = valuePlain(); - return t != null ? t : (T) StableValueUtil.UNSAFE.getReferenceVolatile(this, VALUE_OFFSET); - } - - @ForceInline - private T valuePlain() { - // Appears to be faster than `(T) UNSAFE.getReference(this, VALUE_OFFSET)` - return value; + return StableValueUtil.getAcquire(this, VALUE_OFFSET); } // Factory diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableValueUtil.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableValueUtil.java index b595d1cbb50c8..82378b4e5267a 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableValueUtil.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableValueUtil.java @@ -46,23 +46,18 @@ static String render(T t) { } @ForceInline - static boolean safelyPublish(Object o, long offset, Object value) { - - // Prevents reordering of store operations with other store operations. - // This means any stores made to a field prior to this point cannot be - // reordered with the following CAS operation of the reference to the field. - - // In other words, if a loader (using plain memory semantics) can first observe - // a holder reference, any field updates in the holder reference made prior to - // this fence are guaranteed to be seen. - // See https://gee.cs.oswego.edu/dl/html/j9mm.html "Mixed Modes and Specializations", - // Doug Lea, 2018 - UNSAFE.storeStoreFence(); - - // This upholds the invariant, a `@Stable` field is written to at most once. + static boolean cas(Object o, long offset, Object value) { + // This upholds the invariant, a `@Stable` field is written to at most once + // and implies release semantics. return UNSAFE.compareAndSetReference(o, offset, null, wrap(value)); } + @SuppressWarnings("unchecked") + @ForceInline + static T getAcquire(Object o, long offset) { + return (T) UNSAFE.getReferenceAcquire(o, offset); + } + @ForceInline static long arrayOffset(int index) { return Unsafe.ARRAY_OBJECT_BASE_OFFSET + (long) index * Unsafe.ARRAY_OBJECT_INDEX_SCALE; diff --git a/test/jdk/java/lang/StableValue/JEP.md b/test/jdk/java/lang/StableValue/JEP.md index 68dfef7f913a7..5162dae5d2d55 100644 --- a/test/jdk/java/lang/StableValue/JEP.md +++ b/test/jdk/java/lang/StableValue/JEP.md @@ -198,9 +198,8 @@ A stable value may be set to `null` which then will be considered its set value. Null-averse applications can also use `StableValue>`. When retrieving values, `StableValue` instances holding reference values can be faster -than reference values managed via double-checked-idiom constructs as stable values rely -on explicit memory barriers needed only during the single, store operation rather than performing -volatile access on each retrieval operation. +than reference values managed via `volatile` double-checked-idiom constructs as stable values rely +on less expensive memory semantics. In addition, stable values are eligible for constant folding optimizations by the JVM. In many ways, this is similar to the holder-class idiom in the sense it offers the same From 8487fbdb978f564f2702c5b951a8c57adec84f6b Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Thu, 8 Aug 2024 13:28:20 +0200 Subject: [PATCH 113/327] Switch to volatile semantics and refactor to Object --- .../java/util/ImmutableCollections.java | 25 ++++++++------ .../internal/lang/stable/CachingFunction.java | 12 +++---- .../lang/stable/CachingIntFunction.java | 33 +++++++++++-------- .../internal/lang/stable/CachingSupplier.java | 20 ++++++----- .../internal/lang/stable/StableValueImpl.java | 26 +++++++-------- .../internal/lang/stable/StableValueUtil.java | 18 ++++------ 6 files changed, 70 insertions(+), 64 deletions(-) diff --git a/src/java.base/share/classes/java/util/ImmutableCollections.java b/src/java.base/share/classes/java/util/ImmutableCollections.java index 9deb6fef3d448..213a75a72c727 100644 --- a/src/java.base/share/classes/java/util/ImmutableCollections.java +++ b/src/java.base/share/classes/java/util/ImmutableCollections.java @@ -788,19 +788,24 @@ static final class LazyList extends AbstractImmutableList { @Override public E get(int i) { final StableValueImpl stable = backing.get(i); - E e = stable.value(); + Object e = stable.wrappedValue(); if (e != null) { return StableValueUtil.unwrap(e); } synchronized (stable) { - e = stable.value(); + e = stable.wrappedValue(); if (e != null) { return StableValueUtil.unwrap(e); } - e = mapper.apply(i); - stable.setOrThrow(e); + final E newValue = mapper.apply(i); + if (!stable.trySet(newValue)) { + throw new IllegalStateException( + "Cannot set the holder value for index " + i + " to " + e + + " because a value of " + StableValueUtil.unwrap(stable.wrappedValue()) + + " is alredy set."); + } + return newValue; } - return e; } @Override @@ -1495,19 +1500,19 @@ public V get(Object key) { @ForceInline V computeIfUnset(K key, StableValueImpl stable) { - V v = stable.value(); + Object v = stable.wrappedValue(); if (v != null) { return StableValueUtil.unwrap(v); } synchronized (stable) { - v = stable.value(); + v = stable.wrappedValue(); if (v != null) { return StableValueUtil.unwrap(v); } - v = mapper.apply(key); - stable.setOrThrow(v); + final V newValue = mapper.apply(key); + stable.setOrThrow(newValue); + return newValue; } - return v; } @jdk.internal.ValueBased diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/CachingFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/CachingFunction.java index 5b5a0ecf964e9..d9fade33589e6 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/CachingFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/CachingFunction.java @@ -43,19 +43,19 @@ public R apply(T value) { if (stable == null) { throw new IllegalArgumentException("Input not allowed: " + value); } - R r = stable.value(); + Object r = stable.wrappedValue(); if (r != null) { return StableValueUtil.unwrap(r); } synchronized (stable) { - r = stable.value(); + r = stable.wrappedValue(); if (r != null) { return StableValueUtil.unwrap(r); } - r = original.apply(value); - stable.setOrThrow(r); + final R newValue = original.apply(value); + stable.setOrThrow(newValue); + return newValue; } - return r; } @Override @@ -79,7 +79,7 @@ private String renderValues() { boolean first = true; for (var e:values.entrySet()) { if (first) { first = false; } else { sb.append(", "); }; - final Object value = e.getValue().value(); + final Object value = e.getValue().wrappedValue(); sb.append(e.getKey()).append('='); if (value == this) { sb.append("(this CachingFunction)"); diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/CachingIntFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/CachingIntFunction.java index 2689d42a71842..0882ee2ebfef7 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/CachingIntFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/CachingIntFunction.java @@ -28,7 +28,6 @@ import jdk.internal.vm.annotation.ForceInline; import jdk.internal.vm.annotation.Stable; -import java.util.Arrays; import java.util.Objects; import java.util.function.IntFunction; import java.util.stream.Collectors; @@ -47,10 +46,12 @@ */ public final class CachingIntFunction implements IntFunction { + @Stable private final IntFunction original; + @Stable private final Object[] mutexes; @Stable - private final Object[] values; + private final Object[] wrappedValues; public CachingIntFunction(int size, IntFunction original) { @@ -59,31 +60,30 @@ public CachingIntFunction(int size, for (int i = 0; i < size; i++) { mutexes[i] = new Object(); } - this.values = new Object[size]; + this.wrappedValues = new Object[size]; } - @SuppressWarnings("unchecked") @ForceInline @Override - public R apply(int value) { + public R apply(int index) { try { - Objects.checkIndex(value, values.length); + Objects.checkIndex(index, wrappedValues.length); } catch (IndexOutOfBoundsException e) { throw new IllegalArgumentException(e); } - R r = StableValueUtil.getAcquire(values, StableValueUtil.arrayOffset(value)); + Object r = wrappedValue(index); if (r != null) { return StableValueUtil.unwrap(r); } - synchronized (mutexes[value]) { - r = (R) values[value]; + synchronized (mutexes[index]) { + r = wrappedValues[index]; if (r != null) { return StableValueUtil.unwrap(r); } - r = original.apply(value); - StableValueUtil.cas(values, StableValueUtil.arrayOffset(value), r); + final R newValue = original.apply(index); + StableValueUtil.wrapAndCas(wrappedValues, StableValueUtil.arrayOffset(index), newValue); + return newValue; } - return r; } public static CachingIntFunction of(int size, IntFunction original) { @@ -98,10 +98,15 @@ public String toString() { } private String valuesAsString() { - return IntStream.range(0, values.length) - .mapToObj(i -> StableValueUtil.getAcquire(values, StableValueUtil.arrayOffset(i))) + return IntStream.range(0, wrappedValues.length) + .mapToObj(this::wrappedValue) .map(v -> (v == this) ? "(this CachingIntFunction)" : StableValueUtil.render(v)) .collect(Collectors.joining(", ")); } + @ForceInline + private Object wrappedValue(int i) { + return StableValueUtil.UNSAFE.getReferenceVolatile(wrappedValues, StableValueUtil.arrayOffset(i)); + } + } diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/CachingSupplier.java b/src/java.base/share/classes/jdk/internal/lang/stable/CachingSupplier.java index 060bc0a76a755..abd51535ca761 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/CachingSupplier.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/CachingSupplier.java @@ -41,12 +41,14 @@ public final class CachingSupplier implements Supplier { private static final long VALUE_OFFSET = - StableValueUtil.UNSAFE.objectFieldOffset(CachingSupplier.class, "value"); + StableValueUtil.UNSAFE.objectFieldOffset(CachingSupplier.class, "wrappedValue"); + @Stable private final Supplier original; + @Stable private final Object mutex = new Object(); @Stable - private T value; + private volatile Object wrappedValue; public CachingSupplier(Supplier original) { this.original = original; @@ -55,19 +57,19 @@ public CachingSupplier(Supplier original) { @ForceInline @Override public T get() { - T t = StableValueUtil.getAcquire(this, VALUE_OFFSET); - if (value != null) { + Object t = wrappedValue; + if (t != null) { return StableValueUtil.unwrap(t); } synchronized (mutex) { - t = value; + t = wrappedValue; if (t != null) { return StableValueUtil.unwrap(t); } - t = original.get(); - StableValueUtil.cas(this, VALUE_OFFSET, t); + final T newValue = original.get(); + StableValueUtil.wrapAndCas(this, VALUE_OFFSET, newValue); + return newValue; } - return t; } public static CachingSupplier of(Supplier original) { @@ -76,7 +78,7 @@ public static CachingSupplier of(Supplier original) { @Override public String toString() { - final T t = StableValueUtil.getAcquire(this, VALUE_OFFSET); + final Object t = wrappedValue; return "CachingSupplier[value=" + (t == this ? "(this CachingSupplier)" : StableValueUtil.render(t)) + ", original=" + original + "]"; } 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 0a42263ec99f2..555e470f84063 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 @@ -35,7 +35,7 @@ public final class StableValueImpl implements StableValue { // Unsafe offsets for direct field access private static final long VALUE_OFFSET = - StableValueUtil.UNSAFE.objectFieldOffset(StableValueImpl.class, "value"); + StableValueUtil.UNSAFE.objectFieldOffset(StableValueImpl.class, "wrappedValue"); // Generally, fields annotated with `@Stable` are accessed by the JVM using special // memory semantics rules (see `parse.hpp` and `parse(1|2|3).cpp`). @@ -49,24 +49,24 @@ public final class StableValueImpl implements StableValue { // | other | Set(other) | // @Stable - private T value; + private volatile Object wrappedValue; // Only allow creation via the factory `StableValueImpl::newInstance` private StableValueImpl() {} @ForceInline @Override - public boolean trySet(T value) { - if (value() != null) { + public boolean trySet(T newValue) { + if (wrappedValue != null) { return false; } - return StableValueUtil.cas(this, VALUE_OFFSET, value); + return StableValueUtil.wrapAndCas(this, VALUE_OFFSET, newValue); } @ForceInline @Override public T orElseThrow() { - final T t = value(); + final Object t = wrappedValue; if (t != null) { return StableValueUtil.unwrap(t); } @@ -76,7 +76,7 @@ public T orElseThrow() { @ForceInline @Override public T orElse(T other) { - final T t = value(); + final Object t = wrappedValue; if (t != null) { return StableValueUtil.unwrap(t); } @@ -86,12 +86,12 @@ public T orElse(T other) { @ForceInline @Override public boolean isSet() { - return value() != null; + return wrappedValue != null; } @Override public int hashCode() { - final T t = value(); + final Object t = wrappedValue; return t == this ? 1 : Objects.hashCode(t); @@ -102,20 +102,20 @@ public boolean equals(Object obj) { return obj instanceof StableValueImpl other && // Note that the returned `value()` will be `null` if the holder value // is unset and `nullSentinel()` if the holder value is `null`. - Objects.equals(value(), other.value()); + Objects.equals(wrappedValue, other.wrappedValue); } @Override public String toString() { - final T t = value(); + final Object t = wrappedValue; return t == this ? "(this StableValue)" : "StableValue" + StableValueUtil.render(t); } @ForceInline - public T value() { - return StableValueUtil.getAcquire(this, VALUE_OFFSET); + public Object wrappedValue() { + return wrappedValue; } // Factory diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableValueUtil.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableValueUtil.java index 82378b4e5267a..13ab8dab2b134 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableValueUtil.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableValueUtil.java @@ -30,34 +30,28 @@ static T wrap(T t) { } // Unwraps null sentinel values into `null` + @SuppressWarnings("unchecked") @ForceInline - public static T unwrap(T t) { - return t != nullSentinel() ? t : null; + public static T unwrap(Object t) { + return t != nullSentinel() ? (T) t : null; } @SuppressWarnings("unchecked") @ForceInline - static T nullSentinel() { + private static T nullSentinel() { return (T) NULL_SENTINEL; } - static String render(T t) { + static String render(Object t) { return (t == null) ? ".unset" : "[" + unwrap(t) + "]"; } @ForceInline - static boolean cas(Object o, long offset, Object value) { + static boolean wrapAndCas(Object o, long offset, Object value) { // This upholds the invariant, a `@Stable` field is written to at most once - // and implies release semantics. return UNSAFE.compareAndSetReference(o, offset, null, wrap(value)); } - @SuppressWarnings("unchecked") - @ForceInline - static T getAcquire(Object o, long offset) { - return (T) UNSAFE.getReferenceAcquire(o, offset); - } - @ForceInline static long arrayOffset(int index) { return Unsafe.ARRAY_OBJECT_BASE_OFFSET + (long) index * Unsafe.ARRAY_OBJECT_INDEX_SCALE; From 9e735ccce6c04be65fb5e1219974088d033d3943 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Thu, 8 Aug 2024 15:09:38 +0200 Subject: [PATCH 114/327] Remove DCL vs StableValue performance note --- test/jdk/java/lang/StableValue/JEP.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/test/jdk/java/lang/StableValue/JEP.md b/test/jdk/java/lang/StableValue/JEP.md index 5162dae5d2d55..4d2e2ed4a9691 100644 --- a/test/jdk/java/lang/StableValue/JEP.md +++ b/test/jdk/java/lang/StableValue/JEP.md @@ -197,10 +197,6 @@ thread, or concurrently, by multiple threads. A stable value may be set to `null` which then will be considered its set value. Null-averse applications can also use `StableValue>`. -When retrieving values, `StableValue` instances holding reference values can be faster -than reference values managed via `volatile` double-checked-idiom constructs as stable values rely -on less expensive memory semantics. - In addition, stable values are eligible for constant folding optimizations by the JVM. In many ways, this is similar to the holder-class idiom in the sense it offers the same performance and constant-folding characteristics but, `StableValue` incurs a lower static From c7dcc5c8727b4286b340947c59ecafb884bac2d1 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Thu, 8 Aug 2024 15:10:01 +0200 Subject: [PATCH 115/327] Update the happens-before section --- src/java.base/share/classes/java/lang/StableValue.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 18767faf751f7..68e32449818b7 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -125,11 +125,13 @@ * *

    Memory Consistency Properties

    * Actions on a presumptive holder value in a thread prior to calling a method that sets - * the holder value are seen by any other thread that first observes a set holder value. + * the holder value are seen by any other thread that observes a set holder value. * - * It should be noted that this does not form a proper + * More generally, the action of attempting to interact (i.e. via load or store operations) + * with a StableValue's holder value (e.g. via {@link StableValue#trySet} or + * {@link StableValue#orElseThrow()}) forms a * happens-before - * relation because the stable value set/observe relation is not transitive. + * relation between any other attempt to interact with the StableValue's holder value. * *

    Nullability

    * Except for a StableValue's holder value itself, all method parameters must be From af2eb3c84b67540701820d07021ddd96cb388d4a Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Thu, 8 Aug 2024 15:49:37 +0200 Subject: [PATCH 116/327] Fix issues from comments in the PR --- .../classes/jdk/internal/lang/stable/StableValueImpl.java | 2 +- .../bench/java/lang/stable/CustomCachingFunctions.java | 6 +++--- .../java/lang/stable/CustomCachingPredicateBenchmark.java | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) 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 555e470f84063..1501695acefad 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 @@ -40,7 +40,7 @@ public final class StableValueImpl implements StableValue { // Generally, fields annotated with `@Stable` are accessed by the JVM using special // memory semantics rules (see `parse.hpp` and `parse(1|2|3).cpp`). // - // This field is reflectively accessed via Unsafe using explicit memory semantics. + // This field is used directly and via Unsafe using explicit memory semantics. // // | Value | Meaning | // | -------------- | ------------ | diff --git a/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingFunctions.java b/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingFunctions.java index 2b18c355d3638..925b41b2dde1a 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingFunctions.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingFunctions.java @@ -54,10 +54,10 @@ static BiFunction cachingBiFunction(Set> inputs, .collect(Collectors.groupingBy(Pair::left, Collectors.mapping(Pair::right, Collectors.toSet()))); - // Map::copyOf is crucial! - final Map> map = Map.copyOf(tToUs.entrySet().stream() + // Collectors.toUnmodifiableMap() is crucial! + final Map> map = tToUs.entrySet().stream() .map(e -> Map.entry(e.getKey(), StableValue.newCachingFunction(e.getValue(), (U u) -> original.apply(e.getKey(), u), null))) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))); + .collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue)); return (T t, U u) -> { final Function function = map.get(t); diff --git a/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingPredicateBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingPredicateBenchmark.java index 58e7a5904e952..40e53b65a84e7 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingPredicateBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingPredicateBenchmark.java @@ -95,7 +95,7 @@ record CachingPredicate(Map> delegate, public CachingPredicate(Set inputs, Predicate original) { this(inputs.stream() - .collect(Collectors.toMap(Function.identity(), _ -> StableValue.newInstance())), + .collect(Collectors.toUnmodifiableMap(Function.identity(), _ -> StableValue.newInstance())), original ); } @@ -108,11 +108,11 @@ public boolean test(T t) { } if (stable.isSet()) { - return stable.isSet(); + return stable.orElseThrow(); } synchronized (this) { if (stable.isSet()) { - return stable.isSet(); + return stable.orElseThrow(); } final boolean r = original.test(t); stable.setOrThrow(r); From 33707c4dfdc49098670e5dc2465ea6c51c45fb89 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Fri, 9 Aug 2024 08:16:31 +0200 Subject: [PATCH 117/327] Switch to identity hash/equals --- .../internal/lang/stable/StableValueImpl.java | 16 +--------- .../lang/StableValue/StableValueTest.java | 31 ++++++------------- 2 files changed, 11 insertions(+), 36 deletions(-) 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 1501695acefad..520a764c76bee 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 @@ -89,21 +89,7 @@ public boolean isSet() { return wrappedValue != null; } - @Override - public int hashCode() { - final Object t = wrappedValue; - return t == this - ? 1 - : Objects.hashCode(t); - } - - @Override - public boolean equals(Object obj) { - return obj instanceof StableValueImpl other && - // Note that the returned `value()` will be `null` if the holder value - // is unset and `nullSentinel()` if the holder value is `null`. - Objects.equals(wrappedValue, other.wrappedValue); - } + // The methods equals() and hashCode() should be based on identity (default) @Override public String toString() { diff --git a/test/jdk/java/lang/StableValue/StableValueTest.java b/test/jdk/java/lang/StableValue/StableValueTest.java index cf47189dafc43..670ca44174d23 100644 --- a/test/jdk/java/lang/StableValue/StableValueTest.java +++ b/test/jdk/java/lang/StableValue/StableValueTest.java @@ -79,26 +79,25 @@ void setNonNull() { @Test void testHashCode() { - StableValue s0 = StableValue.newInstance(); - StableValue s1 = StableValue.newInstance(); - assertEquals(s0.hashCode(), s1.hashCode()); - s0.setOrThrow(42); - s1.setOrThrow(42); - assertEquals(s0.hashCode(), s1.hashCode()); + StableValue stableValue = StableValue.newInstance(); + // Should be Object::hashCode + assertEquals(System.identityHashCode(stableValue), stableValue.hashCode()); } @Test void testEquals() { StableValue s0 = StableValue.newInstance(); StableValue s1 = StableValue.newInstance(); - assertEquals(s0, s1); + assertNotEquals(s0, s1); // Identity based s0.setOrThrow(42); s1.setOrThrow(42); - assertEquals(s0, s1); - StableValue other = StableValue.newInstance(); - other.setOrThrow(13); - assertNotEquals(s0, other); + assertNotEquals(s0, s1); assertNotEquals(s0, "a"); + StableValue null0 = StableValue.newInstance(); + StableValue null1 = StableValue.newInstance(); + null0.setOrThrow(null); + null1.setOrThrow(null); + assertNotEquals(null0, null1); } @Test @@ -111,16 +110,6 @@ void circular() { assertDoesNotThrow((() -> stable.equals(stable))); } - @Test - void hashCodeDependsOnHolderValue() { - StableValue stable = StableValue.newInstance(); - int hBefore = stable.hashCode(); - stable.trySet(42); - int hAfter = stable.hashCode(); - // hashCode() shall change - assertNotEquals(hBefore, hAfter); - } - private static final BiPredicate, Integer> TRY_SET = StableValue::trySet; private static final BiPredicate, Integer> SET_OR_THROW = (s, i) -> { try { From 2bf5334da72557a2dc71d7099ce9ec5a80dd00b2 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Fri, 9 Aug 2024 12:06:20 +0200 Subject: [PATCH 118/327] Improve tests --- .../internal/lang/stable/StableValueImpl.java | 2 +- .../lang/StableValue/CachingFunctionTest.java | 69 ++++++++-------- .../StableValue/CachingIntFunctionTest.java | 33 ++++---- .../lang/StableValue/CachingSupplierTest.java | 46 +++++------ .../lang/StableValue/StableValueTest.java | 78 +++++++++++++------ .../StableValuesSafePublicationTest.java | 6 +- 6 files changed, 138 insertions(+), 96 deletions(-) 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 520a764c76bee..9228229c36487 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 @@ -70,7 +70,7 @@ public T orElseThrow() { if (t != null) { return StableValueUtil.unwrap(t); } - throw new NoSuchElementException("No value set"); + throw new NoSuchElementException("No holder value set"); } @ForceInline diff --git a/test/jdk/java/lang/StableValue/CachingFunctionTest.java b/test/jdk/java/lang/StableValue/CachingFunctionTest.java index a1012aa39c9bf..5fd95bcdfba4a 100644 --- a/test/jdk/java/lang/StableValue/CachingFunctionTest.java +++ b/test/jdk/java/lang/StableValue/CachingFunctionTest.java @@ -39,18 +39,28 @@ final class CachingFunctionTest { + private static final int VALUE = 42; + private static final int VALUE2 = 13; + private static final Set INPUTS = Set.of(VALUE, VALUE2); + private static final Function MAPPER = Function.identity(); + @Test void basic() { - StableTestUtil.CountingFunction cif = new StableTestUtil.CountingFunction<>(i -> i); - var cached = StableValue.newCachingFunction(Set.of(13, 42), cif, null); - assertEquals(42, cached.apply(42)); + basic(MAPPER); + basic(_ -> null); + } + + void basic(Function mapper) { + StableTestUtil.CountingFunction cif = new StableTestUtil.CountingFunction<>(mapper); + var cached = StableValue.newCachingFunction(INPUTS, cif, null); + assertEquals(mapper.apply(VALUE), cached.apply(VALUE)); assertEquals(1, cif.cnt()); - assertEquals(42, cached.apply(42)); + assertEquals(mapper.apply(VALUE), cached.apply(VALUE)); assertEquals(1, cif.cnt()); assertTrue(cached.toString().startsWith("CachingFunction[values={")); // Key order is unspecified - assertTrue(cached.toString().contains("13=.unset")); - assertTrue(cached.toString().contains("42=[42]")); + assertTrue(cached.toString().contains(VALUE2 + "=.unset")); + assertTrue(cached.toString().contains(VALUE + "=[" + mapper.apply(VALUE) + "]")); assertTrue(cached.toString().endsWith(", original=" + cif + "]")); // One between the values and one just before "original" assertEquals(2L, cached.toString().chars().filter(ch -> ch == ',').count()); @@ -61,21 +71,16 @@ void basic() { @Test void background() { final AtomicInteger cnt = new AtomicInteger(0); - ThreadFactory factory = new ThreadFactory() { - @java.lang.Override - public Thread newThread(Runnable r) { - return new Thread(() -> { - r.run(); - cnt.incrementAndGet(); - }); - } - }; - var cached = StableValue.newCachingFunction(Set.of(13, 42), i -> i, factory); + ThreadFactory factory = r -> new Thread(() -> { + r.run(); + cnt.incrementAndGet(); + }); + var cached = StableValue.newCachingFunction(INPUTS, MAPPER, factory); while (cnt.get() < 2) { Thread.onSpinWait(); } - assertEquals(42, cached.apply(42)); - assertEquals(13, cached.apply(13)); + assertEquals(VALUE, cached.apply(VALUE)); + assertEquals(VALUE2, cached.apply(VALUE2)); } @Test @@ -83,22 +88,22 @@ void exception() { StableTestUtil.CountingFunction cif = new StableTestUtil.CountingFunction<>(_ -> { throw new UnsupportedOperationException(); }); - var cached = StableValue.newCachingFunction(Set.of(13, 42), cif, null); - assertThrows(UnsupportedOperationException.class, () -> cached.apply(42)); + var cached = StableValue.newCachingFunction(INPUTS, cif, null); + assertThrows(UnsupportedOperationException.class, () -> cached.apply(VALUE)); assertEquals(1, cif.cnt()); - assertThrows(UnsupportedOperationException.class, () -> cached.apply(42)); + assertThrows(UnsupportedOperationException.class, () -> cached.apply(VALUE)); assertEquals(2, cif.cnt()); assertTrue(cached.toString().startsWith("CachingFunction[values={")); // Key order is unspecified - assertTrue(cached.toString().contains("13=.unset")); - assertTrue(cached.toString().contains("42=.unset")); + assertTrue(cached.toString().contains(VALUE2 + "=.unset")); + assertTrue(cached.toString().contains(VALUE + "=.unset")); assertTrue(cached.toString().endsWith(", original=" + cif + "]")); } @Test void circular() { final AtomicReference> ref = new AtomicReference<>(); - Function> cached = StableValue.newCachingFunction(Set.of(0, 1), _ -> ref.get(), null); + Function> cached = StableValue.newCachingFunction(INPUTS, _ -> ref.get(), null); ref.set(cached); cached.apply(0); String toString = cached.toString(); @@ -109,23 +114,19 @@ void circular() { @Test void equality() { - Set keys = Set.of(13, 42); Function mapper = Function.identity(); - Function f0 = StableValue.newCachingFunction(keys, mapper, null); - Function f1 = StableValue.newCachingFunction(keys, mapper, null); + Function f0 = StableValue.newCachingFunction(INPUTS, mapper, null); + Function f1 = StableValue.newCachingFunction(INPUTS, mapper, null); // No function is equal to another function assertNotEquals(f0, f1); } @Test void hashCodeStable() { - Set keys = Set.of(13, 42); - Function f0 = StableValue.newCachingFunction(keys, Function.identity(), null); - int hBefore = f0.hashCode(); - f0.apply(42); - int hAfter = f0.hashCode(); - // hashCode() shall not change - assertEquals(hBefore, hAfter); + Function f0 = StableValue.newCachingFunction(INPUTS, Function.identity(), null); + assertEquals(System.identityHashCode(f0), f0.hashCode()); + f0.apply(VALUE); + assertEquals(System.identityHashCode(f0), f0.hashCode()); } } diff --git a/test/jdk/java/lang/StableValue/CachingIntFunctionTest.java b/test/jdk/java/lang/StableValue/CachingIntFunctionTest.java index f1b10ac2cbd91..a4570d9f4ee18 100644 --- a/test/jdk/java/lang/StableValue/CachingIntFunctionTest.java +++ b/test/jdk/java/lang/StableValue/CachingIntFunctionTest.java @@ -41,18 +41,26 @@ final class CachingIntFunctionTest { private static final int SIZE = 2; + private static final IntFunction MAPPER = i -> i; @Test void basic() { - StableTestUtil.CountingIntFunction cif = new StableTestUtil.CountingIntFunction<>(i -> i); + basic(MAPPER); + basic(i -> null); + } + + void basic(IntFunction mapper) { + StableTestUtil.CountingIntFunction cif = new StableTestUtil.CountingIntFunction<>(mapper); var cached = StableValue.newCachingIntFunction(SIZE, cif, null); assertEquals("CachingIntFunction[values=[.unset, .unset], original=" + cif + "]", cached.toString()); - assertEquals(1, cached.apply(1)); + assertEquals(mapper.apply(1), cached.apply(1)); assertEquals(1, cif.cnt()); - assertEquals(1, cached.apply(1)); + assertEquals(mapper.apply(1), cached.apply(1)); assertEquals(1, cif.cnt()); - assertEquals("CachingIntFunction[values=[.unset, [1]], original=" + cif + "]", cached.toString()); - assertThrows(IllegalArgumentException.class, () -> cached.apply(SIZE + 1)); + assertEquals("CachingIntFunction[values=[.unset, [" + mapper.apply(1) + "]], original=" + cif + "]", cached.toString()); + assertThrows(IllegalArgumentException.class, () -> cached.apply(SIZE)); + assertThrows(IllegalArgumentException.class, () -> cached.apply(-1)); + assertThrows(IllegalArgumentException.class, () -> cached.apply(1_000_000)); } @Test @@ -67,7 +75,7 @@ public Thread newThread(Runnable r) { }); } }; - var cached = StableValue.newCachingIntFunction(SIZE, i -> i, factory); + var cached = StableValue.newCachingIntFunction(SIZE, MAPPER, factory); while (cnt.get() < 2) { Thread.onSpinWait(); } @@ -102,21 +110,18 @@ void circular() { @Test void equality() { - IntFunction mapper = i -> i; - IntFunction f0 = StableValue.newCachingIntFunction(8, mapper, null); - IntFunction f1 = StableValue.newCachingIntFunction(8, mapper, null); + IntFunction f0 = StableValue.newCachingIntFunction(8, MAPPER, null); + IntFunction f1 = StableValue.newCachingIntFunction(8, MAPPER, null); // No function is equal to another function assertNotEquals(f0, f1); } @Test void hashCodeStable() { - IntFunction f0 = StableValue.newCachingIntFunction(8, i -> i, null); - int hBefore = f0.hashCode(); + IntFunction f0 = StableValue.newCachingIntFunction(8, MAPPER, null); + assertEquals(System.identityHashCode(f0), f0.hashCode()); f0.apply(4); - int hAfter = f0.hashCode(); - // hashCode() shall not change - assertEquals(hBefore, hAfter); + assertEquals(System.identityHashCode(f0), f0.hashCode()); } } diff --git a/test/jdk/java/lang/StableValue/CachingSupplierTest.java b/test/jdk/java/lang/StableValue/CachingSupplierTest.java index 6627e88b36efd..b44080458c9ae 100644 --- a/test/jdk/java/lang/StableValue/CachingSupplierTest.java +++ b/test/jdk/java/lang/StableValue/CachingSupplierTest.java @@ -32,41 +32,44 @@ import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.IntFunction; import java.util.function.Supplier; import static org.junit.jupiter.api.Assertions.*; final class CachingSupplierTest { + private static final Supplier SUPPLIER = () -> 42; + @Test void basic() { - StableTestUtil.CountingSupplier cs = new StableTestUtil.CountingSupplier<>(() -> 42); + basic(SUPPLIER); + basic(() -> null); + } + + void basic(Supplier supplier) { + StableTestUtil.CountingSupplier cs = new StableTestUtil.CountingSupplier<>(supplier); var cached = StableValue.newCachingSupplier(cs, null); assertEquals("CachingSupplier[value=.unset, original=" + cs + "]", cached.toString()); - assertEquals(42, cached.get()); + assertEquals(supplier.get(), cached.get()); assertEquals(1, cs.cnt()); - assertEquals(42, cached.get()); + assertEquals(supplier.get(), cached.get()); assertEquals(1, cs.cnt()); - assertEquals("CachingSupplier[value=[42], original=" + cs + "]", cached.toString()); + assertEquals("CachingSupplier[value=[" + supplier.get() + "], original=" + cs + "]", cached.toString()); } @Test void background() { final AtomicInteger cnt = new AtomicInteger(0); - ThreadFactory factory = new ThreadFactory() { - @Override - public Thread newThread(Runnable r) { - return new Thread(() -> { - r.run(); - cnt.incrementAndGet(); - }); - } - }; - var cached = StableValue.newCachingSupplier(() -> 42, factory); + ThreadFactory factory = r -> new Thread(() -> { + r.run(); + cnt.incrementAndGet(); + }); + var cached = StableValue.newCachingSupplier(SUPPLIER, factory); while (cnt.get() < 1) { Thread.onSpinWait(); } - assertEquals(42, cached.get()); + assertEquals(SUPPLIER.get(), cached.get()); } @Test @@ -95,21 +98,18 @@ void circular() { @Test void equality() { - Supplier mapper = () -> 42; - Supplier f0 = StableValue.newCachingSupplier(mapper, null); - Supplier f1 = StableValue.newCachingSupplier(mapper, null); + Supplier f0 = StableValue.newCachingSupplier(SUPPLIER, null); + Supplier f1 = StableValue.newCachingSupplier(SUPPLIER, null); // No function is equal to another function assertNotEquals(f0, f1); } @Test void hashCodeStable() { - Supplier f0 = StableValue.newCachingSupplier(() -> 42, null); - int hBefore = f0.hashCode(); + Supplier f0 = StableValue.newCachingSupplier(SUPPLIER, null); + assertEquals(System.identityHashCode(f0), f0.hashCode()); f0.get(); - int hAfter = f0.hashCode(); - // hashCode() shall not change - assertEquals(hBefore, hAfter); + assertEquals(System.identityHashCode(f0), f0.hashCode()); } } diff --git a/test/jdk/java/lang/StableValue/StableValueTest.java b/test/jdk/java/lang/StableValue/StableValueTest.java index 670ca44174d23..0b65ff09c274e 100644 --- a/test/jdk/java/lang/StableValue/StableValueTest.java +++ b/test/jdk/java/lang/StableValue/StableValueTest.java @@ -43,38 +43,52 @@ final class StableValueTest { + private static final int VALUE = 42; + private static final int VALUE2 = 13; + @Test - void unset() { + void trySet() { + trySet(VALUE); + trySet(null); + } + + void trySet(Integer initial) { StableValue stable = StableValue.newInstance(); - assertNull(stable.orElse(null)); - assertThrows(NoSuchElementException.class, stable::orElseThrow); - assertEquals("StableValue.unset", stable.toString()); - assertTrue(stable.trySet(42)); + assertTrue(stable.trySet(initial)); assertFalse(stable.trySet(null)); - assertFalse(stable.trySet(42)); - assertFalse(stable.trySet(2)); + assertFalse(stable.trySet(VALUE)); + assertFalse(stable.trySet(VALUE2)); + assertEquals(initial, stable.orElseThrow()); } @Test - void setNull() { + void orElse() { StableValue stable = StableValue.newInstance(); - assertTrue(stable.trySet(null)); - assertEquals("StableValue[null]", stable.toString()); - assertNull(stable.orElse(13)); - assertFalse(stable.trySet(null)); - assertFalse(stable.trySet(1)); + assertEquals(VALUE, stable.orElse(VALUE)); + stable.trySet(VALUE); + assertEquals(VALUE, stable.orElse(VALUE2)); } @Test - void setNonNull() { + void orElseThrow() { StableValue stable = StableValue.newInstance(); - assertTrue(stable.trySet(42)); - assertEquals("StableValue[42]", stable.toString()); - assertEquals(42, stable.orElse(null)); - assertFalse(stable.trySet(null)); - assertFalse(stable.trySet(1)); - assertThrows(IllegalStateException.class, () -> stable.setOrThrow(1)); - assertEquals(42, stable.orElseThrow()); + var e = assertThrows(NoSuchElementException.class, stable::orElseThrow); + assertEquals("No holder value set", e.getMessage()); + stable.trySet(VALUE); + assertEquals(VALUE, stable.orElseThrow()); + } + + @Test + void isSet() { + isSet(VALUE); + isSet(null); + } + + void isSet(Integer initial) { + StableValue stable = StableValue.newInstance(); + assertFalse(stable.isSet()); + stable.trySet(initial); + assertTrue(stable.isSet()); } @Test @@ -101,7 +115,27 @@ void testEquals() { } @Test - void circular() { + void toStringUnset() { + StableValue stable = StableValue.newInstance(); + assertEquals("StableValue.unset", stable.toString()); + } + + @Test + void toStringNull() { + StableValue stable = StableValue.newInstance(); + assertTrue(stable.trySet(null)); + assertEquals("StableValue[null]", stable.toString()); + } + + @Test + void toStringNonNull() { + StableValue stable = StableValue.newInstance(); + assertTrue(stable.trySet(VALUE)); + assertEquals("StableValue[" + VALUE + "]", stable.toString()); + } + + @Test + void toStringCircular() { StableValue> stable = StableValue.newInstance(); stable.trySet(stable); String toString = stable.toString(); diff --git a/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java b/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java index c8a14b0cf18e8..d997a4de286ab 100644 --- a/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java +++ b/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java @@ -58,8 +58,10 @@ static StableValue[] stables() { } static final class Holder { - final int a; - final int b; + // These are non-final fields but should be seen + // fully initialized thanks to the HB properties of StableValue. + int a; + int b; Holder() { a = 1; From f5093529ba08a1e859879c97405553046de1d55c Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Fri, 9 Aug 2024 12:47:12 +0200 Subject: [PATCH 119/327] Fix failing test --- test/jdk/java/lang/StableValue/CachingFunctionTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/jdk/java/lang/StableValue/CachingFunctionTest.java b/test/jdk/java/lang/StableValue/CachingFunctionTest.java index 5fd95bcdfba4a..2686cfede4465 100644 --- a/test/jdk/java/lang/StableValue/CachingFunctionTest.java +++ b/test/jdk/java/lang/StableValue/CachingFunctionTest.java @@ -105,7 +105,7 @@ void circular() { final AtomicReference> ref = new AtomicReference<>(); Function> cached = StableValue.newCachingFunction(INPUTS, _ -> ref.get(), null); ref.set(cached); - cached.apply(0); + cached.apply(VALUE); String toString = cached.toString(); assertTrue(toString.contains("(this CachingFunction)")); assertDoesNotThrow(cached::hashCode); From 70b1a170dbee6178be35c068c484a82a0932b3d5 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Fri, 9 Aug 2024 13:33:13 +0200 Subject: [PATCH 120/327] Add additional tests --- .../java/util/ImmutableCollections.java | 3 ++- .../java/lang/StableValue/LazyListTest.java | 21 +++++++++++++------ .../java/lang/StableValue/LazyMapTest.java | 12 +++++++++-- 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/src/java.base/share/classes/java/util/ImmutableCollections.java b/src/java.base/share/classes/java/util/ImmutableCollections.java index 213a75a72c727..a9532164b5adb 100644 --- a/src/java.base/share/classes/java/util/ImmutableCollections.java +++ b/src/java.base/share/classes/java/util/ImmutableCollections.java @@ -799,7 +799,8 @@ public E get(int i) { } final E newValue = mapper.apply(i); if (!stable.trySet(newValue)) { - throw new IllegalStateException( + // This should never happen + throw new InternalError( "Cannot set the holder value for index " + i + " to " + e + " because a value of " + StableValueUtil.unwrap(stable.wrappedValue()) + " is alredy set."); diff --git a/test/jdk/java/lang/StableValue/LazyListTest.java b/test/jdk/java/lang/StableValue/LazyListTest.java index 79aa3840adaf7..5e2ec5e0553db 100644 --- a/test/jdk/java/lang/StableValue/LazyListTest.java +++ b/test/jdk/java/lang/StableValue/LazyListTest.java @@ -235,9 +235,18 @@ static void assertThrowsForOperation(Class expectedType @Test void serializable() { - assertFalse(newList() instanceof Serializable); - assertFalse(newEmptyList() instanceof Serializable); - assertFalse(newList().subList(1, INDEX) instanceof Serializable); + serializable(newList()); + serializable(newEmptyList()); + } + + void serializable(List list) { + assertFalse(list instanceof Serializable); + if (list.size()>INDEX) { + assertFalse(newList().subList(1, INDEX) instanceof Serializable); + } + assertFalse(list.iterator() instanceof Serializable); + assertFalse(list.reversed() instanceof Serializable); + assertFalse(list.spliterator() instanceof Serializable); } @Test @@ -249,12 +258,12 @@ void randomAccess() { @Test void distinct() { - List> list = StableValueUtil.ofList(13); - assertEquals(13, list.size()); + List> list = StableValueUtil.ofList(SIZE); + assertEquals(SIZE, list.size()); // Check, every StableValue is distinct Map, Boolean> idMap = new IdentityHashMap<>(); list.forEach(e -> idMap.put(e, true)); - assertEquals(13, idMap.size()); + assertEquals(SIZE, idMap.size()); } // Support constructs diff --git a/test/jdk/java/lang/StableValue/LazyMapTest.java b/test/jdk/java/lang/StableValue/LazyMapTest.java index 312f1671f204d..788e56584cab9 100644 --- a/test/jdk/java/lang/StableValue/LazyMapTest.java +++ b/test/jdk/java/lang/StableValue/LazyMapTest.java @@ -39,6 +39,7 @@ import java.util.HashSet; import java.util.IdentityHashMap; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.function.Consumer; import java.util.function.Function; @@ -197,8 +198,15 @@ static void assertThrowsForOperation(Class expectedType @Test void serializable() { - assertFalse(newMap() instanceof Serializable); - assertFalse(newEmptyMap() instanceof Serializable); + serializable(newMap()); + serializable(newEmptyMap()); + } + + void serializable(Map map) { + assertFalse(map instanceof Serializable); + assertFalse(map.entrySet() instanceof Serializable); + assertFalse(map.keySet() instanceof Serializable); + assertFalse(map.values() instanceof Serializable); } @Test From 46586243672ce0a1eaea6ffeebdc1ed672e1f573 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Fri, 9 Aug 2024 14:48:48 +0200 Subject: [PATCH 121/327] Clean up: visibility, formatting etc. --- .../share/classes/java/lang/StableValue.java | 2 +- .../internal/lang/stable/CachingFunction.java | 25 +++++-- .../lang/stable/CachingIntFunction.java | 66 +++++++++++-------- .../internal/lang/stable/CachingSupplier.java | 17 +++-- .../internal/lang/stable/StableValueImpl.java | 15 +++-- .../internal/lang/stable/StableValueUtil.java | 14 ++-- 6 files changed, 88 insertions(+), 51 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 68e32449818b7..0adeab012127a 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -212,7 +212,7 @@ default void setOrThrow(T value) { * @param type of the holder value */ static StableValue newInstance() { - return StableValueImpl.newInstance(); + return StableValueUtil.newInstance(); } /** diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/CachingFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/CachingFunction.java index d9fade33589e6..08a7a91151b88 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/CachingFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/CachingFunction.java @@ -34,8 +34,19 @@ // Note: It would be possible to just use `LazyMap::get` with some additional logic // instead of this class but explicitly providing a class like this provides better // debug capability, exception handling, and may provide better performance. -public record CachingFunction(Map> values, - Function original) implements Function { +/** + * Implementation of a cached Function. + * + * @implNote This implementation can be used early in the boot sequence as it does not + * rely on reflection, MethodHandles, Streams etc. + * + * @param values a delegate map of inputs to StableValue mappings + * @param original the original Function + * @param the type of the input to the function + * @param the type of the result of the function + */ +record CachingFunction(Map> values, + Function original) implements Function { @ForceInline @Override public R apply(T value) { @@ -70,10 +81,10 @@ public boolean equals(Object obj) { @Override public String toString() { - return "CachingFunction[values=" + renderValues() + ", original=" + original + "]"; + return "CachingFunction[values=" + renderMappings() + ", original=" + original + "]"; } - private String renderValues() { + private String renderMappings() { final StringBuilder sb = new StringBuilder(); sb.append("{"); boolean first = true; @@ -84,15 +95,15 @@ private String renderValues() { if (value == this) { sb.append("(this CachingFunction)"); } else { - sb.append(StableValueUtil.render(value)); + sb.append(StableValueUtil.renderWrapped(value)); } } sb.append("}"); return sb.toString(); } - public static CachingFunction of(Set inputs, - Function original) { + static CachingFunction of(Set inputs, + Function original) { return new CachingFunction<>(StableValueUtil.ofMap(inputs), original); } diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/CachingIntFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/CachingIntFunction.java index 0882ee2ebfef7..bb762f38e61db 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/CachingIntFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/CachingIntFunction.java @@ -42,26 +42,14 @@ * For performance reasons (~10%), we are not delegating to a StableList but are using * the more primitive functions in StableValueUtil that are shared with StableList/StableValueImpl. * + * @implNote This implementation can be used early in the boot sequence as it does not + * rely on reflection, MethodHandles, Streams etc. + * * @param the return type */ -public final class CachingIntFunction implements IntFunction { - - @Stable - private final IntFunction original; - @Stable - private final Object[] mutexes; - @Stable - private final Object[] wrappedValues; - - public CachingIntFunction(int size, - IntFunction original) { - this.original = original; - this.mutexes = new Object[size]; - for (int i = 0; i < size; i++) { - mutexes[i] = new Object(); - } - this.wrappedValues = new Object[size]; - } +record CachingIntFunction(IntFunction original, + Object[] mutexes, + Object[] wrappedValues) implements IntFunction { @ForceInline @Override @@ -86,22 +74,38 @@ public R apply(int index) { } } - public static CachingIntFunction of(int size, IntFunction original) { - return new CachingIntFunction<>(size, original); + @Override + public int hashCode() { + return System.identityHashCode(this); + } + + @Override + public boolean equals(Object obj) { + return obj == this; } @Override public String toString() { return "CachingIntFunction[values=" + - "[" + valuesAsString() + "]" - + ", original=" + original + ']'; + renderElements() + + ", original=" + original + ']'; } - private String valuesAsString() { - return IntStream.range(0, wrappedValues.length) - .mapToObj(this::wrappedValue) - .map(v -> (v == this) ? "(this CachingIntFunction)" : StableValueUtil.render(v)) - .collect(Collectors.joining(", ")); + private String renderElements() { + final StringBuilder sb = new StringBuilder(); + sb.append("["); + boolean first = true; + for (int i = 0; i < wrappedValues.length; i++) { + if (first) { first = false; } else { sb.append(", "); }; + final Object value = wrappedValue(i); + if (value == this) { + sb.append("(this CachingIntFunction)"); + } else { + sb.append(StableValueUtil.renderWrapped(value)); + } + } + sb.append("]"); + return sb.toString(); } @ForceInline @@ -109,4 +113,12 @@ private Object wrappedValue(int i) { return StableValueUtil.UNSAFE.getReferenceVolatile(wrappedValues, StableValueUtil.arrayOffset(i)); } + static CachingIntFunction of(int size, IntFunction original) { + var mutexes = new Object[size]; + for (int i = 0; i < size; i++) { + mutexes[i] = new Object(); + } + return new CachingIntFunction<>(original, mutexes, new Object[size]); + } + } diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/CachingSupplier.java b/src/java.base/share/classes/jdk/internal/lang/stable/CachingSupplier.java index abd51535ca761..17c23d4081fe0 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/CachingSupplier.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/CachingSupplier.java @@ -36,9 +36,12 @@ * For performance reasons (~10%), we are not delegating to a StableValue but are using * the more primitive functions in StableValueUtil that are shared with StableValueImpl. * + * @implNote This implementation can be used early in the boot sequence as it does not + * rely on reflection, MethodHandles, Streams etc. + * * @param the return type */ -public final class CachingSupplier implements Supplier { +final class CachingSupplier implements Supplier { private static final long VALUE_OFFSET = StableValueUtil.UNSAFE.objectFieldOffset(CachingSupplier.class, "wrappedValue"); @@ -50,7 +53,7 @@ public final class CachingSupplier implements Supplier { @Stable private volatile Object wrappedValue; - public CachingSupplier(Supplier original) { + private CachingSupplier(Supplier original) { this.original = original; } @@ -72,14 +75,14 @@ public T get() { } } - public static CachingSupplier of(Supplier original) { - return new CachingSupplier<>(original); - } - @Override public String toString() { final Object t = wrappedValue; - return "CachingSupplier[value=" + (t == this ? "(this CachingSupplier)" : StableValueUtil.render(t)) + ", original=" + original + "]"; + return "CachingSupplier[value=" + (t == this ? "(this CachingSupplier)" : StableValueUtil.renderWrapped(t)) + ", original=" + original + "]"; + } + + static CachingSupplier of(Supplier original) { + return new CachingSupplier<>(original); } } 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 9228229c36487..9d888cd65eb6f 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 @@ -29,8 +29,15 @@ import jdk.internal.vm.annotation.Stable; import java.util.NoSuchElementException; -import java.util.Objects; +/** + * The implementation of StableValue. + * + * @implNote This implementation can be used early in the boot sequence as it does not + * rely on reflection, MethodHandles, Streams etc. + * + * @param type of the holder value + */ public final class StableValueImpl implements StableValue { // Unsafe offsets for direct field access @@ -89,14 +96,14 @@ public boolean isSet() { return wrappedValue != null; } - // The methods equals() and hashCode() should be based on identity (default) + // The methods equals() and hashCode() should be based on identity (defaults from Object) @Override public String toString() { final Object t = wrappedValue; return t == this ? "(this StableValue)" - : "StableValue" + StableValueUtil.render(t); + : "StableValue" + StableValueUtil.renderWrapped(t); } @ForceInline @@ -106,7 +113,7 @@ public Object wrappedValue() { // Factory - public static StableValueImpl newInstance() { + static StableValueImpl newInstance() { return new StableValueImpl<>(); } diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableValueUtil.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableValueUtil.java index 13ab8dab2b134..a34f0616dda9c 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableValueUtil.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableValueUtil.java @@ -21,11 +21,11 @@ private StableValueUtil() {} // Used to indicate a holder value is `null` (see field `value` below) // A wrapper method `nullSentinel()` is used for generic type conversion. - static final Object NULL_SENTINEL = new Object(); + private static final Object NULL_SENTINEL = new Object(); // Wraps `null` values into a sentinel value @ForceInline - static T wrap(T t) { + private static T wrap(T t) { return (t == null) ? nullSentinel() : t; } @@ -42,7 +42,7 @@ private static T nullSentinel() { return (T) NULL_SENTINEL; } - static String render(Object t) { + static String renderWrapped(Object t) { return (t == null) ? ".unset" : "[" + unwrap(t) + "]"; } @@ -59,6 +59,10 @@ static long arrayOffset(int index) { // Factories + public static StableValueImpl newInstance() { + return StableValueImpl.newInstance(); + } + public static List> ofList(int size) { if (size < 0) { throw new IllegalArgumentException(); @@ -118,8 +122,8 @@ public static IntFunction newCachingIntFunction(int size, } public static Function newCachingFunction(Set inputs, - Function original, - ThreadFactory factory) { + Function original, + ThreadFactory factory) { final Function memoized = CachingFunction.of(inputs, original); From 5285704064979beacbeb98ca601152f8b7e356dc Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Fri, 9 Aug 2024 15:27:56 +0200 Subject: [PATCH 122/327] Add overloads for background thread resolvers --- .../share/classes/java/lang/StableValue.java | 135 +++++++++++++++--- .../lang/StableValue/CachingFunctionTest.java | 21 ++- .../StableValue/CachingIntFunctionTest.java | 21 ++- .../lang/StableValue/CachingSupplierTest.java | 18 ++- .../lang/stable/CachingFunctionBenchmark.java | 4 +- .../stable/CachingIntFunctionBenchmark.java | 4 +- .../lang/stable/CachingSupplierBenchmark.java | 8 +- .../CustomCachingBiFunctionBenchmark.java | 6 +- .../lang/stable/CustomCachingFunctions.java | 8 +- 9 files changed, 170 insertions(+), 55 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 0adeab012127a..37ffd686310ef 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -65,7 +65,7 @@ * Supplier is guaranteed to be successfully invoked at most once even in a multithreaded * environment, can be created like this: * {@snippet lang = java : - * Supplier cache = StableValue.newCachingSupplier(original, null); + * Supplier cache = StableValue.newCachingSupplier(original); * } * The caching supplier can also be computed by a fresh background thread if a * thread factory is provided as a second parameter as shown here: @@ -80,7 +80,7 @@ * guaranteed to be successfully invoked at most once per inout index even in a * multithreaded environment, can be created like this: * {@snippet lang = java: - * IntFunction cache = StableValue.newCachingIntFunction(size, original, null); + * IntFunction cache = StableValue.newCachingIntFunction(size, original); *} * Just like a caching supplier, a thread factory can be provided as a second parameter * allowing all the values for the allowed input values to be computed by distinct @@ -93,7 +93,7 @@ * at most once per input value even in a multithreaded environment, can be created like * this: * {@snippet lang = java : - * Function cache = StableValue.newCachingFunction(inputs, original, null); + * Function cache = StableValue.newCachingFunction(inputs, original); * } * Just like a caching supplier, a thread factory can be provided as a second parameter * allowing all the values for the allowed input values to be computed by distinct @@ -219,8 +219,35 @@ static StableValue newInstance() { * {@return a new caching, thread-safe, stable, lazily computed * {@linkplain Supplier supplier} that records the value of the provided * {@code original} supplier upon being first accessed via - * {@linkplain Supplier#get() Supplier::get}, or optionally via a background thread - * created from the provided {@code factory} (if non-null)} + * {@linkplain Supplier#get() Supplier::get}} + *

    + * The provided {@code original} supplier is guaranteed to be successfully invoked + * at most once even in a multi-threaded environment. Competing threads invoking the + * {@linkplain Supplier#get() Supplier::get} method when a value is already under + * computation will block until a value is computed or an exception is thrown by the + * computing thread. + *

    + * If the {@code original} Supplier invokes the returned Supplier recursively, + * a StackOverflowError will be thrown when the returned + * Supplier's {@linkplain Supplier#get() Supplier::get} method is invoked. + *

    + * If the provided {@code original} supplier throws an exception, it is relayed + * to the initial caller. + * + * @param original supplier used to compute a memoized value + * @param the type of results supplied by the returned supplier + */ + static Supplier newCachingSupplier(Supplier original) { + Objects.requireNonNull(original); + return StableValueUtil.newCachingSupplier(original, null); + } + + /** + * {@return a new caching, thread-safe, stable, lazily computed + * {@linkplain Supplier supplier} that records the value of the provided + * {@code original} supplier upon being first accessed via + * {@linkplain Supplier#get() Supplier::get}, or via a background thread + * created from the provided {@code factory}} *

    * The provided {@code original} supplier is guaranteed to be successfully invoked * at most once even in a multi-threaded environment. Competing threads invoking the @@ -239,16 +266,14 @@ static StableValue newInstance() { * exception handler}. * * @param original supplier used to compute a memoized value - * @param factory an optional factory that, if non-null, will be used to create - * a background thread that will attempt to compute the memoized - * value. If the factory is {@code null}, no background thread will - * be created. + * @param factory a factory that will be used to create a background thread that will + * attempt to compute the memoized value. * @param the type of results supplied by the returned supplier */ static Supplier newCachingSupplier(Supplier original, ThreadFactory factory) { Objects.requireNonNull(original); - // `factory` is nullable + Objects.requireNonNull(factory); return StableValueUtil.newCachingSupplier(original, factory); } @@ -256,7 +281,40 @@ static Supplier newCachingSupplier(Supplier original, * {@return a new caching, thread-safe, stable, lazily computed * {@link IntFunction } that, for each allowed input, records the values of the * provided {@code original} IntFunction upon being first accessed via - * {@linkplain IntFunction#apply(int) IntFunction::apply}, or optionally via background + * {@linkplain IntFunction#apply(int) IntFunction::apply}} + *

    + * The provided {@code original} IntFunction is guaranteed to be successfully invoked + * at most once per allowed input, even in a multi-threaded environment. Competing + * threads invoking the {@linkplain IntFunction#apply(int) IntFunction::apply} method + * when a value is already under computation will block until a value is computed or + * an exception is thrown by the computing thread. + *

    + * If the {@code original} IntFunction invokes the returned IntFunction recursively + * for a particular input value, a StackOverflowError will be thrown when the returned + * IntFunction's {@linkplain IntFunction#apply(int) IntFunction::apply} method is + * invoked. + *

    + * If the provided {@code original} IntFunction throws an exception, it is relayed + * to the initial caller. + * + * @param size the size of the allowed inputs in {@code [0, size)} + * @param original IntFunction used to compute a memoized value + * @param the type of results delivered by the returned IntFunction + */ + static IntFunction newCachingIntFunction(int size, + IntFunction original) { + if (size < 0) { + throw new IllegalArgumentException(); + } + Objects.requireNonNull(original); + return StableValueUtil.newCachingIntFunction(size, original, null); + } + + /** + * {@return a new caching, thread-safe, stable, lazily computed + * {@link IntFunction } that, for each allowed input, records the values of the + * provided {@code original} IntFunction upon being first accessed via + * {@linkplain IntFunction#apply(int) IntFunction::apply}, or via background * threads created from the provided {@code factory} (if non-null)} *

    * The provided {@code original} IntFunction is guaranteed to be successfully invoked @@ -280,20 +338,18 @@ static Supplier newCachingSupplier(Supplier original, * * @param size the size of the allowed inputs in {@code [0, size)} * @param original IntFunction used to compute a memoized value - * @param factory an optional factory that, if non-null, will be used to create - * {@code size} background threads that will attempt to compute all - * the memoized values. If the provided factory is {@code null}, no - * background threads will be created. + * @param factory a factory that will be used to create {@code size} background + * threads that will attempt to compute all the memoized values. * @param the type of results delivered by the returned IntFunction */ static IntFunction newCachingIntFunction(int size, IntFunction original, ThreadFactory factory) { if (size < 0) { - throw new IllegalStateException(); + throw new IllegalArgumentException(); } Objects.requireNonNull(original); - // `factory` is nullable + Objects.requireNonNull(factory); return StableValueUtil.newCachingIntFunction(size, original, factory); } @@ -324,10 +380,45 @@ static IntFunction newCachingIntFunction(int size, * * @param inputs the set of allowed input values * @param original Function used to compute a memoized value - * @param factory an optional factory that, if non-null, will be used to create - * {@code size} background threads that will attempt to compute the - * memoized values. If the provided factory is {@code null}, no - * background threads will be created. + * @param the type of the input to the returned Function + * @param the type of results delivered by the returned Function + */ + static Function newCachingFunction(Set inputs, + Function original) { + Objects.requireNonNull(inputs); + Objects.requireNonNull(original); + return StableValueUtil.newCachingFunction(inputs, original, null); + } + + /** + * {@return a new caching, thread-safe, stable, lazily computed {@link Function} + * that, for each allowed input in the given set of {@code inputs}, records the + * values of the provided {@code original} Function upon being first accessed via + * {@linkplain Function#apply(Object) Function::apply}, or via background + * threads created from the provided {@code factory} (if non-null)} + *

    + * The provided {@code original} Function is guaranteed to be successfully invoked + * at most once per allowed input, even in a multi-threaded environment. Competing + * threads invoking the {@linkplain Function#apply(Object) Function::apply} method + * when a value is already under computation will block until a value is computed or + * an exception is thrown by the computing thread. + *

    + * If the {@code original} Function invokes the returned Function recursively + * for a particular input value, a StackOverflowError will be thrown when the returned + * Function's {@linkplain Function#apply(Object) Function::apply} method is invoked. + *

    + * If the provided {@code original} Function throws an exception, it is relayed + * to the initial caller. If the memoized Function is computed by a background + * thread, exceptions from the provided {@code original} Function will be relayed to + * the background thread's {@linkplain Thread#getUncaughtExceptionHandler() uncaught + * exception handler}. + *

    + * The order in which background threads are started is unspecified. + * + * @param inputs the set of allowed input values + * @param original Function used to compute a memoized value + * @param factory a factory that will be used to create {@code size} background + * threads that will attempt to compute the memoized values. * @param the type of the input to the returned Function * @param the type of results delivered by the returned Function */ @@ -336,7 +427,7 @@ static Function newCachingFunction(Set inputs, ThreadFactory factory) { Objects.requireNonNull(inputs); Objects.requireNonNull(original); - // `factory` is nullable + Objects.requireNonNull(factory); return StableValueUtil.newCachingFunction(inputs, original, factory); } diff --git a/test/jdk/java/lang/StableValue/CachingFunctionTest.java b/test/jdk/java/lang/StableValue/CachingFunctionTest.java index 2686cfede4465..4974b8202a5c1 100644 --- a/test/jdk/java/lang/StableValue/CachingFunctionTest.java +++ b/test/jdk/java/lang/StableValue/CachingFunctionTest.java @@ -44,6 +44,15 @@ final class CachingFunctionTest { private static final Set INPUTS = Set.of(VALUE, VALUE2); private static final Function MAPPER = Function.identity(); + @Test + void factoryInvariants() { + assertThrows(NullPointerException.class, () -> StableValue.newCachingFunction(null, MAPPER)); + assertThrows(NullPointerException.class, () -> StableValue.newCachingFunction(INPUTS, null)); + assertThrows(NullPointerException.class, () -> StableValue.newCachingFunction(null, MAPPER, Thread.ofVirtual().factory())); + assertThrows(NullPointerException.class, () -> StableValue.newCachingFunction(INPUTS, null, Thread.ofVirtual().factory())); + assertThrows(NullPointerException.class, () -> StableValue.newCachingFunction(INPUTS, MAPPER, null)); + } + @Test void basic() { basic(MAPPER); @@ -52,7 +61,7 @@ void basic() { void basic(Function mapper) { StableTestUtil.CountingFunction cif = new StableTestUtil.CountingFunction<>(mapper); - var cached = StableValue.newCachingFunction(INPUTS, cif, null); + var cached = StableValue.newCachingFunction(INPUTS, cif); assertEquals(mapper.apply(VALUE), cached.apply(VALUE)); assertEquals(1, cif.cnt()); assertEquals(mapper.apply(VALUE), cached.apply(VALUE)); @@ -88,7 +97,7 @@ void exception() { StableTestUtil.CountingFunction cif = new StableTestUtil.CountingFunction<>(_ -> { throw new UnsupportedOperationException(); }); - var cached = StableValue.newCachingFunction(INPUTS, cif, null); + var cached = StableValue.newCachingFunction(INPUTS, cif); assertThrows(UnsupportedOperationException.class, () -> cached.apply(VALUE)); assertEquals(1, cif.cnt()); assertThrows(UnsupportedOperationException.class, () -> cached.apply(VALUE)); @@ -103,7 +112,7 @@ void exception() { @Test void circular() { final AtomicReference> ref = new AtomicReference<>(); - Function> cached = StableValue.newCachingFunction(INPUTS, _ -> ref.get(), null); + Function> cached = StableValue.newCachingFunction(INPUTS, _ -> ref.get()); ref.set(cached); cached.apply(VALUE); String toString = cached.toString(); @@ -115,15 +124,15 @@ void circular() { @Test void equality() { Function mapper = Function.identity(); - Function f0 = StableValue.newCachingFunction(INPUTS, mapper, null); - Function f1 = StableValue.newCachingFunction(INPUTS, mapper, null); + Function f0 = StableValue.newCachingFunction(INPUTS, mapper); + Function f1 = StableValue.newCachingFunction(INPUTS, mapper); // No function is equal to another function assertNotEquals(f0, f1); } @Test void hashCodeStable() { - Function f0 = StableValue.newCachingFunction(INPUTS, Function.identity(), null); + Function f0 = StableValue.newCachingFunction(INPUTS, Function.identity()); assertEquals(System.identityHashCode(f0), f0.hashCode()); f0.apply(VALUE); assertEquals(System.identityHashCode(f0), f0.hashCode()); diff --git a/test/jdk/java/lang/StableValue/CachingIntFunctionTest.java b/test/jdk/java/lang/StableValue/CachingIntFunctionTest.java index a4570d9f4ee18..38509f89a2a45 100644 --- a/test/jdk/java/lang/StableValue/CachingIntFunctionTest.java +++ b/test/jdk/java/lang/StableValue/CachingIntFunctionTest.java @@ -43,6 +43,15 @@ final class CachingIntFunctionTest { private static final int SIZE = 2; private static final IntFunction MAPPER = i -> i; + @Test + void factoryInvariants() { + assertThrows(IllegalArgumentException.class, () -> StableValue.newCachingIntFunction(-1, MAPPER)); + assertThrows(NullPointerException.class, () -> StableValue.newCachingIntFunction(SIZE, null)); + assertThrows(IllegalArgumentException.class, () -> StableValue.newCachingIntFunction(-1, MAPPER, Thread.ofVirtual().factory())); + assertThrows(NullPointerException.class, () -> StableValue.newCachingIntFunction(SIZE, null, Thread.ofVirtual().factory())); + assertThrows(NullPointerException.class, () -> StableValue.newCachingIntFunction(SIZE, MAPPER, null)); + } + @Test void basic() { basic(MAPPER); @@ -51,7 +60,7 @@ void basic() { void basic(IntFunction mapper) { StableTestUtil.CountingIntFunction cif = new StableTestUtil.CountingIntFunction<>(mapper); - var cached = StableValue.newCachingIntFunction(SIZE, cif, null); + var cached = StableValue.newCachingIntFunction(SIZE, cif); assertEquals("CachingIntFunction[values=[.unset, .unset], original=" + cif + "]", cached.toString()); assertEquals(mapper.apply(1), cached.apply(1)); assertEquals(1, cif.cnt()); @@ -88,7 +97,7 @@ void exception() { StableTestUtil.CountingIntFunction cif = new StableTestUtil.CountingIntFunction<>(_ -> { throw new UnsupportedOperationException(); }); - var cached = StableValue.newCachingIntFunction(SIZE, cif, null); + var cached = StableValue.newCachingIntFunction(SIZE, cif); assertThrows(UnsupportedOperationException.class, () -> cached.apply(1)); assertEquals(1, cif.cnt()); assertThrows(UnsupportedOperationException.class, () -> cached.apply(1)); @@ -99,7 +108,7 @@ void exception() { @Test void circular() { final AtomicReference> ref = new AtomicReference<>(); - IntFunction> cached = StableValue.newCachingIntFunction(SIZE, _ -> ref.get(), null); + IntFunction> cached = StableValue.newCachingIntFunction(SIZE, _ -> ref.get()); ref.set(cached); cached.apply(0); String toString = cached.toString(); @@ -110,15 +119,15 @@ void circular() { @Test void equality() { - IntFunction f0 = StableValue.newCachingIntFunction(8, MAPPER, null); - IntFunction f1 = StableValue.newCachingIntFunction(8, MAPPER, null); + IntFunction f0 = StableValue.newCachingIntFunction(8, MAPPER); + IntFunction f1 = StableValue.newCachingIntFunction(8, MAPPER); // No function is equal to another function assertNotEquals(f0, f1); } @Test void hashCodeStable() { - IntFunction f0 = StableValue.newCachingIntFunction(8, MAPPER, null); + IntFunction f0 = StableValue.newCachingIntFunction(8, MAPPER); assertEquals(System.identityHashCode(f0), f0.hashCode()); f0.apply(4); assertEquals(System.identityHashCode(f0), f0.hashCode()); diff --git a/test/jdk/java/lang/StableValue/CachingSupplierTest.java b/test/jdk/java/lang/StableValue/CachingSupplierTest.java index b44080458c9ae..c542c61863fc6 100644 --- a/test/jdk/java/lang/StableValue/CachingSupplierTest.java +++ b/test/jdk/java/lang/StableValue/CachingSupplierTest.java @@ -41,6 +41,12 @@ final class CachingSupplierTest { private static final Supplier SUPPLIER = () -> 42; + @Test + void factoryInvariants() { + assertThrows(IllegalArgumentException.class, () -> StableValue.newCachingSupplier(null)); + assertThrows(NullPointerException.class, () -> StableValue.newCachingSupplier(SUPPLIER, null)); + } + @Test void basic() { basic(SUPPLIER); @@ -49,7 +55,7 @@ void basic() { void basic(Supplier supplier) { StableTestUtil.CountingSupplier cs = new StableTestUtil.CountingSupplier<>(supplier); - var cached = StableValue.newCachingSupplier(cs, null); + var cached = StableValue.newCachingSupplier(cs); assertEquals("CachingSupplier[value=.unset, original=" + cs + "]", cached.toString()); assertEquals(supplier.get(), cached.get()); assertEquals(1, cs.cnt()); @@ -77,7 +83,7 @@ void exception() { StableTestUtil.CountingSupplier cs = new StableTestUtil.CountingSupplier<>(() -> { throw new UnsupportedOperationException(); }); - var cached = StableValue.newCachingSupplier(cs, null); + var cached = StableValue.newCachingSupplier(cs); assertThrows(UnsupportedOperationException.class, cached::get); assertEquals(1, cs.cnt()); assertThrows(UnsupportedOperationException.class, cached::get); @@ -88,7 +94,7 @@ void exception() { @Test void circular() { final AtomicReference> ref = new AtomicReference<>(); - Supplier> cached = StableValue.newCachingSupplier(ref::get, null); + Supplier> cached = StableValue.newCachingSupplier(ref::get); ref.set(cached); cached.get(); String toString = cached.toString(); @@ -98,15 +104,15 @@ void circular() { @Test void equality() { - Supplier f0 = StableValue.newCachingSupplier(SUPPLIER, null); - Supplier f1 = StableValue.newCachingSupplier(SUPPLIER, null); + Supplier f0 = StableValue.newCachingSupplier(SUPPLIER); + Supplier f1 = StableValue.newCachingSupplier(SUPPLIER); // No function is equal to another function assertNotEquals(f0, f1); } @Test void hashCodeStable() { - Supplier f0 = StableValue.newCachingSupplier(SUPPLIER, null); + Supplier f0 = StableValue.newCachingSupplier(SUPPLIER); assertEquals(System.identityHashCode(f0), f0.hashCode()); f0.get(); assertEquals(System.identityHashCode(f0), f0.hashCode()); diff --git a/test/micro/org/openjdk/bench/java/lang/stable/CachingFunctionBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/CachingFunctionBenchmark.java index da728b63a77bc..e888fd5d3cbec 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/CachingFunctionBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/CachingFunctionBenchmark.java @@ -62,10 +62,10 @@ public class CachingFunctionBenchmark { private static final Set SET = IntStream.range(0, SIZE).boxed().collect(Collectors.toSet()); private static final Map STABLE = StableValue.lazyMap(SET, Function.identity()); - private static final Function FUNCTION = StableValue.newCachingFunction(SET, Function.identity(), null); + private static final Function FUNCTION = StableValue.newCachingFunction(SET, Function.identity()); private final Map stable = StableValue.lazyMap(SET, Function.identity()); - private final Function function = StableValue.newCachingFunction(SET, Function.identity(), null); + private final Function function = StableValue.newCachingFunction(SET, Function.identity()); @Benchmark public int stable() { diff --git a/test/micro/org/openjdk/bench/java/lang/stable/CachingIntFunctionBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/CachingIntFunctionBenchmark.java index 9a1e91e93d368..96e784968d564 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/CachingIntFunctionBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/CachingIntFunctionBenchmark.java @@ -59,10 +59,10 @@ public class CachingIntFunctionBenchmark { private static final IntFunction IDENTITY = i -> i; private static final List STABLE = StableValue.lazyList(SIZE, IDENTITY); - private static final IntFunction INT_FUNCTION = StableValue.newCachingIntFunction(SIZE, IDENTITY, null); + private static final IntFunction INT_FUNCTION = StableValue.newCachingIntFunction(SIZE, IDENTITY); private final List stable = StableValue.lazyList(SIZE, IDENTITY); - private final IntFunction intFunction = StableValue.newCachingIntFunction(SIZE, IDENTITY, null); + private final IntFunction intFunction = StableValue.newCachingIntFunction(SIZE, IDENTITY); @Benchmark public int stable() { diff --git a/test/micro/org/openjdk/bench/java/lang/stable/CachingSupplierBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/CachingSupplierBenchmark.java index 2710a46fb8ea8..2f8a334f3dc96 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/CachingSupplierBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/CachingSupplierBenchmark.java @@ -59,13 +59,13 @@ public class CachingSupplierBenchmark { private static final StableValue STABLE = init(StableValue.newInstance(), VALUE); private static final StableValue STABLE2 = init(StableValue.newInstance(), VALUE2); - private static final Supplier SUPPLIER = StableValue.newCachingSupplier(() -> VALUE, null); - private static final Supplier SUPPLIER2 = StableValue.newCachingSupplier(() -> VALUE, null); + private static final Supplier SUPPLIER = StableValue.newCachingSupplier(() -> VALUE); + private static final Supplier SUPPLIER2 = StableValue.newCachingSupplier(() -> VALUE); private final StableValue stable = init(StableValue.newInstance(), VALUE); private final StableValue stable2 = init(StableValue.newInstance(), VALUE2); - private final Supplier supplier = StableValue.newCachingSupplier(() -> VALUE, null); - private final Supplier supplier2 = StableValue.newCachingSupplier(() -> VALUE2, null); + private final Supplier supplier = StableValue.newCachingSupplier(() -> VALUE); + private final Supplier supplier2 = StableValue.newCachingSupplier(() -> VALUE2); @Benchmark public int stable() { diff --git a/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingBiFunctionBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingBiFunctionBenchmark.java index cfbf6ceacf5e0..5a35d85841632 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingBiFunctionBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingBiFunctionBenchmark.java @@ -121,7 +121,7 @@ public int staticStableValue() { // Pair seams to not work that well... static BiFunction cachingBiFunction2(Set> inputs, BiFunction original) { - final Function, R> delegate = StableValue.newCachingFunction(inputs, p -> original.apply(p.left(), p.right()), null); + final Function, R> delegate = StableValue.newCachingFunction(inputs, p -> original.apply(p.left(), p.right())); return (T t, U u) -> delegate.apply(new Pair<>(t, u)); } @@ -132,7 +132,7 @@ static BiFunction cachingBiFunction2(Set> inputs, record CachingBiFunction2(Function, R> delegate) implements BiFunction { public CachingBiFunction2(Set> inputs, BiFunction original) { - this(StableValue.newCachingFunction(inputs, (Pair p) -> original.apply(p.left(), p.right()), null)); + this(StableValue.newCachingFunction(inputs, (Pair p) -> original.apply(p.left(), p.right()))); } @Override @@ -253,7 +253,7 @@ static Map> delegate(Set> copy = Map.ofEntries(map.entrySet().stream() - .map(e -> Map.entry(e.getKey(), StableValue.newCachingFunction( e.getValue().keySet(), (U u) -> original.apply(e.getKey(), u) ,null))) + .map(e -> Map.entry(e.getKey(), StableValue.newCachingFunction( e.getValue().keySet(), (U u) -> original.apply(e.getKey(), u)))) .toArray(Map.Entry[]::new)); return copy; diff --git a/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingFunctions.java b/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingFunctions.java index 925b41b2dde1a..7531de6973ffa 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingFunctions.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingFunctions.java @@ -43,7 +43,7 @@ record Pair(L left, R right){} static Predicate cachingPredicate(Set inputs, Predicate original) { - final Function delegate = StableValue.newCachingFunction(inputs, original::test, null); + final Function delegate = StableValue.newCachingFunction(inputs, original::test); return delegate::apply; } @@ -56,7 +56,7 @@ static BiFunction cachingBiFunction(Set> inputs, // Collectors.toUnmodifiableMap() is crucial! final Map> map = tToUs.entrySet().stream() - .map(e -> Map.entry(e.getKey(), StableValue.newCachingFunction(e.getValue(), (U u) -> original.apply(e.getKey(), u), null))) + .map(e -> Map.entry(e.getKey(), StableValue.newCachingFunction(e.getValue(), (U u) -> original.apply(e.getKey(), u)))) .collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue)); return (T t, U u) -> { @@ -83,13 +83,13 @@ static BinaryOperator cachingBinaryOperator(Set> inputs, static UnaryOperator cachingUnaryOperator(Set inputs, UnaryOperator original) { - final Function function = StableValue.newCachingFunction(inputs, original, null); + final Function function = StableValue.newCachingFunction(inputs, original); return function::apply; } static IntSupplier cachingIntSupplier(IntSupplier original) { - final Supplier delegate = StableValue.newCachingSupplier(original::getAsInt, null); + final Supplier delegate = StableValue.newCachingSupplier(original::getAsInt); return delegate::get; } From a5217d72f0975d70ec6e03f98ec2203b10da566b Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Fri, 9 Aug 2024 15:30:19 +0200 Subject: [PATCH 123/327] Update JEP to cover the new overloads --- test/jdk/java/lang/StableValue/JEP.md | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/test/jdk/java/lang/StableValue/JEP.md b/test/jdk/java/lang/StableValue/JEP.md index 4d2e2ed4a9691..0fc73ee820a88 100644 --- a/test/jdk/java/lang/StableValue/JEP.md +++ b/test/jdk/java/lang/StableValue/JEP.md @@ -232,7 +232,7 @@ class Foo { // 1. Centrally declare a caching supplier and define how it should be computed private static final Supplier LOGGER = - StableValue.newCachingSupplier( () -> Logger.getLogger("com.company.Foo"), null); + StableValue.newCachingSupplier( () -> Logger.getLogger("com.company.Foo")); static Logger logger() { // 2. Access the cached value with as-declared-final performance @@ -244,9 +244,6 @@ class Foo { } ``` -Note: the last `null` parameter signifies an optional thread factory that will be explained at the end -of this chapter. - In the example above, the original `Supplier` provided is invoked at most once per loading of the containing class `Foo` (`Foo`, in turn, can be loaded at most once into any given `ClassLoader`) and it is backed by a lazily computed stable value. @@ -261,7 +258,7 @@ class CachedNum { // 1. Centrally declare a caching IntFunction backed by a list of StableValue elements private static final IntFunction LOGGERS = - StableValue.newCachingIntFunction(2, CachedNum::fromNumber, null); + StableValue.newCachingIntFunction(2, CachedNum::fromNumber); // 2. Define a function that is to be called the first // time a particular message number is referenced @@ -282,8 +279,6 @@ class CachedNum { } ``` -Note: Again, the last null parameter signifies an optional thread factory that will be explained at the end of this chapter. - As can be seen, manually mapping numbers to strings is a bit tedious. This brings us to the most general caching function variant provided is a caching `Function` which, for example, can make sure `Logger::getLogger` in one of the first examples above is invoked at most once per input value (provided it executes successfully) in a multithreaded environment. Such a @@ -323,7 +318,7 @@ to write such constructs in a few lines. #### Background threads -As noted above, the caching-returning factories in the Stable Values API offers an optional +The caching-returning factory overloads in the Stable Values API offers an optional tailing thread factory parameter from which new value-computing background threads will be created: ``` From b0e3b8d2124e380d30db037e5a4d0d825365a071 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Fri, 9 Aug 2024 15:32:32 +0200 Subject: [PATCH 124/327] Fix error in test --- test/jdk/java/lang/StableValue/CachingSupplierTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/jdk/java/lang/StableValue/CachingSupplierTest.java b/test/jdk/java/lang/StableValue/CachingSupplierTest.java index c542c61863fc6..881a495d3e8db 100644 --- a/test/jdk/java/lang/StableValue/CachingSupplierTest.java +++ b/test/jdk/java/lang/StableValue/CachingSupplierTest.java @@ -43,7 +43,7 @@ final class CachingSupplierTest { @Test void factoryInvariants() { - assertThrows(IllegalArgumentException.class, () -> StableValue.newCachingSupplier(null)); + assertThrows(NullPointerException.class, () -> StableValue.newCachingSupplier(null)); assertThrows(NullPointerException.class, () -> StableValue.newCachingSupplier(SUPPLIER, null)); } From 671dd084d21a6592b724051d14db030f9d2e7a4d Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 12 Aug 2024 09:48:02 +0200 Subject: [PATCH 125/327] Add test for recursive invocation --- .../share/classes/java/util/ImmutableCollections.java | 3 ++- test/jdk/java/lang/StableValue/LazyListTest.java | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/java.base/share/classes/java/util/ImmutableCollections.java b/src/java.base/share/classes/java/util/ImmutableCollections.java index a9532164b5adb..62e524660c284 100644 --- a/src/java.base/share/classes/java/util/ImmutableCollections.java +++ b/src/java.base/share/classes/java/util/ImmutableCollections.java @@ -799,7 +799,8 @@ public E get(int i) { } final E newValue = mapper.apply(i); if (!stable.trySet(newValue)) { - // This should never happen + // This should never happen. Not even if the mapper recursively + // call itself (see LazyListTest::recursive). throw new InternalError( "Cannot set the holder value for index " + i + " to " + e + " because a value of " + StableValueUtil.unwrap(stable.wrappedValue()) + diff --git a/test/jdk/java/lang/StableValue/LazyListTest.java b/test/jdk/java/lang/StableValue/LazyListTest.java index 5e2ec5e0553db..5a0999cf7dcf9 100644 --- a/test/jdk/java/lang/StableValue/LazyListTest.java +++ b/test/jdk/java/lang/StableValue/LazyListTest.java @@ -43,6 +43,7 @@ import java.util.RandomAccess; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import java.util.function.IntFunction; import java.util.stream.IntStream; @@ -200,6 +201,15 @@ void iteratorPartial() { assertThrows(NoSuchElementException.class, iterator::next); } + + @Test + void recursiveCall() { + AtomicReference> ref = new AtomicReference<>(); + var lazy = StableValue.lazyList(SIZE, i -> ref.get().apply(i)); + ref.set(lazy::get); + assertThrows(StackOverflowError.class, () -> lazy.get(INDEX)); + } + // Immutability @ParameterizedTest From db77d00067a7099d1a414db3a86c7ecf9f5177d7 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Thu, 15 Aug 2024 16:19:02 +0200 Subject: [PATCH 126/327] Update JEP --- test/jdk/java/lang/StableValue/JEP.md | 65 ++++++++++++++++----------- 1 file changed, 40 insertions(+), 25 deletions(-) diff --git a/test/jdk/java/lang/StableValue/JEP.md b/test/jdk/java/lang/StableValue/JEP.md index 0fc73ee820a88..1e69a0113cf48 100644 --- a/test/jdk/java/lang/StableValue/JEP.md +++ b/test/jdk/java/lang/StableValue/JEP.md @@ -51,44 +51,35 @@ As the `logger` field is private, the only way for clients to access it is to ca Unfortunately, the above solution does not work in a multithreaded environment. For instance, updates to the `logger` field made by one thread may not be immediately visible to other threads. This condition might result in multiple concurrent calls to the `Logger::create` method, thereby violating the "at-most-once" update guarantee. -##### Thread safety with double-checked locking +##### Thread safety with synchronized -One possible way to achieve thread safety would be to serialize access to the `Application::getLogger` method - i.e. by marking that method as `synchronized`. However, doing so has a performance cost, as multiple threads cannot concurrently obtain the application's logger object, even *long after* this object has been computed, and safely stored in the `logger` field. In other words, using `synchronized` amounts at applying a *permanent* performance tax on *all* logger accesses, in the rare event that a race occurs during the initial update of the `logger` field. - -A more efficient solution is the so-called [class holder idiom](https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom), which achieves thread safe "at-most-once" update guarantees by leaning on the lazy semantics of class initialization. However, for this approach to work correctly, the memoized data should be `static`, which is not the case here. - -In order to achieve thread safety without compromising performance, developers often resort to the brittle [double-checked idiom](https://en.wikipedia.org/wiki/Double-checked_locking): +One possible way to achieve thread safety would be to serialize access to the `Application::getLogger` method - i.e. by marking that method as `synchronized`. ``` -class Application { +public class Application { - private volatile Logger logger; + private Logger logger; - public Logger logger() { - Logger v = logger; - if (v == null) { - synchronized (this) { - v = logger; - if (v == null) { - logger = v = Logger.create("com.company.Application"); - } - } - } - return v; + public synchronized Logger getLogger() { + if (logger == null) { + logger = Logger.create("com.company.Application"); } + return logger; + } } ``` -The basic idea behind double-checked locking is to reduce the chances for callers to enter a `synchronized` block. After all, in the common case, we expect the `logger` field to already contain a logger object, in which case we can just return that object, without any performance hit. In the rare event where `logger` is not set, we must enter a `synchronized` block, and check its value again (as such value might have changed upon entering the block). For the double-checked idiom to work correctly, it is necessary for the `logger` field is marked as `volatile`. This ensures that reading that field across multiple threads can result in one of two outcomes: the field either appears to be uninitialized (its value set to `null`), or initialized (its value set to the final logger object). That is, no *dirty reads* are possible. +However, doing so has a performance cost, as multiple threads cannot concurrently obtain the application's logger object, even *long after* this object has been computed, and safely stored in the `logger` field. In other words, using `synchronized` amounts at applying a *permanent* performance tax on *all* logger accesses, in the rare event that a race occurs during the initial update of the `logger` field. + +A more efficient solution is the so-called [class holder idiom](https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom), which achieves thread safe "at-most-once" update guarantees by leaning on the lazy semantics of class initialization. However, for this approach to work correctly, the memoized data should be `static`, which is not the case here. Instead, for instance variables, it is also possible to resort to the brittle [double-checked idiom](https://en.wikipedia.org/wiki/Double-checked_locking) further described in the Alternative section below. -##### Problems with double-checked locking +##### Problems with synchronized -Unfortunately, double-checked locking has several inherent design flaws: +Unfortunately, synchronized locking has several inherent design flaws: -* *brittleness* - the convoluted nature of the code required to write a correct double-checked locking makes it all too easy for developers to make subtle mistakes. A very common one is forgetting to add the `volatile` keyword to the `logger` field. -* *lack of expressiveness* - even when written correctly, the double-checked idiom leaves a lot to be desired. The "at-most-once" mutation guarantee is not explicitly manifest in the code: after all the `logger` field is just a plain mutable field. This leaves important semantics gaps that is impossible to plug. For example, the `logger` field can be accidentally mutated in another method of the `Application` class. In another example, the field might be reflectively mutated using `setAccessible`. Avoiding these pitfalls is ultimately left to developers. +* *lack of expressiveness* - he synchronized idiom leaves a lot to be desired. The "at-most-once" mutation guarantee is not explicitly manifest in the code: after all the `logger` field is just a plain mutable field. This leaves important semantics gaps that is impossible to plug. For example, the `logger` field can be accidentally mutated in another method of the `Application` class. In another example, the field might be reflectively mutated using `setAccessible`. Avoiding these pitfalls is ultimately left to developers. * *lack of optimizations* - as the `logger` field is updated at most once, one might expect the JVM to optimize access to this field accordingly, e.g. by [constant-folding](https://en.wikipedia.org/wiki/Constant_folding) access to an already-initialized `logger` field. Unfortunately, since `logger` is just a plan mutable field, the JVM cannot trust the field to never be updated again. As such, access to at-most-once fields, when realized with double-checked locking is not as efficient as it could be. -* *limited applicability* - double-checked locking fails to scale to more complex use cases where e.g. the client might need an *array* of values where each element can be updated at most once. In this case, marking the array field as `volatile` is not enough, as the `volatile` modifier doesn't apply to the array *elements* but to the array as a whole. Instead, clients would have to resort to even more complex solutions using where at-most-once array elements are accessed using `VarHandles`. Needless to say, such solutions are even more brittle and error-prone, and should be avoided at all costs. +* *limited applicability* - synchronized locking fails to scale to more complex use cases where e.g. the client might need an *array* of values where each element can be updated at most once. Access to one element will block access to _all_ the other elements unless there is a distinct synchronization object for each element. ##### At-most-once as a first-class concept @@ -429,6 +420,30 @@ There are other classes in the JDK that support lazy computation including `Map` and `ThreadLocal` all of which, unfortunately, support arbitrary mutation and thus, hinder the JVM from reasoning about constantness thereby preventing constant folding and other optimizations. +Another alternative is to resort to the brittle [double-checked idiom](https://en.wikipedia.org/wiki/Double-checked_locking): + +``` +class Application { + + private volatile Logger logger; + + public Logger logger() { + Logger v = logger; + if (v == null) { + synchronized (this) { + v = logger; + if (v == null) { + logger = v = Logger.create("com.company.Application"); + } + } + } + return v; + } +} +``` + +The basic idea behind double-checked locking is to reduce the chances for callers to enter a `synchronized` block. After all, in the common case, we expect the `logger` field to already contain a logger object, in which case we can just return that object, without any performance hit. In the rare event where `logger` is not set, we must enter a `synchronized` block, and check its value again (as such value might have changed upon entering the block). For the double-checked idiom to work correctly, it is necessary for the `logger` field is marked as `volatile`. This ensures that reading that field across multiple threads can result in one of two outcomes: the field either appears to be uninitialized (its value set to `null`), or initialized (its value set to the final logger object). That is, no *dirty reads* are possible. As with all existing solutions, the double-checked locking idiom does not provide constant folding. + So, alternatives would be to keep using explicit double-checked locking, maps, holder classes, Atomic classes, and third-party frameworks. Another alternative would be to add language support for immutable value holders. From 47a1a779981fdbb79026564aacf0dc8175e73069 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 4 Sep 2024 16:05:24 +0200 Subject: [PATCH 127/327] Refactor stable value --- .../share/classes/java/lang/StableValue.java | 130 ++++++++++++++++-- .../java/util/ImmutableCollections.java | 64 +++------ .../internal/lang/stable/CachingFunction.java | 18 +-- .../lang/stable/CachingIntFunction.java | 42 ++---- .../internal/lang/stable/CachingSupplier.java | 47 ++----- ...lueUtil.java => StableValueFactories.java} | 71 ++-------- .../internal/lang/stable/StableValueImpl.java | 120 +++++++++++++++- .../java/lang/StableValue/LazyListTest.java | 10 +- .../java/lang/StableValue/LazyMapTest.java | 5 +- 9 files changed, 298 insertions(+), 209 deletions(-) rename src/java.base/share/classes/jdk/internal/lang/stable/{StableValueUtil.java => StableValueFactories.java} (54%) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 37ffd686310ef..ed71373f634bf 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -28,7 +28,7 @@ import jdk.internal.access.SharedSecrets; import jdk.internal.javac.PreviewFeature; import jdk.internal.lang.stable.StableValueImpl; -import jdk.internal.lang.stable.StableValueUtil; +import jdk.internal.lang.stable.StableValueFactories; import java.io.Serializable; import java.util.List; @@ -42,8 +42,8 @@ import java.util.function.Supplier; /** - * A thin, atomic, non-blocking, thread-safe, set-at-most-once, stable value holder - * eligible for certain JVM optimizations if set to a value. + * A thin, atomic, thread-safe, set-at-most-once, stable value holder eligible for + * certain JVM optimizations if set to a value. *

    * A stable value is said to be monotonic because the state of a stable value can only go * from unset to set and consequently, a value can only be set @@ -204,6 +204,116 @@ default void setOrThrow(T value) { } } + /** + * {@return the set holder value if set, otherwise attempts to compute and set a + * new (nullable) value using the provided {@code supplier}, returning the + * (pre-existing or newly set) value} + *

    + * The provided {@code supplier} is guaranteed to be invoked at most once if it + * completes without throwing an exception. + *

    + * If the supplier throws an (unchecked) exception, the exception is rethrown, and no + * value is set. The most common usage is to construct a new object serving as a + * lazily computed value or memoized result, as in: + * + *

     {@code
    +     * Value witness = stable.computeIfUnset(Value::new);
    +     * }
    + * + * @implSpec The implementation logic is equivalent to the following steps for this + * {@code stable}: + * + *
     {@code
    +     * if (stable.isSet()) {
    +     *     return stable.get();
    +     * } else {
    +     *     V newValue = supplier.get();
    +     *     stable.setOrThrow(newValue);
    +     *     return newValue;
    +     * }
    +     * }
    + * Except it is thread-safe and will only return the same witness value + * regardless if invoked by several threads. Also, the provided {@code supplier} + * will only be invoked once even if invoked from several threads unless the + * {@code supplier} throws an exception. + * + * @param supplier to be used for computing a value + * @throws StackOverflowError if the provided {@code supplier} recursively + * invokes the provided {@code supplier} upon being invoked. + */ + T computeIfUnset(Supplier supplier); + + /** + * {@return the set holder value if set, otherwise attempts to compute and set a + * new (nullable) value using the provided {@code key} and provided {@code mapper}, + * returning the (pre-existing or newly set) value} + *

    + * The provided {@code mapper} is guaranteed to be invoked at most once if it + * completes without throwing an exception. + *

    + * If the mapper throws an (unchecked) exception, the exception is rethrown, and no + * value is set. + * + * @implSpec The implementation logic is equivalent to the following steps for this + * {@code stable}: + * + *

     {@code
    +     * if (stable.isSet()) {
    +     *     return stable.get();
    +     * } else {
    +     *     V newValue = mapper.apply(value);
    +     *     stable.setOrThrow(newValue);
    +     *     return newValue;
    +     * }
    +     * }
    + * Except it is thread-safe and will only return the same witness value + * regardless if invoked by several threads. Also, the provided {@code supplier} + * will only be invoked once even if invoked from several threads unless the + * {@code supplier} throws an exception. + * + * @param key to be used by the provided mapper + * @param mapper that takes the provided key to be used for computing a value + * @throws StackOverflowError if the provided {@code mapper} recursively + * invokes the provided {@code mapper} upon being invoked. + */ + T computeIfUnset(int key, IntFunction mapper); + + /** + * {@return the set holder value if set, otherwise attempts to compute and set a + * new (nullable) value using the provided {@code key} and provided {@code mapper}, + * returning the (pre-existing or newly set) value} + *

    + * The provided {@code mapper} is guaranteed to be invoked at most once if it + * completes without throwing an exception. + *

    + * If the mapper throws an (unchecked) exception, the exception is rethrown, and no + * value is set. + * + * @implSpec The implementation logic is equivalent to the following steps for this + * {@code stable}: + * + *

     {@code
    +     * if (stable.isSet()) {
    +     *     return stable.get();
    +     * } else {
    +     *     V newValue = mapper.apply(value);
    +     *     stable.setOrThrow(newValue);
    +     *     return newValue;
    +     * }
    +     * }
    + * Except it is thread-safe and will only return the same witness value + * regardless if invoked by several threads. Also, the provided {@code supplier} + * will only be invoked once even if invoked from several threads unless the + * {@code supplier} throws an exception. + * + * @param key to be used by the provided mapper + * @param mapper that takes the provided key to be used for computing a value + * @param key type + * @throws StackOverflowError if the provided {@code mapper} recursively + * invokes the provided {@code mapper} upon being invoked. + */ + T computeIfUnset(K key, Function mapper); + // Factories /** @@ -212,7 +322,7 @@ default void setOrThrow(T value) { * @param type of the holder value */ static StableValue newInstance() { - return StableValueUtil.newInstance(); + return StableValueFactories.newInstance(); } /** @@ -239,7 +349,7 @@ static StableValue newInstance() { */ static Supplier newCachingSupplier(Supplier original) { Objects.requireNonNull(original); - return StableValueUtil.newCachingSupplier(original, null); + return StableValueFactories.newCachingSupplier(original, null); } /** @@ -274,7 +384,7 @@ static Supplier newCachingSupplier(Supplier original, ThreadFactory factory) { Objects.requireNonNull(original); Objects.requireNonNull(factory); - return StableValueUtil.newCachingSupplier(original, factory); + return StableValueFactories.newCachingSupplier(original, factory); } /** @@ -307,7 +417,7 @@ static IntFunction newCachingIntFunction(int size, throw new IllegalArgumentException(); } Objects.requireNonNull(original); - return StableValueUtil.newCachingIntFunction(size, original, null); + return StableValueFactories.newCachingIntFunction(size, original, null); } /** @@ -350,7 +460,7 @@ static IntFunction newCachingIntFunction(int size, } Objects.requireNonNull(original); Objects.requireNonNull(factory); - return StableValueUtil.newCachingIntFunction(size, original, factory); + return StableValueFactories.newCachingIntFunction(size, original, factory); } /** @@ -387,7 +497,7 @@ static Function newCachingFunction(Set inputs, Function original) { Objects.requireNonNull(inputs); Objects.requireNonNull(original); - return StableValueUtil.newCachingFunction(inputs, original, null); + return StableValueFactories.newCachingFunction(inputs, original, null); } /** @@ -428,7 +538,7 @@ static Function newCachingFunction(Set inputs, Objects.requireNonNull(inputs); Objects.requireNonNull(original); Objects.requireNonNull(factory); - return StableValueUtil.newCachingFunction(inputs, original, factory); + return StableValueFactories.newCachingFunction(inputs, original, factory); } /** diff --git a/src/java.base/share/classes/java/util/ImmutableCollections.java b/src/java.base/share/classes/java/util/ImmutableCollections.java index 62e524660c284..7f9e79c8230b2 100644 --- a/src/java.base/share/classes/java/util/ImmutableCollections.java +++ b/src/java.base/share/classes/java/util/ImmutableCollections.java @@ -42,7 +42,7 @@ import jdk.internal.access.JavaUtilCollectionAccess; import jdk.internal.access.SharedSecrets; import jdk.internal.lang.stable.StableValueImpl; -import jdk.internal.lang.stable.StableValueUtil; +import jdk.internal.lang.stable.StableValueFactories; import jdk.internal.misc.CDS; import jdk.internal.util.NullableKeyValueHolder; import jdk.internal.vm.annotation.ForceInline; @@ -773,47 +773,32 @@ static final class LazyList extends AbstractImmutableList { @Stable private final IntFunction mapper; @Stable - private final List> backing; + private final StableValueImpl[] backing; LazyList(int size, IntFunction mapper) { this.mapper = mapper; - this.backing = StableValueUtil.ofList(size); + this.backing = StableValueFactories.ofArray(size); } - @Override public boolean isEmpty() { return backing.isEmpty();} - @Override public int size() { return backing.size(); } + @Override public boolean isEmpty() { return backing.length == 0;} + @Override public int size() { return backing.length; } @Override public Object[] toArray() { return copyInto(new Object[size()]); } @ForceInline @Override public E get(int i) { - final StableValueImpl stable = backing.get(i); - Object e = stable.wrappedValue(); - if (e != null) { - return StableValueUtil.unwrap(e); - } - synchronized (stable) { - e = stable.wrappedValue(); - if (e != null) { - return StableValueUtil.unwrap(e); - } - final E newValue = mapper.apply(i); - if (!stable.trySet(newValue)) { - // This should never happen. Not even if the mapper recursively - // call itself (see LazyListTest::recursive). - throw new InternalError( - "Cannot set the holder value for index " + i + " to " + e + - " because a value of " + StableValueUtil.unwrap(stable.wrappedValue()) + - " is alredy set."); - } - return newValue; + try { + return backing[i] + .computeIfUnset(i, mapper); + } catch (ArrayIndexOutOfBoundsException aioobe) { + throw new IndexOutOfBoundsException(i); } } @Override @SuppressWarnings("unchecked") public T[] toArray(T[] a) { - final int size = backing.size(); + final int size = backing.length; if (a.length < size) { // Make a new array of a's runtime type, but my contents: T[] n = (T[])Array.newInstance(a.getClass().getComponentType(), size); @@ -849,7 +834,7 @@ public int lastIndexOf(Object o) { @SuppressWarnings("unchecked") private T[] copyInto(Object[] a) { - final int len = backing.size(); + final int len = backing.length; for (int i = 0; i < len; i++) { a[i] = get(i); } @@ -1481,7 +1466,7 @@ static final class LazyMap LazyMap(Set keys, Function mapper) { this.mapper = mapper; - this.delegate = StableValueUtil.ofMap(keys); + this.delegate = StableValueFactories.ofMap(keys); } @Override public boolean containsKey(Object o) { return delegate.containsKey(o); } @@ -1497,24 +1482,7 @@ public V get(Object key) { } @SuppressWarnings("unchecked") final K k = (K) key; - return computeIfUnset(k, stable); - } - - @ForceInline - V computeIfUnset(K key, StableValueImpl stable) { - Object v = stable.wrappedValue(); - if (v != null) { - return StableValueUtil.unwrap(v); - } - synchronized (stable) { - v = stable.wrappedValue(); - if (v != null) { - return StableValueUtil.unwrap(v); - } - final V newValue = mapper.apply(key); - stable.setOrThrow(newValue); - return newValue; - } + return stable.computeIfUnset(k, mapper); } @jdk.internal.ValueBased @@ -1547,7 +1515,7 @@ final class LazyMapIterator implements Iterator> { public Entry next() { final Map.Entry> inner = delegateIterator.next(); final K key = inner.getKey(); - return new NullableKeyValueHolder<>(key, computeIfUnset(key, inner.getValue())); + return new NullableKeyValueHolder<>(key, inner.getValue().computeIfUnset(key, mapper)); } @Override @@ -1557,7 +1525,7 @@ public void forEachRemaining(Consumer> action) { @Override public void accept(Entry> inner) { final K key = inner.getKey(); - action.accept(new NullableKeyValueHolder<>(key, LazyMap.this.computeIfUnset(key, inner.getValue()))); + action.accept(new NullableKeyValueHolder<>(key, inner.getValue().computeIfUnset(key, mapper))); } }; delegateIterator.forEachRemaining(innerAction); diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/CachingFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/CachingFunction.java index 08a7a91151b88..0b9419155f210 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/CachingFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/CachingFunction.java @@ -54,19 +54,7 @@ public R apply(T value) { if (stable == null) { throw new IllegalArgumentException("Input not allowed: " + value); } - Object r = stable.wrappedValue(); - if (r != null) { - return StableValueUtil.unwrap(r); - } - synchronized (stable) { - r = stable.wrappedValue(); - if (r != null) { - return StableValueUtil.unwrap(r); - } - final R newValue = original.apply(value); - stable.setOrThrow(newValue); - return newValue; - } + return stable.computeIfUnset(value, original); } @Override @@ -95,7 +83,7 @@ private String renderMappings() { if (value == this) { sb.append("(this CachingFunction)"); } else { - sb.append(StableValueUtil.renderWrapped(value)); + sb.append(StableValueImpl.renderWrapped(value)); } } sb.append("}"); @@ -104,7 +92,7 @@ private String renderMappings() { static CachingFunction of(Set inputs, Function original) { - return new CachingFunction<>(StableValueUtil.ofMap(inputs), original); + return new CachingFunction<>(StableValueFactories.ofMap(inputs), original); } } diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/CachingIntFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/CachingIntFunction.java index bb762f38e61db..473fdf17b9a65 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/CachingIntFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/CachingIntFunction.java @@ -47,30 +47,17 @@ * * @param the return type */ -record CachingIntFunction(IntFunction original, - Object[] mutexes, - Object[] wrappedValues) implements IntFunction { +record CachingIntFunction(StableValueImpl[] delegates, + IntFunction original) implements IntFunction { @ForceInline @Override public R apply(int index) { try { - Objects.checkIndex(index, wrappedValues.length); - } catch (IndexOutOfBoundsException e) { - throw new IllegalArgumentException(e); - } - Object r = wrappedValue(index); - if (r != null) { - return StableValueUtil.unwrap(r); - } - synchronized (mutexes[index]) { - r = wrappedValues[index]; - if (r != null) { - return StableValueUtil.unwrap(r); - } - final R newValue = original.apply(index); - StableValueUtil.wrapAndCas(wrappedValues, StableValueUtil.arrayOffset(index), newValue); - return newValue; + return delegates[index] + .computeIfUnset(index, original); + } catch (java.lang.ArrayIndexOutOfBoundsException ioob) { + throw new IllegalArgumentException("Input not allowed: " + index, ioob); } } @@ -95,30 +82,21 @@ private String renderElements() { final StringBuilder sb = new StringBuilder(); sb.append("["); boolean first = true; - for (int i = 0; i < wrappedValues.length; i++) { + for (int i = 0; i < delegates.length; i++) { if (first) { first = false; } else { sb.append(", "); }; - final Object value = wrappedValue(i); + final Object value = delegates[i].wrappedValue(); if (value == this) { sb.append("(this CachingIntFunction)"); } else { - sb.append(StableValueUtil.renderWrapped(value)); + sb.append(StableValueImpl.renderWrapped(value)); } } sb.append("]"); return sb.toString(); } - @ForceInline - private Object wrappedValue(int i) { - return StableValueUtil.UNSAFE.getReferenceVolatile(wrappedValues, StableValueUtil.arrayOffset(i)); - } - static CachingIntFunction of(int size, IntFunction original) { - var mutexes = new Object[size]; - for (int i = 0; i < size; i++) { - mutexes[i] = new Object(); - } - return new CachingIntFunction<>(original, mutexes, new Object[size]); + return new CachingIntFunction<>(StableValueFactories.ofArray(size), original); } } diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/CachingSupplier.java b/src/java.base/share/classes/jdk/internal/lang/stable/CachingSupplier.java index 17c23d4081fe0..3c78a3e40ec64 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/CachingSupplier.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/CachingSupplier.java @@ -33,56 +33,37 @@ /** * Implementation of a cached supplier. *

    - * For performance reasons (~10%), we are not delegating to a StableValue but are using - * the more primitive functions in StableValueUtil that are shared with StableValueImpl. - * * @implNote This implementation can be used early in the boot sequence as it does not * rely on reflection, MethodHandles, Streams etc. * * @param the return type */ -final class CachingSupplier implements Supplier { - - private static final long VALUE_OFFSET = - StableValueUtil.UNSAFE.objectFieldOffset(CachingSupplier.class, "wrappedValue"); +record CachingSupplier(StableValueImpl delegate, Supplier original) implements Supplier { - @Stable - private final Supplier original; - @Stable - private final Object mutex = new Object(); - @Stable - private volatile Object wrappedValue; + @ForceInline + @Override + public T get() { + return delegate.computeIfUnset(original); + } - private CachingSupplier(Supplier original) { - this.original = original; + @Override + public int hashCode() { + return System.identityHashCode(this); } - @ForceInline @Override - public T get() { - Object t = wrappedValue; - if (t != null) { - return StableValueUtil.unwrap(t); - } - synchronized (mutex) { - t = wrappedValue; - if (t != null) { - return StableValueUtil.unwrap(t); - } - final T newValue = original.get(); - StableValueUtil.wrapAndCas(this, VALUE_OFFSET, newValue); - return newValue; - } + public boolean equals(Object obj) { + return obj == this; } @Override public String toString() { - final Object t = wrappedValue; - return "CachingSupplier[value=" + (t == this ? "(this CachingSupplier)" : StableValueUtil.renderWrapped(t)) + ", original=" + original + "]"; + final Object t = delegate.wrappedValue(); + return "CachingSupplier[value=" + (t == this ? "(this CachingSupplier)" : StableValueImpl.renderWrapped(t)) + ", original=" + original + "]"; } static CachingSupplier of(Supplier original) { - return new CachingSupplier<>(original); + return new CachingSupplier<>(StableValueImpl.newInstance(), original); } } diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableValueUtil.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableValueFactories.java similarity index 54% rename from src/java.base/share/classes/jdk/internal/lang/stable/StableValueUtil.java rename to src/java.base/share/classes/jdk/internal/lang/stable/StableValueFactories.java index a34f0616dda9c..01b9c2b5acc68 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableValueUtil.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableValueFactories.java @@ -1,9 +1,5 @@ package jdk.internal.lang.stable; -import jdk.internal.misc.Unsafe; -import jdk.internal.vm.annotation.ForceInline; - -import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; @@ -12,50 +8,9 @@ import java.util.function.IntFunction; import java.util.function.Supplier; -public final class StableValueUtil { - - private StableValueUtil() {} - - // Unsafe allows StableValue to be used early in the boot sequence - static final Unsafe UNSAFE = Unsafe.getUnsafe(); - - // Used to indicate a holder value is `null` (see field `value` below) - // A wrapper method `nullSentinel()` is used for generic type conversion. - private static final Object NULL_SENTINEL = new Object(); - - // Wraps `null` values into a sentinel value - @ForceInline - private static T wrap(T t) { - return (t == null) ? nullSentinel() : t; - } - - // Unwraps null sentinel values into `null` - @SuppressWarnings("unchecked") - @ForceInline - public static T unwrap(Object t) { - return t != nullSentinel() ? (T) t : null; - } +public final class StableValueFactories { - @SuppressWarnings("unchecked") - @ForceInline - private static T nullSentinel() { - return (T) NULL_SENTINEL; - } - - static String renderWrapped(Object t) { - return (t == null) ? ".unset" : "[" + unwrap(t) + "]"; - } - - @ForceInline - static boolean wrapAndCas(Object o, long offset, Object value) { - // This upholds the invariant, a `@Stable` field is written to at most once - return UNSAFE.compareAndSetReference(o, offset, null, wrap(value)); - } - - @ForceInline - static long arrayOffset(int index) { - return Unsafe.ARRAY_OBJECT_BASE_OFFSET + (long) index * Unsafe.ARRAY_OBJECT_INDEX_SCALE; - } + private StableValueFactories() {} // Factories @@ -63,7 +18,7 @@ public static StableValueImpl newInstance() { return StableValueImpl.newInstance(); } - public static List> ofList(int size) { + public static StableValueImpl[] ofArray(int size) { if (size < 0) { throw new IllegalArgumentException(); } @@ -72,7 +27,7 @@ public static List> ofList(int size) { for (int i = 0; i < size; i++) { stableValues[i] = StableValueImpl.newInstance(); } - return List.of(stableValues); + return stableValues; } public static Map> ofMap(Set keys) { @@ -89,53 +44,53 @@ public static Map> ofMap(Set keys) { public static Supplier newCachingSupplier(Supplier original, ThreadFactory factory) { - final Supplier memoized = CachingSupplier.of(original); + final Supplier caching = CachingSupplier.of(original); if (factory != null) { final Thread thread = factory.newThread(new Runnable() { @Override public void run() { - memoized.get(); + caching.get(); } }); thread.start(); } - return memoized; + return caching; } public static IntFunction newCachingIntFunction(int size, IntFunction original, ThreadFactory factory) { - final IntFunction memoized = CachingIntFunction.of(size, original); + final IntFunction caching = CachingIntFunction.of(size, original); if (factory != null) { for (int i = 0; i < size; i++) { final int input = i; final Thread thread = factory.newThread(new Runnable() { - @Override public void run() { memoized.apply(input); } + @Override public void run() { caching.apply(input); } }); thread.start(); } } - return memoized; + return caching; } public static Function newCachingFunction(Set inputs, Function original, ThreadFactory factory) { - final Function memoized = CachingFunction.of(inputs, original); + final Function caching = CachingFunction.of(inputs, original); if (factory != null) { for (final T t : inputs) { final Thread thread = factory.newThread(new Runnable() { - @Override public void run() { memoized.apply(t); } + @Override public void run() { caching.apply(t); } }); thread.start(); } } - return memoized; + return caching; } } 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 9d888cd65eb6f..c2e966a05eb41 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 @@ -25,10 +25,15 @@ package jdk.internal.lang.stable; +import jdk.internal.misc.Unsafe; import jdk.internal.vm.annotation.ForceInline; import jdk.internal.vm.annotation.Stable; import java.util.NoSuchElementException; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.IntFunction; +import java.util.function.Supplier; /** * The implementation of StableValue. @@ -40,9 +45,12 @@ */ public final class StableValueImpl implements StableValue { + // Unsafe allows StableValue to be used early in the boot sequence + static final Unsafe UNSAFE = Unsafe.getUnsafe(); + // Unsafe offsets for direct field access private static final long VALUE_OFFSET = - StableValueUtil.UNSAFE.objectFieldOffset(StableValueImpl.class, "wrappedValue"); + UNSAFE.objectFieldOffset(StableValueImpl.class, "wrappedValue"); // Generally, fields annotated with `@Stable` are accessed by the JVM using special // memory semantics rules (see `parse.hpp` and `parse(1|2|3).cpp`). @@ -58,8 +66,13 @@ public final class StableValueImpl implements StableValue { @Stable private volatile Object wrappedValue; + @Stable + private final Object mutex; + // Only allow creation via the factory `StableValueImpl::newInstance` - private StableValueImpl() {} + private StableValueImpl() { + this.mutex = new Object(); + } @ForceInline @Override @@ -67,7 +80,11 @@ public boolean trySet(T newValue) { if (wrappedValue != null) { return false; } - return StableValueUtil.wrapAndCas(this, VALUE_OFFSET, newValue); + // Mutual exclusion is required here as `computeIfUnset` might also + // attempt to modify the `wrappedValue` + synchronized (mutex) { + return wrapAndCas(newValue); + } } @ForceInline @@ -75,7 +92,7 @@ public boolean trySet(T newValue) { public T orElseThrow() { final Object t = wrappedValue; if (t != null) { - return StableValueUtil.unwrap(t); + return unwrap(t); } throw new NoSuchElementException("No holder value set"); } @@ -85,7 +102,7 @@ public T orElseThrow() { public T orElse(T other) { final Object t = wrappedValue; if (t != null) { - return StableValueUtil.unwrap(t); + return unwrap(t); } return other; } @@ -96,6 +113,60 @@ public boolean isSet() { return wrappedValue != null; } + // (p, _) -> p.get() + private static final BiFunction, Object, Object> SUPPLIER_EXTRACTOR = new BiFunction<>() { + @Override public Object apply(Supplier supplier, Object unused) { return supplier.get(); } + }; + + // IntFunction::apply + private static final BiFunction, Integer, Object> INT_FUNCTION_EXTRACTOR = new BiFunction<>() { + @Override public Object apply(IntFunction mapper, Integer key) { return mapper.apply(key); } + }; + + // Function::apply + private static final BiFunction, Object, Object> FUNCTION_EXTRACTOR = new BiFunction<>() { + @Override public Object apply(Function mapper, Object key) { return mapper.apply(key); } + }; + + @SuppressWarnings("unchecked") + @ForceInline + @Override + public T computeIfUnset(Supplier supplier) { + return computeIfUnset0(null, supplier, (BiFunction, ?, T>) (BiFunction) SUPPLIER_EXTRACTOR); + } + + @SuppressWarnings("unchecked") + @ForceInline + @Override + public T computeIfUnset(int key, IntFunction mapper) { + return computeIfUnset0(key, mapper, (BiFunction, ? super Integer, T>) (BiFunction) INT_FUNCTION_EXTRACTOR); + } + + @SuppressWarnings("unchecked") + @ForceInline + @Override + public T computeIfUnset(K key, Function mapper) { + return computeIfUnset0(key, mapper, (BiFunction, ? super K, T>) (BiFunction) FUNCTION_EXTRACTOR); + } + + // A consolidated method for all computeIfUnset overloads + @ForceInline + private T computeIfUnset0(K key, P provider, BiFunction extractor) { + Object t = wrappedValue; + if (t != null) { + return unwrap(t); + } + synchronized (mutex) { + t = wrappedValue; + if (t != null) { + return unwrap(t); + } + final T newValue = extractor.apply(provider, key); + // The mutex is reentrant so we need to check if the value was actually set. + return wrapAndCas(newValue) ? newValue : orElseThrow(); + } + } + // The methods equals() and hashCode() should be based on identity (defaults from Object) @Override @@ -103,14 +174,51 @@ public String toString() { final Object t = wrappedValue; return t == this ? "(this StableValue)" - : "StableValue" + StableValueUtil.renderWrapped(t); + : "StableValue" + renderWrapped(t); } + // Internal methods shared with other internal classes + @ForceInline public Object wrappedValue() { return wrappedValue; } + static String renderWrapped(Object t) { + return (t == null) ? ".unset" : "[" + unwrap(t) + "]"; + } + + // Private methods + + @ForceInline + private boolean wrapAndCas(Object value) { + // This upholds the invariant, a `@Stable` field is written to at most once + return UNSAFE.compareAndSetReference(this, VALUE_OFFSET, null, wrap(value)); + } + + // Used to indicate a holder value is `null` (see field `value` below) + // A wrapper method `nullSentinel()` is used for generic type conversion. + private static final Object NULL_SENTINEL = new Object(); + + // Wraps `null` values into a sentinel value + @ForceInline + private static T wrap(T t) { + return (t == null) ? nullSentinel() : t; + } + + // Unwraps null sentinel values into `null` + @SuppressWarnings("unchecked") + @ForceInline + private static T unwrap(Object t) { + return t != nullSentinel() ? (T) t : null; + } + + @SuppressWarnings("unchecked") + @ForceInline + private static T nullSentinel() { + return (T) NULL_SENTINEL; + } + // Factory static StableValueImpl newInstance() { diff --git a/test/jdk/java/lang/StableValue/LazyListTest.java b/test/jdk/java/lang/StableValue/LazyListTest.java index 5a0999cf7dcf9..9901faf5a6e59 100644 --- a/test/jdk/java/lang/StableValue/LazyListTest.java +++ b/test/jdk/java/lang/StableValue/LazyListTest.java @@ -29,7 +29,7 @@ */ import jdk.internal.lang.stable.StableValueImpl; -import jdk.internal.lang.stable.StableValueUtil; +import jdk.internal.lang.stable.StableValueFactories; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -268,11 +268,13 @@ void randomAccess() { @Test void distinct() { - List> list = StableValueUtil.ofList(SIZE); - assertEquals(SIZE, list.size()); + StableValueImpl[] array = StableValueFactories.ofArray(SIZE); + assertEquals(SIZE, array.length); // Check, every StableValue is distinct Map, Boolean> idMap = new IdentityHashMap<>(); - list.forEach(e -> idMap.put(e, true)); + for (var e: array) { + idMap.put(e, true); + } assertEquals(SIZE, idMap.size()); } diff --git a/test/jdk/java/lang/StableValue/LazyMapTest.java b/test/jdk/java/lang/StableValue/LazyMapTest.java index 788e56584cab9..89b919243f145 100644 --- a/test/jdk/java/lang/StableValue/LazyMapTest.java +++ b/test/jdk/java/lang/StableValue/LazyMapTest.java @@ -29,7 +29,7 @@ */ import jdk.internal.lang.stable.StableValueImpl; -import jdk.internal.lang.stable.StableValueUtil; +import jdk.internal.lang.stable.StableValueFactories; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -39,7 +39,6 @@ import java.util.HashSet; import java.util.IdentityHashMap; import java.util.Map; -import java.util.Objects; import java.util.Set; import java.util.function.Consumer; import java.util.function.Function; @@ -211,7 +210,7 @@ void serializable(Map map) { @Test void distinct() { - Map> map = StableValueUtil.ofMap(Set.of(1, 2, 3)); + Map> map = StableValueFactories.ofMap(Set.of(1, 2, 3)); assertEquals(3, map.size()); // Check, every StableValue is distinct Map, Boolean> idMap = new IdentityHashMap<>(); From 763397323dafb07dccaf502f5b9963a14a363454 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 4 Sep 2024 17:48:59 +0200 Subject: [PATCH 128/327] Clean up and add overload --- .../share/classes/java/lang/StableValue.java | 43 +++++++++- .../jdk/internal/javac/PreviewFeature.java | 2 +- .../internal/lang/stable/StableValueImpl.java | 20 ++++- test/jdk/java/lang/StableValue/JepTest.java | 78 ++++++++++++++++--- .../java/lang/StableValue/StableTestUtil.java | 16 ++++ .../lang/StableValue/StableValueTest.java | 42 ++++++++++ 6 files changed, 186 insertions(+), 15 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index ed71373f634bf..1e9ede644fccd 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -37,6 +37,7 @@ import java.util.Objects; import java.util.Set; import java.util.concurrent.ThreadFactory; +import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.IntFunction; import java.util.function.Supplier; @@ -261,7 +262,7 @@ default void setOrThrow(T value) { * if (stable.isSet()) { * return stable.get(); * } else { - * V newValue = mapper.apply(value); + * V newValue = mapper.apply(key); * stable.setOrThrow(newValue); * return newValue; * } @@ -296,7 +297,7 @@ default void setOrThrow(T value) { * if (stable.isSet()) { * return stable.get(); * } else { - * V newValue = mapper.apply(value); + * V newValue = mapper.apply(key); * stable.setOrThrow(newValue); * return newValue; * } @@ -314,6 +315,44 @@ default void setOrThrow(T value) { */ T computeIfUnset(K key, Function mapper); + /** + * {@return the set holder value if set, otherwise attempts to compute and set a + * new (nullable) value using the provided {@code firstKey}, {@code secondKey}, and + * provided {@code mapper}, returning the (pre-existing or newly set) value} + *

    + * The provided {@code mapper} is guaranteed to be invoked at most once if it + * completes without throwing an exception. + *

    + * If the mapper throws an (unchecked) exception, the exception is rethrown, and no + * value is set. + * + * @implSpec The implementation logic is equivalent to the following steps for this + * {@code stable}: + * + *

     {@code
    +     * if (stable.isSet()) {
    +     *     return stable.get();
    +     * } else {
    +     *     V newValue = mapper.apply(firstKey, secondKey);
    +     *     stable.setOrThrow(newValue);
    +     *     return newValue;
    +     * }
    +     * }
    + * Except it is thread-safe and will only return the same witness value + * regardless if invoked by several threads. Also, the provided {@code supplier} + * will only be invoked once even if invoked from several threads unless the + * {@code supplier} throws an exception. + * + * @param firstKey to be used by the provided mapper + * @param secondKey to be used by the provided mapper + * @param mapper that takes the provided keys to be used for computing a value + * @param firstKey type + * @param secondKey type + * @throws StackOverflowError if the provided {@code mapper} recursively + * invokes the provided {@code mapper} upon being invoked. + */ + T computeIfUnset(K firstKey, L secondKey, BiFunction mapper); + // Factories /** diff --git a/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java b/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java index 87b64d7c633bc..42abe0815be4f 100644 --- a/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java +++ b/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java @@ -82,7 +82,7 @@ public enum Feature { @JEP(number=476, title="Module Import Declarations", status="Preview") MODULE_IMPORTS, LANGUAGE_MODEL, - @JEP(number = 8330465, title = "Stable Values & Collections", status = "Preview") + @JEP(number = 8330465, title = "Stable Values", status = "Preview") STABLE_VALUES, /** * A key for testing. 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 c2e966a05eb41..61fce76c2a6e6 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 @@ -149,7 +149,25 @@ public T computeIfUnset(K key, Function mapper) { return computeIfUnset0(key, mapper, (BiFunction, ? super K, T>) (BiFunction) FUNCTION_EXTRACTOR); } - // A consolidated method for all computeIfUnset overloads + @ForceInline + @Override + public T computeIfUnset(K firstKey, L secondKey, BiFunction mapper) { + Object t = wrappedValue; + if (t != null) { + return unwrap(t); + } + synchronized (mutex) { + t = wrappedValue; + if (t != null) { + return unwrap(t); + } + final T newValue = mapper.apply(firstKey, secondKey); + // The mutex is reentrant so we need to check if the value was actually set. + return wrapAndCas(newValue) ? newValue : orElseThrow(); + } + } + + // A consolidated method for some computeIfUnset overloads @ForceInline private T computeIfUnset0(K key, P provider, BiFunction extractor) { Object t = wrappedValue; diff --git a/test/jdk/java/lang/StableValue/JepTest.java b/test/jdk/java/lang/StableValue/JepTest.java index dd613c7042642..684e27cc4cdcb 100644 --- a/test/jdk/java/lang/StableValue/JepTest.java +++ b/test/jdk/java/lang/StableValue/JepTest.java @@ -35,6 +35,7 @@ import java.nio.file.Path; import java.util.Map; import java.util.Set; +import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.IntFunction; import java.util.function.Predicate; @@ -249,12 +250,12 @@ private static String readFromFile(int messageNumber) { } record CachingPredicate(Map> delegate, - Predicate original) implements Predicate { + Function original) implements Predicate { public CachingPredicate(Set inputs, Predicate original) { this(inputs.stream() .collect(Collectors.toMap(Function.identity(), _ -> StableValue.newInstance())), - original + original::test ); } @@ -265,18 +266,73 @@ public boolean test(T t) { throw new IllegalArgumentException(t.toString()); } - if (stable.isSet()) { - return stable.isSet(); + return stable.computeIfUnset(t, original); + } + } + + record CachingPredicate2(Map delegate) implements Predicate { + + public CachingPredicate2(Set inputs, Predicate original) { + this(StableValue.lazyMap(inputs, original::test)); + } + + @Override + public boolean test(T t) { + return delegate.get(t); + } + } + + public static void main(String[] args) { + Predicate even = i -> i % 2 == 0; + Predicate cachingPredicate = StableValue.lazyMap(Set.of(1, 2), even::test)::get; + } + + record Pair(L left, R right){} + + record CachingBiFunction(Map, StableValue> delegate, + Function, R> original) implements BiFunction { + + @Override + public R apply(T t, U u) { + final var key = new Pair<>(t, u); + final StableValue stable = delegate.get(key); + if (stable == null) { + throw new IllegalArgumentException(t + ", " + u); } - synchronized (this) { - if (stable.isSet()) { - return stable.isSet(); - } - final Boolean r = (Boolean) original.test(t); - stable.setOrThrow(r); - return r; + return stable.computeIfUnset(key, original); + } + + static CachingBiFunction of(Set> inputs, BiFunction original) { + + Map, StableValue> map = inputs.stream() + .collect(Collectors.toMap(Function.identity(), _ -> StableValue.newInstance())); + + return new CachingBiFunction<>(map, pair -> original.apply(pair.left(), pair.right())); + } + + } + + record CachingBiFunction2(Map, StableValue> delegate, + BiFunction original) implements BiFunction { + + @Override + public R apply(T t, U u) { + final var key = new Pair<>(t, u); + final StableValue stable = delegate.get(key); + if (stable == null) { + throw new IllegalArgumentException(t + ", " + u); } + return stable.computeIfUnset(key.left(), key.right(), original); + } + + static BiFunction of(Set> inputs, BiFunction original) { + + Map, StableValue> map = inputs.stream() + .collect(Collectors.toMap(Function.identity(), _ -> StableValue.newInstance())); + + return new CachingBiFunction2<>(map, original); } + } } diff --git a/test/jdk/java/lang/StableValue/StableTestUtil.java b/test/jdk/java/lang/StableValue/StableTestUtil.java index a37c63b744680..4a5457ccc1eed 100644 --- a/test/jdk/java/lang/StableValue/StableTestUtil.java +++ b/test/jdk/java/lang/StableValue/StableTestUtil.java @@ -22,6 +22,7 @@ */ import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.IntFunction; import java.util.function.Supplier; @@ -78,6 +79,21 @@ public R apply(T t) { } + public static final class CountingBiFunction + extends AbstractCounting> + implements BiFunction { + + public CountingBiFunction(BiFunction delegate) { + super(delegate); + } + + @Override + public R apply(T t, U u) { + incrementCounter(); + return delegate.apply(t, u); + } + } + abstract static class AbstractCounting { private final AtomicInteger cnt = new AtomicInteger(); diff --git a/test/jdk/java/lang/StableValue/StableValueTest.java b/test/jdk/java/lang/StableValue/StableValueTest.java index 0b65ff09c274e..a6ad665d0899d 100644 --- a/test/jdk/java/lang/StableValue/StableValueTest.java +++ b/test/jdk/java/lang/StableValue/StableValueTest.java @@ -36,6 +36,8 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.LockSupport; import java.util.function.BiPredicate; +import java.util.function.Function; +import java.util.function.Supplier; import java.util.stream.IntStream; import static org.junit.jupiter.api.Assertions.*; @@ -91,6 +93,46 @@ void isSet(Integer initial) { assertTrue(stable.isSet()); } + @Test + void testComputeIfUnsetSupplier() { + StableTestUtil.CountingSupplier cs = new StableTestUtil.CountingSupplier<>(() -> VALUE); + StableValue stable = StableValue.newInstance(); + assertEquals(VALUE, stable.computeIfUnset(cs)); + assertEquals(1, cs.cnt()); + assertEquals(VALUE, stable.computeIfUnset(cs)); + assertEquals(1, cs.cnt()); + } + + @Test + void testComputeIfUnsetIntFunction() { + StableTestUtil.CountingIntFunction cs = new StableTestUtil.CountingIntFunction<>(i -> i); + StableValue stable = StableValue.newInstance(); + assertEquals(VALUE, stable.computeIfUnset(VALUE, cs)); + assertEquals(1, cs.cnt()); + assertEquals(VALUE, stable.computeIfUnset(VALUE, cs)); + assertEquals(1, cs.cnt()); + } + + @Test + void testComputeIfUnsetFunction() { + StableTestUtil.CountingFunction cs = new StableTestUtil.CountingFunction<>(Function.identity()); + StableValue stable = StableValue.newInstance(); + assertEquals(VALUE, stable.computeIfUnset(VALUE, cs)); + assertEquals(1, cs.cnt()); + assertEquals(VALUE, stable.computeIfUnset(VALUE, cs)); + assertEquals(1, cs.cnt()); + } + + @Test + void testComputeIfUnsetBiFunction() { + StableTestUtil.CountingBiFunction cs = new StableTestUtil.CountingBiFunction<>(Integer::max); + StableValue stable = StableValue.newInstance(); + assertEquals(VALUE, stable.computeIfUnset(VALUE, 0, cs)); + assertEquals(1, cs.cnt()); + assertEquals(VALUE, stable.computeIfUnset(VALUE, 0, cs)); + assertEquals(1, cs.cnt()); + } + @Test void testHashCode() { StableValue stableValue = StableValue.newInstance(); From 9c8322851c9dc7de0178bd5a2563ae71d744fbef Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Thu, 5 Sep 2024 08:53:58 +0200 Subject: [PATCH 129/327] Synchronize on this --- .../share/classes/java/lang/StableValue.java | 19 +++++++++++-------- .../internal/lang/stable/StableValueImpl.java | 13 ++++--------- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 1e9ede644fccd..750348d767b20 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -53,6 +53,7 @@ * StableValue is mainly intended to be a member of a holding class and is usually neither * exposed directly via accessors nor passed as a method parameter. * + * *

    Factories

    *

    * To create a new fresh (unset) StableValue, use the @@ -138,14 +139,16 @@ * Except for a StableValue's holder value itself, all method parameters must be * non-null or a {@link NullPointerException} will be thrown. * - * - * Implementations of this interface can be - * value-based - * classes; programmers should treat instances that are - * {@linkplain Object#equals(Object) equal} as interchangeable and should not - * use instances for synchronization, or unpredictable behavior may - * occur. For example, in a future release, synchronization may fail. - * The {@code equals} method should be used for comparisons. + * @implNote Implementing classes are free to synchronize on {@code this} and consequently, + * care should be taken whenever (directly or indirectly) synchronizing on + * a StableValue. Failure to do this may lead to deadlock. + * + * @implNote Instance fields explicitly declared as StableValue are eligible for certain + * JVM optimizations compared to normal instance fields. This comes with + * restrictions on reflective modifications. Although most ways of reflective + * modification of such fields are disabled, it is strongly discouraged to + * circumvent these protection means as reflectively modifying such fields may + * lead to unspecified behavior. * * @param type of the holder value * 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 61fce76c2a6e6..dbf8f093a0bdf 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 @@ -66,13 +66,8 @@ public final class StableValueImpl implements StableValue { @Stable private volatile Object wrappedValue; - @Stable - private final Object mutex; - // Only allow creation via the factory `StableValueImpl::newInstance` - private StableValueImpl() { - this.mutex = new Object(); - } + private StableValueImpl() {} @ForceInline @Override @@ -82,7 +77,7 @@ public boolean trySet(T newValue) { } // Mutual exclusion is required here as `computeIfUnset` might also // attempt to modify the `wrappedValue` - synchronized (mutex) { + synchronized (this) { return wrapAndCas(newValue); } } @@ -156,7 +151,7 @@ public T computeIfUnset(K firstKey, L secondKey, BiFunction T computeIfUnset0(K key, P provider, BiFunction extracto if (t != null) { return unwrap(t); } - synchronized (mutex) { + synchronized (this) { t = wrappedValue; if (t != null) { return unwrap(t); From b5108bb8fe555b2d97d96581ff22eeb071e8a334 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 9 Sep 2024 16:32:06 +0200 Subject: [PATCH 130/327] Simplify --- .../share/classes/java/lang/StableValue.java | 247 +----------------- .../java/util/ImmutableCollections.java | 17 +- .../internal/lang/stable/CachingFunction.java | 4 +- .../lang/stable/CachingIntFunction.java | 9 +- .../internal/lang/stable/CachingSupplier.java | 1 - .../lang/stable/StableValueFactories.java | 65 +---- .../internal/lang/stable/StableValueImpl.java | 57 +--- .../lang/StableValue/CachingFunctionTest.java | 18 -- .../StableValue/CachingIntFunctionTest.java | 23 -- .../lang/StableValue/CachingSupplierTest.java | 15 -- test/jdk/java/lang/StableValue/JepTest.java | 93 +++++-- .../lang/StableValue/StableValueTest.java | 30 --- .../lang/stable/CachingFunctionBenchmark.java | 5 +- .../stable/CachingIntFunctionBenchmark.java | 6 +- .../lang/stable/CachingSupplierBenchmark.java | 7 +- .../lang/stable/StableValueBenchmark.java | 5 +- 16 files changed, 116 insertions(+), 486 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 750348d767b20..52376ca202b27 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -36,8 +36,6 @@ import java.util.NoSuchElementException; import java.util.Objects; import java.util.Set; -import java.util.concurrent.ThreadFactory; -import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.IntFunction; import java.util.function.Supplier; @@ -69,11 +67,6 @@ * {@snippet lang = java : * Supplier cache = StableValue.newCachingSupplier(original); * } - * The caching supplier can also be computed by a fresh background thread if a - * thread factory is provided as a second parameter as shown here: - * {@snippet lang = java : - * Supplier cache = StableValue.newCachingSupplier(original, Thread.ofVirtual().factory()); - * } * * *

  • @@ -84,9 +77,6 @@ * {@snippet lang = java: * IntFunction cache = StableValue.newCachingIntFunction(size, original); *} - * Just like a caching supplier, a thread factory can be provided as a second parameter - * allowing all the values for the allowed input values to be computed by distinct - * background threads. *
  • * *
  • @@ -97,9 +87,6 @@ * {@snippet lang = java : * Function cache = StableValue.newCachingFunction(inputs, original); * } - * Just like a caching supplier, a thread factory can be provided as a second parameter - * allowing all the values for the allowed input values to be computed by distinct - * background threads. *
  • * *
  • @@ -247,115 +234,6 @@ default void setOrThrow(T value) { */ T computeIfUnset(Supplier supplier); - /** - * {@return the set holder value if set, otherwise attempts to compute and set a - * new (nullable) value using the provided {@code key} and provided {@code mapper}, - * returning the (pre-existing or newly set) value} - *

    - * The provided {@code mapper} is guaranteed to be invoked at most once if it - * completes without throwing an exception. - *

    - * If the mapper throws an (unchecked) exception, the exception is rethrown, and no - * value is set. - * - * @implSpec The implementation logic is equivalent to the following steps for this - * {@code stable}: - * - *

     {@code
    -     * if (stable.isSet()) {
    -     *     return stable.get();
    -     * } else {
    -     *     V newValue = mapper.apply(key);
    -     *     stable.setOrThrow(newValue);
    -     *     return newValue;
    -     * }
    -     * }
    - * Except it is thread-safe and will only return the same witness value - * regardless if invoked by several threads. Also, the provided {@code supplier} - * will only be invoked once even if invoked from several threads unless the - * {@code supplier} throws an exception. - * - * @param key to be used by the provided mapper - * @param mapper that takes the provided key to be used for computing a value - * @throws StackOverflowError if the provided {@code mapper} recursively - * invokes the provided {@code mapper} upon being invoked. - */ - T computeIfUnset(int key, IntFunction mapper); - - /** - * {@return the set holder value if set, otherwise attempts to compute and set a - * new (nullable) value using the provided {@code key} and provided {@code mapper}, - * returning the (pre-existing or newly set) value} - *

    - * The provided {@code mapper} is guaranteed to be invoked at most once if it - * completes without throwing an exception. - *

    - * If the mapper throws an (unchecked) exception, the exception is rethrown, and no - * value is set. - * - * @implSpec The implementation logic is equivalent to the following steps for this - * {@code stable}: - * - *

     {@code
    -     * if (stable.isSet()) {
    -     *     return stable.get();
    -     * } else {
    -     *     V newValue = mapper.apply(key);
    -     *     stable.setOrThrow(newValue);
    -     *     return newValue;
    -     * }
    -     * }
    - * Except it is thread-safe and will only return the same witness value - * regardless if invoked by several threads. Also, the provided {@code supplier} - * will only be invoked once even if invoked from several threads unless the - * {@code supplier} throws an exception. - * - * @param key to be used by the provided mapper - * @param mapper that takes the provided key to be used for computing a value - * @param key type - * @throws StackOverflowError if the provided {@code mapper} recursively - * invokes the provided {@code mapper} upon being invoked. - */ - T computeIfUnset(K key, Function mapper); - - /** - * {@return the set holder value if set, otherwise attempts to compute and set a - * new (nullable) value using the provided {@code firstKey}, {@code secondKey}, and - * provided {@code mapper}, returning the (pre-existing or newly set) value} - *

    - * The provided {@code mapper} is guaranteed to be invoked at most once if it - * completes without throwing an exception. - *

    - * If the mapper throws an (unchecked) exception, the exception is rethrown, and no - * value is set. - * - * @implSpec The implementation logic is equivalent to the following steps for this - * {@code stable}: - * - *

     {@code
    -     * if (stable.isSet()) {
    -     *     return stable.get();
    -     * } else {
    -     *     V newValue = mapper.apply(firstKey, secondKey);
    -     *     stable.setOrThrow(newValue);
    -     *     return newValue;
    -     * }
    -     * }
    - * Except it is thread-safe and will only return the same witness value - * regardless if invoked by several threads. Also, the provided {@code supplier} - * will only be invoked once even if invoked from several threads unless the - * {@code supplier} throws an exception. - * - * @param firstKey to be used by the provided mapper - * @param secondKey to be used by the provided mapper - * @param mapper that takes the provided keys to be used for computing a value - * @param firstKey type - * @param secondKey type - * @throws StackOverflowError if the provided {@code mapper} recursively - * invokes the provided {@code mapper} upon being invoked. - */ - T computeIfUnset(K firstKey, L secondKey, BiFunction mapper); - // Factories /** @@ -391,42 +269,7 @@ static StableValue newInstance() { */ static Supplier newCachingSupplier(Supplier original) { Objects.requireNonNull(original); - return StableValueFactories.newCachingSupplier(original, null); - } - - /** - * {@return a new caching, thread-safe, stable, lazily computed - * {@linkplain Supplier supplier} that records the value of the provided - * {@code original} supplier upon being first accessed via - * {@linkplain Supplier#get() Supplier::get}, or via a background thread - * created from the provided {@code factory}} - *

    - * The provided {@code original} supplier is guaranteed to be successfully invoked - * at most once even in a multi-threaded environment. Competing threads invoking the - * {@linkplain Supplier#get() Supplier::get} method when a value is already under - * computation will block until a value is computed or an exception is thrown by the - * computing thread. - *

    - * If the {@code original} Supplier invokes the returned Supplier recursively, - * a StackOverflowError will be thrown when the returned - * Supplier's {@linkplain Supplier#get() Supplier::get} method is invoked. - *

    - * If the provided {@code original} supplier throws an exception, it is relayed - * to the initial caller. If the memoized supplier is computed by a background thread, - * exceptions from the provided {@code original} supplier will be relayed to the - * background thread's {@linkplain Thread#getUncaughtExceptionHandler() uncaught - * exception handler}. - * - * @param original supplier used to compute a memoized value - * @param factory a factory that will be used to create a background thread that will - * attempt to compute the memoized value. - * @param the type of results supplied by the returned supplier - */ - static Supplier newCachingSupplier(Supplier original, - ThreadFactory factory) { - Objects.requireNonNull(original); - Objects.requireNonNull(factory); - return StableValueFactories.newCachingSupplier(original, factory); + return StableValueFactories.newCachingSupplier(original); } /** @@ -459,50 +302,7 @@ static IntFunction newCachingIntFunction(int size, throw new IllegalArgumentException(); } Objects.requireNonNull(original); - return StableValueFactories.newCachingIntFunction(size, original, null); - } - - /** - * {@return a new caching, thread-safe, stable, lazily computed - * {@link IntFunction } that, for each allowed input, records the values of the - * provided {@code original} IntFunction upon being first accessed via - * {@linkplain IntFunction#apply(int) IntFunction::apply}, or via background - * threads created from the provided {@code factory} (if non-null)} - *

    - * The provided {@code original} IntFunction is guaranteed to be successfully invoked - * at most once per allowed input, even in a multi-threaded environment. Competing - * threads invoking the {@linkplain IntFunction#apply(int) IntFunction::apply} method - * when a value is already under computation will block until a value is computed or - * an exception is thrown by the computing thread. - *

    - * If the {@code original} IntFunction invokes the returned IntFunction recursively - * for a particular input value, a StackOverflowError will be thrown when the returned - * IntFunction's {@linkplain IntFunction#apply(int) IntFunction::apply} method is - * invoked. - *

    - * If the provided {@code original} IntFunction throws an exception, it is relayed - * to the initial caller. If the memoized IntFunction is computed by a background - * thread, exceptions from the provided {@code original} IntFunction will be relayed - * to the background thread's {@linkplain Thread#getUncaughtExceptionHandler() - * uncaught exception handler}. - *

    - * The order in which background threads are started is unspecified. - * - * @param size the size of the allowed inputs in {@code [0, size)} - * @param original IntFunction used to compute a memoized value - * @param factory a factory that will be used to create {@code size} background - * threads that will attempt to compute all the memoized values. - * @param the type of results delivered by the returned IntFunction - */ - static IntFunction newCachingIntFunction(int size, - IntFunction original, - ThreadFactory factory) { - if (size < 0) { - throw new IllegalArgumentException(); - } - Objects.requireNonNull(original); - Objects.requireNonNull(factory); - return StableValueFactories.newCachingIntFunction(size, original, factory); + return StableValueFactories.newCachingIntFunction(size, original); } /** @@ -539,48 +339,7 @@ static Function newCachingFunction(Set inputs, Function original) { Objects.requireNonNull(inputs); Objects.requireNonNull(original); - return StableValueFactories.newCachingFunction(inputs, original, null); - } - - /** - * {@return a new caching, thread-safe, stable, lazily computed {@link Function} - * that, for each allowed input in the given set of {@code inputs}, records the - * values of the provided {@code original} Function upon being first accessed via - * {@linkplain Function#apply(Object) Function::apply}, or via background - * threads created from the provided {@code factory} (if non-null)} - *

    - * The provided {@code original} Function is guaranteed to be successfully invoked - * at most once per allowed input, even in a multi-threaded environment. Competing - * threads invoking the {@linkplain Function#apply(Object) Function::apply} method - * when a value is already under computation will block until a value is computed or - * an exception is thrown by the computing thread. - *

    - * If the {@code original} Function invokes the returned Function recursively - * for a particular input value, a StackOverflowError will be thrown when the returned - * Function's {@linkplain Function#apply(Object) Function::apply} method is invoked. - *

    - * If the provided {@code original} Function throws an exception, it is relayed - * to the initial caller. If the memoized Function is computed by a background - * thread, exceptions from the provided {@code original} Function will be relayed to - * the background thread's {@linkplain Thread#getUncaughtExceptionHandler() uncaught - * exception handler}. - *

    - * The order in which background threads are started is unspecified. - * - * @param inputs the set of allowed input values - * @param original Function used to compute a memoized value - * @param factory a factory that will be used to create {@code size} background - * threads that will attempt to compute the memoized values. - * @param the type of the input to the returned Function - * @param the type of results delivered by the returned Function - */ - static Function newCachingFunction(Set inputs, - Function original, - ThreadFactory factory) { - Objects.requireNonNull(inputs); - Objects.requireNonNull(original); - Objects.requireNonNull(factory); - return StableValueFactories.newCachingFunction(inputs, original, factory); + return StableValueFactories.newCachingFunction(inputs, original); } /** diff --git a/src/java.base/share/classes/java/util/ImmutableCollections.java b/src/java.base/share/classes/java/util/ImmutableCollections.java index 7f9e79c8230b2..c1c1738920acd 100644 --- a/src/java.base/share/classes/java/util/ImmutableCollections.java +++ b/src/java.base/share/classes/java/util/ImmutableCollections.java @@ -37,6 +37,7 @@ import java.util.function.Function; import java.util.function.IntFunction; import java.util.function.Predicate; +import java.util.function.Supplier; import java.util.function.UnaryOperator; import jdk.internal.access.JavaUtilCollectionAccess; @@ -789,7 +790,8 @@ static final class LazyList extends AbstractImmutableList { public E get(int i) { try { return backing[i] - .computeIfUnset(i, mapper); + .computeIfUnset(new Supplier() { + @Override public E get() { return mapper.apply(i); }}); } catch (ArrayIndexOutOfBoundsException aioobe) { throw new IndexOutOfBoundsException(i); } @@ -1482,7 +1484,8 @@ public V get(Object key) { } @SuppressWarnings("unchecked") final K k = (K) key; - return stable.computeIfUnset(k, mapper); + return stable.computeIfUnset(new Supplier() { + @Override public V get() { return mapper.apply(k); }}); } @jdk.internal.ValueBased @@ -1514,8 +1517,9 @@ final class LazyMapIterator implements Iterator> { @Override public Entry next() { final Map.Entry> inner = delegateIterator.next(); - final K key = inner.getKey(); - return new NullableKeyValueHolder<>(key, inner.getValue().computeIfUnset(key, mapper)); + final K k = inner.getKey(); + return new NullableKeyValueHolder<>(k, inner.getValue().computeIfUnset(new Supplier() { + @Override public V get() { return mapper.apply(k); }})); } @Override @@ -1524,8 +1528,9 @@ public void forEachRemaining(Consumer> action) { new Consumer<>() { @Override public void accept(Entry> inner) { - final K key = inner.getKey(); - action.accept(new NullableKeyValueHolder<>(key, inner.getValue().computeIfUnset(key, mapper))); + final K k = inner.getKey(); + action.accept(new NullableKeyValueHolder<>(k, inner.getValue().computeIfUnset(new Supplier() { + @Override public V get() { return mapper.apply(k); }}))); } }; delegateIterator.forEachRemaining(innerAction); diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/CachingFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/CachingFunction.java index 0b9419155f210..fd85f345b425d 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/CachingFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/CachingFunction.java @@ -30,6 +30,7 @@ import java.util.Map; import java.util.Set; import java.util.function.Function; +import java.util.function.Supplier; // Note: It would be possible to just use `LazyMap::get` with some additional logic // instead of this class but explicitly providing a class like this provides better @@ -54,7 +55,8 @@ public R apply(T value) { if (stable == null) { throw new IllegalArgumentException("Input not allowed: " + value); } - return stable.computeIfUnset(value, original); + return stable.computeIfUnset(new Supplier() { + @Override public R get() { return original.apply(value); }}); } @Override diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/CachingIntFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/CachingIntFunction.java index 473fdf17b9a65..59abe2459df0a 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/CachingIntFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/CachingIntFunction.java @@ -28,10 +28,8 @@ import jdk.internal.vm.annotation.ForceInline; import jdk.internal.vm.annotation.Stable; -import java.util.Objects; import java.util.function.IntFunction; -import java.util.stream.Collectors; -import java.util.stream.IntStream; +import java.util.function.Supplier; // Note: It would be possible to just use `LazyList::get` instead of this // class but explicitly providing a class like this provides better @@ -47,7 +45,7 @@ * * @param the return type */ -record CachingIntFunction(StableValueImpl[] delegates, +record CachingIntFunction(@Stable StableValueImpl[] delegates, IntFunction original) implements IntFunction { @ForceInline @@ -55,7 +53,8 @@ record CachingIntFunction(StableValueImpl[] delegates, public R apply(int index) { try { return delegates[index] - .computeIfUnset(index, original); + .computeIfUnset(new Supplier() { + @Override public R get() { return original.apply(index); }}); } catch (java.lang.ArrayIndexOutOfBoundsException ioob) { throw new IllegalArgumentException("Input not allowed: " + index, ioob); } diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/CachingSupplier.java b/src/java.base/share/classes/jdk/internal/lang/stable/CachingSupplier.java index 3c78a3e40ec64..787e808e63a74 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/CachingSupplier.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/CachingSupplier.java @@ -26,7 +26,6 @@ package jdk.internal.lang.stable; import jdk.internal.vm.annotation.ForceInline; -import jdk.internal.vm.annotation.Stable; import java.util.function.Supplier; diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableValueFactories.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableValueFactories.java index 01b9c2b5acc68..ab5fd66d79941 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableValueFactories.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableValueFactories.java @@ -18,6 +18,20 @@ public static StableValueImpl newInstance() { return StableValueImpl.newInstance(); } + public static Supplier newCachingSupplier(Supplier original) { + return CachingSupplier.of(original); + } + + public static IntFunction newCachingIntFunction(int size, + IntFunction original) { + return CachingIntFunction.of(size, original); + } + + public static Function newCachingFunction(Set inputs, + Function original) { + return CachingFunction.of(inputs, original); + } + public static StableValueImpl[] ofArray(int size) { if (size < 0) { throw new IllegalArgumentException(); @@ -41,56 +55,5 @@ public static Map> ofMap(Set keys) { return Map.ofEntries(entries); } - public static Supplier newCachingSupplier(Supplier original, - ThreadFactory factory) { - - final Supplier caching = CachingSupplier.of(original); - - if (factory != null) { - final Thread thread = factory.newThread(new Runnable() { - @Override - public void run() { - caching.get(); - } - }); - thread.start(); - } - return caching; - } - - public static IntFunction newCachingIntFunction(int size, - IntFunction original, - ThreadFactory factory) { - - final IntFunction caching = CachingIntFunction.of(size, original); - - if (factory != null) { - for (int i = 0; i < size; i++) { - final int input = i; - final Thread thread = factory.newThread(new Runnable() { - @Override public void run() { caching.apply(input); } - }); - thread.start(); - } - } - return caching; - } - - public static Function newCachingFunction(Set inputs, - Function original, - ThreadFactory factory) { - - final Function caching = CachingFunction.of(inputs, original); - - if (factory != null) { - for (final T t : inputs) { - final Thread thread = factory.newThread(new Runnable() { - @Override public void run() { caching.apply(t); } - }); - thread.start(); - } - } - return caching; - } } 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 dbf8f093a0bdf..cda0ef7de478b 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 @@ -30,9 +30,6 @@ import jdk.internal.vm.annotation.Stable; import java.util.NoSuchElementException; -import java.util.function.BiFunction; -import java.util.function.Function; -import java.util.function.IntFunction; import java.util.function.Supplier; /** @@ -108,63 +105,11 @@ public boolean isSet() { return wrappedValue != null; } - // (p, _) -> p.get() - private static final BiFunction, Object, Object> SUPPLIER_EXTRACTOR = new BiFunction<>() { - @Override public Object apply(Supplier supplier, Object unused) { return supplier.get(); } - }; - - // IntFunction::apply - private static final BiFunction, Integer, Object> INT_FUNCTION_EXTRACTOR = new BiFunction<>() { - @Override public Object apply(IntFunction mapper, Integer key) { return mapper.apply(key); } - }; - - // Function::apply - private static final BiFunction, Object, Object> FUNCTION_EXTRACTOR = new BiFunction<>() { - @Override public Object apply(Function mapper, Object key) { return mapper.apply(key); } - }; @SuppressWarnings("unchecked") @ForceInline @Override public T computeIfUnset(Supplier supplier) { - return computeIfUnset0(null, supplier, (BiFunction, ?, T>) (BiFunction) SUPPLIER_EXTRACTOR); - } - - @SuppressWarnings("unchecked") - @ForceInline - @Override - public T computeIfUnset(int key, IntFunction mapper) { - return computeIfUnset0(key, mapper, (BiFunction, ? super Integer, T>) (BiFunction) INT_FUNCTION_EXTRACTOR); - } - - @SuppressWarnings("unchecked") - @ForceInline - @Override - public T computeIfUnset(K key, Function mapper) { - return computeIfUnset0(key, mapper, (BiFunction, ? super K, T>) (BiFunction) FUNCTION_EXTRACTOR); - } - - @ForceInline - @Override - public T computeIfUnset(K firstKey, L secondKey, BiFunction mapper) { - Object t = wrappedValue; - if (t != null) { - return unwrap(t); - } - synchronized (this) { - t = wrappedValue; - if (t != null) { - return unwrap(t); - } - final T newValue = mapper.apply(firstKey, secondKey); - // The mutex is reentrant so we need to check if the value was actually set. - return wrapAndCas(newValue) ? newValue : orElseThrow(); - } - } - - // A consolidated method for some computeIfUnset overloads - @ForceInline - private T computeIfUnset0(K key, P provider, BiFunction extractor) { Object t = wrappedValue; if (t != null) { return unwrap(t); @@ -174,7 +119,7 @@ private T computeIfUnset0(K key, P provider, BiFunction extracto if (t != null) { return unwrap(t); } - final T newValue = extractor.apply(provider, key); + final T newValue = supplier.get(); // The mutex is reentrant so we need to check if the value was actually set. return wrapAndCas(newValue) ? newValue : orElseThrow(); } diff --git a/test/jdk/java/lang/StableValue/CachingFunctionTest.java b/test/jdk/java/lang/StableValue/CachingFunctionTest.java index 4974b8202a5c1..c0f9eed91a11e 100644 --- a/test/jdk/java/lang/StableValue/CachingFunctionTest.java +++ b/test/jdk/java/lang/StableValue/CachingFunctionTest.java @@ -48,9 +48,6 @@ final class CachingFunctionTest { void factoryInvariants() { assertThrows(NullPointerException.class, () -> StableValue.newCachingFunction(null, MAPPER)); assertThrows(NullPointerException.class, () -> StableValue.newCachingFunction(INPUTS, null)); - assertThrows(NullPointerException.class, () -> StableValue.newCachingFunction(null, MAPPER, Thread.ofVirtual().factory())); - assertThrows(NullPointerException.class, () -> StableValue.newCachingFunction(INPUTS, null, Thread.ofVirtual().factory())); - assertThrows(NullPointerException.class, () -> StableValue.newCachingFunction(INPUTS, MAPPER, null)); } @Test @@ -77,21 +74,6 @@ void basic(Function mapper) { assertTrue(x.getMessage().contains("-1")); } - @Test - void background() { - final AtomicInteger cnt = new AtomicInteger(0); - ThreadFactory factory = r -> new Thread(() -> { - r.run(); - cnt.incrementAndGet(); - }); - var cached = StableValue.newCachingFunction(INPUTS, MAPPER, factory); - while (cnt.get() < 2) { - Thread.onSpinWait(); - } - assertEquals(VALUE, cached.apply(VALUE)); - assertEquals(VALUE2, cached.apply(VALUE2)); - } - @Test void exception() { StableTestUtil.CountingFunction cif = new StableTestUtil.CountingFunction<>(_ -> { diff --git a/test/jdk/java/lang/StableValue/CachingIntFunctionTest.java b/test/jdk/java/lang/StableValue/CachingIntFunctionTest.java index 38509f89a2a45..4351cf26f54e3 100644 --- a/test/jdk/java/lang/StableValue/CachingIntFunctionTest.java +++ b/test/jdk/java/lang/StableValue/CachingIntFunctionTest.java @@ -47,9 +47,6 @@ final class CachingIntFunctionTest { void factoryInvariants() { assertThrows(IllegalArgumentException.class, () -> StableValue.newCachingIntFunction(-1, MAPPER)); assertThrows(NullPointerException.class, () -> StableValue.newCachingIntFunction(SIZE, null)); - assertThrows(IllegalArgumentException.class, () -> StableValue.newCachingIntFunction(-1, MAPPER, Thread.ofVirtual().factory())); - assertThrows(NullPointerException.class, () -> StableValue.newCachingIntFunction(SIZE, null, Thread.ofVirtual().factory())); - assertThrows(NullPointerException.class, () -> StableValue.newCachingIntFunction(SIZE, MAPPER, null)); } @Test @@ -72,26 +69,6 @@ void basic(IntFunction mapper) { assertThrows(IllegalArgumentException.class, () -> cached.apply(1_000_000)); } - @Test - void background() { - final AtomicInteger cnt = new AtomicInteger(0); - ThreadFactory factory = new ThreadFactory() { - @java.lang.Override - public Thread newThread(Runnable r) { - return new Thread(() -> { - r.run(); - cnt.incrementAndGet(); - }); - } - }; - var cached = StableValue.newCachingIntFunction(SIZE, MAPPER, factory); - while (cnt.get() < 2) { - Thread.onSpinWait(); - } - assertEquals(0, cached.apply(0)); - assertEquals(1, cached.apply(1)); - } - @Test void exception() { StableTestUtil.CountingIntFunction cif = new StableTestUtil.CountingIntFunction<>(_ -> { diff --git a/test/jdk/java/lang/StableValue/CachingSupplierTest.java b/test/jdk/java/lang/StableValue/CachingSupplierTest.java index 881a495d3e8db..8a559c39a3c07 100644 --- a/test/jdk/java/lang/StableValue/CachingSupplierTest.java +++ b/test/jdk/java/lang/StableValue/CachingSupplierTest.java @@ -44,7 +44,6 @@ final class CachingSupplierTest { @Test void factoryInvariants() { assertThrows(NullPointerException.class, () -> StableValue.newCachingSupplier(null)); - assertThrows(NullPointerException.class, () -> StableValue.newCachingSupplier(SUPPLIER, null)); } @Test @@ -64,20 +63,6 @@ void basic(Supplier supplier) { assertEquals("CachingSupplier[value=[" + supplier.get() + "], original=" + cs + "]", cached.toString()); } - @Test - void background() { - final AtomicInteger cnt = new AtomicInteger(0); - ThreadFactory factory = r -> new Thread(() -> { - r.run(); - cnt.incrementAndGet(); - }); - var cached = StableValue.newCachingSupplier(SUPPLIER, factory); - while (cnt.get() < 1) { - Thread.onSpinWait(); - } - assertEquals(SUPPLIER.get(), cached.get()); - } - @Test void exception() { StableTestUtil.CountingSupplier cs = new StableTestUtil.CountingSupplier<>(() -> { diff --git a/test/jdk/java/lang/StableValue/JepTest.java b/test/jdk/java/lang/StableValue/JepTest.java index 684e27cc4cdcb..9b55da2eb5710 100644 --- a/test/jdk/java/lang/StableValue/JepTest.java +++ b/test/jdk/java/lang/StableValue/JepTest.java @@ -33,15 +33,21 @@ import java.lang.invoke.VarHandle; import java.nio.file.Files; import java.nio.file.Path; +import java.util.AbstractList; +import java.util.EnumSet; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.IntFunction; +import java.util.function.IntPredicate; import java.util.function.Predicate; import java.util.function.Supplier; import java.util.logging.Logger; import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; final class JepTest { @@ -146,7 +152,7 @@ class Foo2 { // 1. Centrally declare a caching supplier and define how it should be computed private static final Supplier LOGGER = - StableValue.newCachingSupplier( () -> Logger.getLogger("com.company.Foo"), null ); + StableValue.newCachingSupplier( () -> Logger.getLogger("com.company.Foo") ); static Logger logger() { @@ -174,7 +180,7 @@ static Logger logger(String name) { class CachedNum { // 1. Centrally declare a cached IntFunction backed by a list of StableValue elements private static final IntFunction LOGGERS = - StableValue.newCachingIntFunction(2, CachedNum::fromNumber, null); + StableValue.newCachingIntFunction(2, CachedNum::fromNumber); // 2. Define a function that is to be called the first // time a particular message number is referenced @@ -197,24 +203,7 @@ class Cached { // 1. Centrally declare a cached function backed by a map of stable values private static final Function LOGGERS = StableValue.newCachingFunction(Set.of("com.company.Foo", "com.company.Bar"), - Logger::getLogger, null); - - private static final String NAME = "com.company.Foo"; - - // 2. Access the cached value via the function with as-declared-final - // performance (evaluation made before the first access) - Logger logger = LOGGERS.apply(NAME); - } - - class CachedBackground { - - // 1. Centrally declare a cached function backed by a map of stable values - // computed in the background by two distinct virtual threads. - private static final Function LOGGERS = - StableValue.newCachingFunction(Set.of("com.company.Foo", "com.company.Bar"), - Logger::getLogger, - // Create cheap virtual threads for background computation - Thread.ofVirtual().factory()); + Logger::getLogger); private static final String NAME = "com.company.Foo"; @@ -231,7 +220,7 @@ class ErrorMessages { // 1. Centrally declare a cached IntFunction backed by a list of StableValue elements private static final IntFunction ERROR_FUNCTION = - StableValue.newCachingIntFunction(SIZE, ErrorMessages::readFromFile, null); + StableValue.newCachingIntFunction(SIZE, ErrorMessages::readFromFile); // 2. Define a function that is to be called the first // time a particular message number is referenced @@ -266,7 +255,7 @@ public boolean test(T t) { throw new IllegalArgumentException(t.toString()); } - return stable.computeIfUnset(t, original); + return stable.computeIfUnset(() -> original.apply(t)); } } @@ -299,7 +288,7 @@ public R apply(T t, U u) { if (stable == null) { throw new IllegalArgumentException(t + ", " + u); } - return stable.computeIfUnset(key, original); + return stable.computeIfUnset(() -> original.apply(key)); } static CachingBiFunction of(Set> inputs, BiFunction original) { @@ -322,7 +311,7 @@ public R apply(T t, U u) { if (stable == null) { throw new IllegalArgumentException(t + ", " + u); } - return stable.computeIfUnset(key.left(), key.right(), original); + return stable.computeIfUnset(() -> original.apply(key.left(), key.right())); } static BiFunction of(Set> inputs, BiFunction original) { @@ -335,4 +324,60 @@ static BiFunction of(Set> inputs, BiFunction LOGGER = StableValue.newInstance(); + + public Logger getLogger() { + return LOGGER.computeIfUnset(() -> Logger.getLogger("com.company.Foo")); + } + } + + record CachingIntPredicate(List> outputs, + IntPredicate resultFunction) implements IntPredicate { + + CachingIntPredicate(int size, IntPredicate resultFunction) { + this(Stream.generate(StableValue::newInstance).limit(size).toList(), resultFunction); + } + + @Override + public boolean test(int value) { + return outputs.get(value).computeIfUnset(() -> resultFunction.test(value)); + } + + } + + + static + class FixedStableList extends AbstractList { + private final List> elements; + + FixedStableList(int size) { + this.elements = Stream.generate(StableValue::newInstance) + .limit(size) + .toList(); + } + + @Override + public E get(int index) { + return elements.get(index).orElseThrow(); + } + + + + @Override + public E set(int index, E element) { + elements.get(index).setOrThrow(element); + return null; + } + + + + @Override + public int size() { + return elements.size(); + } + } + + } diff --git a/test/jdk/java/lang/StableValue/StableValueTest.java b/test/jdk/java/lang/StableValue/StableValueTest.java index a6ad665d0899d..073245213be2b 100644 --- a/test/jdk/java/lang/StableValue/StableValueTest.java +++ b/test/jdk/java/lang/StableValue/StableValueTest.java @@ -103,36 +103,6 @@ void testComputeIfUnsetSupplier() { assertEquals(1, cs.cnt()); } - @Test - void testComputeIfUnsetIntFunction() { - StableTestUtil.CountingIntFunction cs = new StableTestUtil.CountingIntFunction<>(i -> i); - StableValue stable = StableValue.newInstance(); - assertEquals(VALUE, stable.computeIfUnset(VALUE, cs)); - assertEquals(1, cs.cnt()); - assertEquals(VALUE, stable.computeIfUnset(VALUE, cs)); - assertEquals(1, cs.cnt()); - } - - @Test - void testComputeIfUnsetFunction() { - StableTestUtil.CountingFunction cs = new StableTestUtil.CountingFunction<>(Function.identity()); - StableValue stable = StableValue.newInstance(); - assertEquals(VALUE, stable.computeIfUnset(VALUE, cs)); - assertEquals(1, cs.cnt()); - assertEquals(VALUE, stable.computeIfUnset(VALUE, cs)); - assertEquals(1, cs.cnt()); - } - - @Test - void testComputeIfUnsetBiFunction() { - StableTestUtil.CountingBiFunction cs = new StableTestUtil.CountingBiFunction<>(Integer::max); - StableValue stable = StableValue.newInstance(); - assertEquals(VALUE, stable.computeIfUnset(VALUE, 0, cs)); - assertEquals(1, cs.cnt()); - assertEquals(VALUE, stable.computeIfUnset(VALUE, 0, cs)); - assertEquals(1, cs.cnt()); - } - @Test void testHashCode() { StableValue stableValue = StableValue.newInstance(); diff --git a/test/micro/org/openjdk/bench/java/lang/stable/CachingFunctionBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/CachingFunctionBenchmark.java index e888fd5d3cbec..274e165df4ccb 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/CachingFunctionBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/CachingFunctionBenchmark.java @@ -51,9 +51,8 @@ @Warmup(iterations = 5, time = 1) @Measurement(iterations = 5, time = 2) @Fork(value = 2, jvmArgsAppend = { - "--enable-preview", - // Prevent the use of uncommon traps - "-XX:PerMethodTrapLimit=0"}) + "--enable-preview" +}) @Threads(Threads.MAX) // Benchmark under contention @OperationsPerInvocation(100) public class CachingFunctionBenchmark { diff --git a/test/micro/org/openjdk/bench/java/lang/stable/CachingIntFunctionBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/CachingIntFunctionBenchmark.java index 96e784968d564..c1d3d06f45d82 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/CachingIntFunctionBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/CachingIntFunctionBenchmark.java @@ -48,9 +48,8 @@ @Warmup(iterations = 5, time = 1) @Measurement(iterations = 5, time = 2) @Fork(value = 2, jvmArgsAppend = { - "--enable-preview", - // Prevent the use of uncommon traps - "-XX:PerMethodTrapLimit=0"}) + "--enable-preview" +}) @Threads(Threads.MAX) // Benchmark under contention @OperationsPerInvocation(100) public class CachingIntFunctionBenchmark { @@ -73,6 +72,7 @@ public int stable() { return sum; } + @Benchmark public int intFunction() { int sum = 0; diff --git a/test/micro/org/openjdk/bench/java/lang/stable/CachingSupplierBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/CachingSupplierBenchmark.java index 2f8a334f3dc96..0039e5efc732a 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/CachingSupplierBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/CachingSupplierBenchmark.java @@ -47,9 +47,8 @@ @Warmup(iterations = 5, time = 1) @Measurement(iterations = 5, time = 2) @Fork(value = 2, jvmArgsAppend = { - "--enable-preview", - // Prevent the use of uncommon traps - "-XX:PerMethodTrapLimit=0"}) + "--enable-preview" +}) @Threads(Threads.MAX) // Benchmark under contention @OperationsPerInvocation(2) public class CachingSupplierBenchmark { @@ -77,6 +76,7 @@ public int supplier() { return supplier.get() + supplier2.get(); } +/* @Benchmark public int staticStable() { return STABLE.orElseThrow() + STABLE2.orElseThrow(); @@ -86,6 +86,7 @@ public int staticStable() { public int staticSupplier() { return SUPPLIER.get() + SUPPLIER2.get(); } +*/ private static StableValue init(StableValue m, Integer value) { m.trySet(value); diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java index 4051bf81f62f0..7e8fcd9434590 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java @@ -38,9 +38,8 @@ @Warmup(iterations = 5, time = 1) @Measurement(iterations = 5, time = 2) @Fork(value = 2, jvmArgsAppend = { - "--enable-preview", - // Prevent the use of uncommon traps - "-XX:PerMethodTrapLimit=0"}) + "--enable-preview" +}) @Threads(Threads.MAX) // Benchmark under contention @OperationsPerInvocation(2) public class StableValueBenchmark { From ba22e20e3f4245a206c7c3ccf0de4b34077f0f25 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 9 Sep 2024 17:39:35 +0200 Subject: [PATCH 131/327] Make arrays of SV trusted --- src/hotspot/share/ci/ciField.cpp | 3 +- src/hotspot/share/classfile/vmSymbols.hpp | 1 + src/hotspot/share/runtime/fieldDescriptor.cpp | 3 +- .../share/classes/java/lang/StableValue.java | 12 +++--- .../share/classes/sun/misc/Unsafe.java | 9 ++++- test/jdk/java/lang/StableValue/JepTest.java | 10 ++--- .../StableValue/TrustedFieldTypeTest.java | 39 +++++++++++++++++++ 7 files changed, 62 insertions(+), 15 deletions(-) diff --git a/src/hotspot/share/ci/ciField.cpp b/src/hotspot/share/ci/ciField.cpp index 0bddef4868758..0c87197efccc4 100644 --- a/src/hotspot/share/ci/ciField.cpp +++ b/src/hotspot/share/ci/ciField.cpp @@ -254,7 +254,8 @@ static bool trust_final_non_static_fields(ciInstanceKlass* holder) { } static bool trust_final_non_static_fields_of_type(Symbol* signature) { - return (signature == vmSymbols::java_lang_StableValue_signature()); + return signature == vmSymbols::java_lang_StableValue_signature() || + signature == vmSymbols::java_lang_StableValue_array_signature(); } void ciField::initialize_from(fieldDescriptor* fd) { diff --git a/src/hotspot/share/classfile/vmSymbols.hpp b/src/hotspot/share/classfile/vmSymbols.hpp index 93d247dc52d28..463bdb6fbd4a7 100644 --- a/src/hotspot/share/classfile/vmSymbols.hpp +++ b/src/hotspot/share/classfile/vmSymbols.hpp @@ -750,6 +750,7 @@ class SerializeClosure; \ /* Stable Values */ \ template(java_lang_StableValue_signature, "Ljava/lang/StableValue;") \ + template(java_lang_StableValue_array_signature, "[Ljava/lang/StableValue;") \ /*end*/ // enum for figuring positions and size of Symbol::_vm_symbols[] diff --git a/src/hotspot/share/runtime/fieldDescriptor.cpp b/src/hotspot/share/runtime/fieldDescriptor.cpp index 5268a457db34f..4445105795ee1 100644 --- a/src/hotspot/share/runtime/fieldDescriptor.cpp +++ b/src/hotspot/share/runtime/fieldDescriptor.cpp @@ -44,7 +44,8 @@ Symbol* fieldDescriptor::generic_signature() const { bool fieldDescriptor::is_trusted_final() const { InstanceKlass* ik = field_holder(); - return is_final() && (is_static() || ik->is_hidden() || ik->is_record() || signature() == vmSymbols::java_lang_StableValue_signature()); + return is_final() && (is_static() || ik->is_hidden() || ik->is_record() || + signature() == vmSymbols::java_lang_StableValue_signature() || signature() == vmSymbols::java_lang_StableValue_array_signature()); } AnnotationArray* fieldDescriptor::annotations() const { diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 52376ca202b27..44f71cadcc258 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -130,12 +130,12 @@ * care should be taken whenever (directly or indirectly) synchronizing on * a StableValue. Failure to do this may lead to deadlock. * - * @implNote Instance fields explicitly declared as StableValue are eligible for certain - * JVM optimizations compared to normal instance fields. This comes with - * restrictions on reflective modifications. Although most ways of reflective - * modification of such fields are disabled, it is strongly discouraged to - * circumvent these protection means as reflectively modifying such fields may - * lead to unspecified behavior. + * @implNote Instance fields explicitly declared as StableValue or one-dimensional arrays + * thereof are eligible for certain JVM optimizations compared to normal + * instance fields. This comes with restrictions on reflective modifications. + * Although most ways of reflective modification of such fields are disabled, + * it is strongly discouraged to circumvent these protection means as + * reflectively modifying such fields may lead to unspecified behavior. * * @param type of the holder value * diff --git a/src/jdk.unsupported/share/classes/sun/misc/Unsafe.java b/src/jdk.unsupported/share/classes/sun/misc/Unsafe.java index 01351e600bdf9..8482fd1cb226d 100644 --- a/src/jdk.unsupported/share/classes/sun/misc/Unsafe.java +++ b/src/jdk.unsupported/share/classes/sun/misc/Unsafe.java @@ -993,12 +993,17 @@ private static void assertNotTrusted(Field f) { throw new UnsupportedOperationException("can't get base address on a record class: " + f); } Class fieldType = f.getType(); - // Todo: Change to "java.lang.StableValue.class.isAssignableFrom(fieldType)" after StableValue exits preview - if (fieldType.getName().equals("java.lang.StableValue")) { + // Todo: Change to "java.lang.StableValue.class.isAssignableFrom(fieldType)" etc. after StableValue exits preview + if (fieldType.getName().equals("java.lang.StableValue") || (fieldType.isArray() && deepComponent(fieldType).getName().equals("java.lang.StableValue"))) { throw new UnsupportedOperationException("can't get field offset for a field of type " + fieldType.getName() + ": " + f); } } + @ForceInline + private static Class deepComponent(Class clazz) { + return clazz.isArray() ? deepComponent(clazz.getComponentType()) : clazz; + } + /** The value of {@code arrayBaseOffset(boolean[].class)}. * * @deprecated Not needed when using {@link VarHandle} or {@link java.lang.foreign}. diff --git a/test/jdk/java/lang/StableValue/JepTest.java b/test/jdk/java/lang/StableValue/JepTest.java index 9b55da2eb5710..40259aeb98727 100644 --- a/test/jdk/java/lang/StableValue/JepTest.java +++ b/test/jdk/java/lang/StableValue/JepTest.java @@ -350,24 +350,24 @@ public boolean test(int value) { static class FixedStableList extends AbstractList { - private final List> elements; + private final StableValue[] elements; FixedStableList(int size) { this.elements = Stream.generate(StableValue::newInstance) .limit(size) - .toList(); + .toArray(StableValue[]::new); } @Override public E get(int index) { - return elements.get(index).orElseThrow(); + return elements[index].orElseThrow(); } @Override public E set(int index, E element) { - elements.get(index).setOrThrow(element); + elements[index].setOrThrow(element); return null; } @@ -375,7 +375,7 @@ public E set(int index, E element) { @Override public int size() { - return elements.size(); + return elements.length; } } diff --git a/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java b/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java index 7f6f8df980385..64d0e327a35d7 100644 --- a/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java +++ b/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java @@ -47,6 +47,9 @@ final class Holder { final class HolderNonFinal { private StableValue value = StableValue.newInstance(); } + final class ArrayHolder { + private final StableValue[] array = (StableValue[]) new StableValue[]{}; + } Field valueField = Holder.class.getDeclaredField("value"); valueField.setAccessible(true); @@ -64,6 +67,16 @@ final class HolderNonFinal { // As the field is not final, both read and write should be ok (not trusted) Object readNonFinal = valueNonFinal.get(holderNonFinal); valueNonFinal.set(holderNonFinal, StableValue.newInstance()); + + Field arrayField = ArrayHolder.class.getDeclaredField("array"); + arrayField.setAccessible(true); + ArrayHolder arrayHolder = new ArrayHolder(); + // We should be able to read the StableValue array + read = arrayField.get(arrayHolder); + // We should NOT be able to write to the StableValue array + assertThrows(IllegalAccessException.class, () -> + arrayField.set(arrayHolder, new StableValue[1]) + ); } @SuppressWarnings("removal") @@ -76,12 +89,21 @@ void sunMiscUnsafe() throws NoSuchFieldException, IllegalAccessException { final class Holder { private final StableValue value = StableValue.newInstance(); } + final class ArrayHolder { + private final StableValue[] array = (StableValue[]) new StableValue[]{}; + } Field valueField = Holder.class.getDeclaredField("value"); assertThrows(UnsupportedOperationException.class, () -> unsafe.objectFieldOffset(valueField) ); + Field arrayField = ArrayHolder.class.getDeclaredField("array"); + + assertThrows(UnsupportedOperationException.class, () -> + unsafe.objectFieldOffset(arrayField) + ); + } @Test @@ -89,10 +111,16 @@ void varHandle() throws NoSuchFieldException, IllegalAccessException { MethodHandles.Lookup lookup = MethodHandles.lookup(); StableValue originalValue = StableValue.newInstance(); + @SuppressWarnings("unchecked") + StableValue[] originalArrayValue = new StableValue[10]; final class Holder { private final StableValue value = originalValue; } + final class ArrayHolder { + private final StableValue[] array = originalArrayValue; + } + VarHandle valueVarHandle = lookup.findVarHandle(Holder.class, "value", StableValue.class); Holder holder = new Holder(); @@ -105,6 +133,17 @@ final class Holder { valueVarHandle.compareAndSet(holder, originalValue, StableValue.newInstance()) ); + VarHandle arrayVarHandle = lookup.findVarHandle(ArrayHolder.class, "array", StableValue[].class); + ArrayHolder arrayHolder = new ArrayHolder(); + + assertThrows(UnsupportedOperationException.class, () -> + arrayVarHandle.set(arrayHolder, new StableValue[1]) + ); + + assertThrows(UnsupportedOperationException.class, () -> + arrayVarHandle.compareAndSet(arrayHolder, originalArrayValue, new StableValue[1]) + ); + } } From 65c83ea7431a15e3340b39ad7c5811f991613078 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 10 Sep 2024 09:58:16 +0200 Subject: [PATCH 132/327] Add support for a special CachingEnumFunction --- .../lang/stable/CachingEnumFunction.java | 115 +++++++++++++ .../lang/stable/CachingIntFunction.java | 2 +- .../lang/stable/EmptyCachingFunction.java | 69 ++++++++ .../lang/stable/StableValueFactories.java | 19 ++- .../lang/StableValue/CachingFunctionTest.java | 152 +++++++++++++----- 5 files changed, 316 insertions(+), 41 deletions(-) create mode 100644 src/java.base/share/classes/jdk/internal/lang/stable/CachingEnumFunction.java create mode 100644 src/java.base/share/classes/jdk/internal/lang/stable/EmptyCachingFunction.java diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/CachingEnumFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/CachingEnumFunction.java new file mode 100644 index 0000000000000..579d5455c4a47 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/lang/stable/CachingEnumFunction.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2024, 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.vm.annotation.ForceInline; +import jdk.internal.vm.annotation.Stable; + +import java.util.Objects; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Supplier; + +/** + * Optimized implementation of a cached Function with enums as keys. + * + * @implNote This implementation can be used early in the boot sequence as it does not + * rely on reflection, MethodHandles, Streams etc. + * + * @param firstOrdinal the lowest ordinal used + * @param delegates a delegate array of inputs to StableValue mappings + * @param original the original Function + * @param the type of the input to the function + * @param the type of the result of the function + */ +record CachingEnumFunction, R>(Class enumType, + int firstOrdinal, + @Stable StableValueImpl[] delegates, + Function original) implements Function { + @ForceInline + @Override + public R apply(E value) { + final int index = value.ordinal() - firstOrdinal; + try { + return delegates[index] + .computeIfUnset(new Supplier() { + @Override public R get() { return original.apply(value); }}); + } catch (ArrayIndexOutOfBoundsException ioob) { + throw new IllegalArgumentException("Input not allowed: " + value, ioob); + } + } + + @Override + public int hashCode() { + return System.identityHashCode(this); + } + + @Override + public boolean equals(Object obj) { + return obj == this; + } + + @Override + public String toString() { + return "CachingEnumFunction[values=" + renderElements() + ", original=" + original + "]"; + } + + private String renderElements() { + final StringBuilder sb = new StringBuilder(); + sb.append("{"); + boolean first = true; + int ordinal = firstOrdinal; + final E[] enumElements = enumType.getEnumConstants(); + for (int i = 0; i < delegates.length; i++) { + if (first) { first = false; } else { sb.append(", "); }; + final Object value = delegates[i].wrappedValue(); + sb.append(enumElements[ordinal++]).append('='); + if (value == this) { + sb.append("(this CachingEnumFunction)"); + } else { + sb.append(StableValueImpl.renderWrapped(value)); + } + } + sb.append("}"); + return sb.toString(); + } + + @SuppressWarnings("unchecked") + static , R> Function of(Set inputs, + Function original) { + // The input set is not empty + int min = Integer.MAX_VALUE; + int max = Integer.MIN_VALUE; + for (T t : inputs) { + min = Math.min(min, ((E) t).ordinal()); + max = Math.max(max, ((E) t).ordinal()); + } + final int size = max - min + 1; + final Class enumType = (Class)inputs.iterator().next().getClass(); + return (Function) new CachingEnumFunction(enumType, min, StableValueFactories.ofArray(size), (Function) original); + } + +} diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/CachingIntFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/CachingIntFunction.java index 59abe2459df0a..b6e5173b0a411 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/CachingIntFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/CachingIntFunction.java @@ -55,7 +55,7 @@ public R apply(int index) { return delegates[index] .computeIfUnset(new Supplier() { @Override public R get() { return original.apply(index); }}); - } catch (java.lang.ArrayIndexOutOfBoundsException ioob) { + } catch (ArrayIndexOutOfBoundsException ioob) { throw new IllegalArgumentException("Input not allowed: " + index, ioob); } } diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/EmptyCachingFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/EmptyCachingFunction.java new file mode 100644 index 0000000000000..f82ccbc3643ae --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/lang/stable/EmptyCachingFunction.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2024, 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.vm.annotation.ForceInline; + +import java.util.function.Function; + +/** + * An empty caching function with no allowed inputs + * + * @implNote This implementation can be used early in the boot sequence as it does not + * rely on reflection, MethodHandles, Streams etc. + * + * @param original the original Function + * @param the type of the input to the function + * @param the type of the result of the function + */ +record EmptyCachingFunction(Function original) implements Function { + + @ForceInline + @Override + public R apply(T value) { + throw new IllegalArgumentException("Input not allowed: " + value); + } + + @Override + public int hashCode() { + return System.identityHashCode(this); + } + + @Override + public boolean equals(Object obj) { + return obj == this; + } + + @Override + public String toString() { + return "EmptyCachingFunction[values={}, original=" + original + "]"; + } + + static Function of(Function original) { + return new EmptyCachingFunction<>(original); + } + +} diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableValueFactories.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableValueFactories.java index ab5fd66d79941..702db7172ec8b 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableValueFactories.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableValueFactories.java @@ -1,5 +1,6 @@ package jdk.internal.lang.stable; +import java.util.EnumSet; import java.util.Map; import java.util.Objects; import java.util.Set; @@ -29,7 +30,23 @@ public static IntFunction newCachingIntFunction(int size, public static Function newCachingFunction(Set inputs, Function original) { - return CachingFunction.of(inputs, original); + if (inputs.isEmpty()) { + return EmptyCachingFunction.of(original); + } + return inputs instanceof EnumSet + ? CachingEnumFunction.of(inputs, original) + : CachingFunction.of(inputs, original); + } + + @SuppressWarnings("unchecked") + private static > EnumSet asEnumSet(Set original) { + return (EnumSet) original; + } + + public static , R> Function newCachingEnumFunction(EnumSet inputs, + Function original) { + + return CachingEnumFunction.of(inputs, original); } public static StableValueImpl[] ofArray(int size) { diff --git a/test/jdk/java/lang/StableValue/CachingFunctionTest.java b/test/jdk/java/lang/StableValue/CachingFunctionTest.java index c0f9eed91a11e..27f2190e70085 100644 --- a/test/jdk/java/lang/StableValue/CachingFunctionTest.java +++ b/test/jdk/java/lang/StableValue/CachingFunctionTest.java @@ -27,97 +27,171 @@ * @run junit/othervm --enable-preview CachingFunctionTest */ -import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import java.util.Arrays; +import java.util.Comparator; +import java.util.EnumSet; +import java.util.LinkedHashSet; import java.util.Set; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.TreeSet; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; +import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.*; final class CachingFunctionTest { - private static final int VALUE = 42; - private static final int VALUE2 = 13; - private static final Set INPUTS = Set.of(VALUE, VALUE2); - private static final Function MAPPER = Function.identity(); + enum Value { + THIRTEEN(13), + FORTY_TWO(42), + ILLEGAL(-1); - @Test - void factoryInvariants() { + final int intValue; + + Value(int intValue) { + this.intValue = intValue; + } + + int asInt() { + return intValue; + } + + } + + private static final Value VALUE = Value.FORTY_TWO; + private static final Value VALUE2 = Value.THIRTEEN; + private static final Function MAPPER = Value::asInt; + + @ParameterizedTest + @MethodSource("allSets") + void factoryInvariants(Set inputs) { assertThrows(NullPointerException.class, () -> StableValue.newCachingFunction(null, MAPPER)); - assertThrows(NullPointerException.class, () -> StableValue.newCachingFunction(INPUTS, null)); + assertThrows(NullPointerException.class, () -> StableValue.newCachingFunction(inputs, null)); } - @Test - void basic() { - basic(MAPPER); - basic(_ -> null); + @ParameterizedTest + @MethodSource("nonEmptySets") + void basic(Set inputs) { + basic(inputs, MAPPER); + basic(inputs, _ -> null); } - void basic(Function mapper) { - StableTestUtil.CountingFunction cif = new StableTestUtil.CountingFunction<>(mapper); - var cached = StableValue.newCachingFunction(INPUTS, cif); + void basic(Set inputs, Function mapper) { + StableTestUtil.CountingFunction cif = new StableTestUtil.CountingFunction<>(mapper); + var cached = StableValue.newCachingFunction(inputs, cif); assertEquals(mapper.apply(VALUE), cached.apply(VALUE)); assertEquals(1, cif.cnt()); assertEquals(mapper.apply(VALUE), cached.apply(VALUE)); assertEquals(1, cif.cnt()); - assertTrue(cached.toString().startsWith("CachingFunction[values={")); + assertTrue(cached.toString().startsWith(cached.getClass().getSimpleName() + "[values={")); // Key order is unspecified assertTrue(cached.toString().contains(VALUE2 + "=.unset")); assertTrue(cached.toString().contains(VALUE + "=[" + mapper.apply(VALUE) + "]")); assertTrue(cached.toString().endsWith(", original=" + cif + "]")); // One between the values and one just before "original" assertEquals(2L, cached.toString().chars().filter(ch -> ch == ',').count()); - var x = assertThrows(IllegalArgumentException.class, () -> cached.apply(-1)); - assertTrue(x.getMessage().contains("-1")); + var x = assertThrows(IllegalArgumentException.class, () -> cached.apply(Value.ILLEGAL)); + assertTrue(x.getMessage().contains("ILLEGAL")); } - @Test - void exception() { - StableTestUtil.CountingFunction cif = new StableTestUtil.CountingFunction<>(_ -> { + @ParameterizedTest + @MethodSource("emptySets") + void empty(Set inputs) { + Function f0 = StableValue.newCachingFunction(inputs, Value::asInt); + assertTrue(f0.toString().contains("{}")); + } + + @ParameterizedTest + @MethodSource("nonEmptySets") + void exception(Set inputs) { + StableTestUtil.CountingFunction cif = new StableTestUtil.CountingFunction<>(_ -> { throw new UnsupportedOperationException(); }); - var cached = StableValue.newCachingFunction(INPUTS, cif); + var cached = StableValue.newCachingFunction(inputs, cif); assertThrows(UnsupportedOperationException.class, () -> cached.apply(VALUE)); assertEquals(1, cif.cnt()); assertThrows(UnsupportedOperationException.class, () -> cached.apply(VALUE)); assertEquals(2, cif.cnt()); - assertTrue(cached.toString().startsWith("CachingFunction[values={")); + assertTrue(cached.toString().startsWith(cached.getClass().getSimpleName() + "[values={")); // Key order is unspecified assertTrue(cached.toString().contains(VALUE2 + "=.unset")); assertTrue(cached.toString().contains(VALUE + "=.unset")); assertTrue(cached.toString().endsWith(", original=" + cif + "]")); } - @Test - void circular() { + @ParameterizedTest + @MethodSource("nonEmptySets") + void circular(Set inputs) { final AtomicReference> ref = new AtomicReference<>(); - Function> cached = StableValue.newCachingFunction(INPUTS, _ -> ref.get()); + Function> cached = StableValue.newCachingFunction(inputs, _ -> ref.get()); ref.set(cached); cached.apply(VALUE); String toString = cached.toString(); - assertTrue(toString.contains("(this CachingFunction)")); + assertTrue(toString.contains("(this " + cached.getClass().getSimpleName() + ")")); assertDoesNotThrow(cached::hashCode); assertDoesNotThrow((() -> cached.equals(cached))); } - @Test - void equality() { - Function mapper = Function.identity(); - Function f0 = StableValue.newCachingFunction(INPUTS, mapper); - Function f1 = StableValue.newCachingFunction(INPUTS, mapper); + @ParameterizedTest + @MethodSource("allSets") + void equality(Set inputs) { + Function mapper = Value::asInt; + Function f0 = StableValue.newCachingFunction(inputs, mapper); + Function f1 = StableValue.newCachingFunction(inputs, mapper); // No function is equal to another function assertNotEquals(f0, f1); } - @Test - void hashCodeStable() { - Function f0 = StableValue.newCachingFunction(INPUTS, Function.identity()); - assertEquals(System.identityHashCode(f0), f0.hashCode()); - f0.apply(VALUE); + @ParameterizedTest + @MethodSource("allSets") + void hashCodeStable(Set inputs) { + Function f0 = StableValue.newCachingFunction(inputs, Value::asInt); assertEquals(System.identityHashCode(f0), f0.hashCode()); + if (!inputs.isEmpty()) { + f0.apply(VALUE); + assertEquals(System.identityHashCode(f0), f0.hashCode()); + } + } + + private static Stream> nonEmptySets() { + return Stream.of( + Set.of(VALUE, VALUE2), + linkedHashSet(VALUE, VALUE2), + treeSet(VALUE2, VALUE), + EnumSet.of(VALUE, VALUE2) + ); + } + + private static Stream> emptySets() { + return Stream.of( + Set.of(), + linkedHashSet(), + treeSet(), + EnumSet.noneOf(Value.class) + ); + } + + private static Stream> allSets() { + return Stream.concat( + nonEmptySets(), + emptySets() + ); + } + + static Set treeSet(Value... values) { + return populate(new TreeSet<>(Comparator.comparingInt(Value::asInt).reversed()),values); + } + + static Set linkedHashSet(Value... values) { + return populate(new LinkedHashSet<>(), values); + } + + static Set populate(Set set, Value... values) { + set.addAll(Arrays.asList(values)); + return set; } } From 1aad10b3922f90f1495a7121e93d5adb2a378881 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 10 Sep 2024 10:10:29 +0200 Subject: [PATCH 133/327] Improve test --- .../lang/StableValue/CachingFunctionTest.java | 44 ++++++++++++------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/test/jdk/java/lang/StableValue/CachingFunctionTest.java b/test/jdk/java/lang/StableValue/CachingFunctionTest.java index 27f2190e70085..76eced902b5ea 100644 --- a/test/jdk/java/lang/StableValue/CachingFunctionTest.java +++ b/test/jdk/java/lang/StableValue/CachingFunctionTest.java @@ -27,6 +27,7 @@ * @run junit/othervm --enable-preview CachingFunctionTest */ +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -45,8 +46,13 @@ final class CachingFunctionTest { enum Value { + // Zero is here so that we have enums with ordinals before the first one + // actually used in input sets (i.e. ZERO is not in the input set) + ZERO(0), + // Valid values THIRTEEN(13), FORTY_TWO(42), + // Illegal values (not in the input set) ILLEGAL(-1); final int intValue; @@ -61,8 +67,6 @@ int asInt() { } - private static final Value VALUE = Value.FORTY_TWO; - private static final Value VALUE2 = Value.THIRTEEN; private static final Function MAPPER = Value::asInt; @ParameterizedTest @@ -82,14 +86,14 @@ void basic(Set inputs) { void basic(Set inputs, Function mapper) { StableTestUtil.CountingFunction cif = new StableTestUtil.CountingFunction<>(mapper); var cached = StableValue.newCachingFunction(inputs, cif); - assertEquals(mapper.apply(VALUE), cached.apply(VALUE)); + assertEquals(mapper.apply(Value.FORTY_TWO), cached.apply(Value.FORTY_TWO)); assertEquals(1, cif.cnt()); - assertEquals(mapper.apply(VALUE), cached.apply(VALUE)); + assertEquals(mapper.apply(Value.FORTY_TWO), cached.apply(Value.FORTY_TWO)); assertEquals(1, cif.cnt()); assertTrue(cached.toString().startsWith(cached.getClass().getSimpleName() + "[values={")); // Key order is unspecified - assertTrue(cached.toString().contains(VALUE2 + "=.unset")); - assertTrue(cached.toString().contains(VALUE + "=[" + mapper.apply(VALUE) + "]")); + assertTrue(cached.toString().contains(Value.THIRTEEN + "=.unset")); + assertTrue(cached.toString().contains(Value.FORTY_TWO + "=[" + mapper.apply(Value.FORTY_TWO) + "]")); assertTrue(cached.toString().endsWith(", original=" + cif + "]")); // One between the values and one just before "original" assertEquals(2L, cached.toString().chars().filter(ch -> ch == ',').count()); @@ -111,14 +115,14 @@ void exception(Set inputs) { throw new UnsupportedOperationException(); }); var cached = StableValue.newCachingFunction(inputs, cif); - assertThrows(UnsupportedOperationException.class, () -> cached.apply(VALUE)); + assertThrows(UnsupportedOperationException.class, () -> cached.apply(Value.FORTY_TWO)); assertEquals(1, cif.cnt()); - assertThrows(UnsupportedOperationException.class, () -> cached.apply(VALUE)); + assertThrows(UnsupportedOperationException.class, () -> cached.apply(Value.FORTY_TWO)); assertEquals(2, cif.cnt()); assertTrue(cached.toString().startsWith(cached.getClass().getSimpleName() + "[values={")); // Key order is unspecified - assertTrue(cached.toString().contains(VALUE2 + "=.unset")); - assertTrue(cached.toString().contains(VALUE + "=.unset")); + assertTrue(cached.toString().contains(Value.THIRTEEN + "=.unset")); + assertTrue(cached.toString().contains(Value.FORTY_TWO + "=.unset")); assertTrue(cached.toString().endsWith(", original=" + cif + "]")); } @@ -128,7 +132,7 @@ void circular(Set inputs) { final AtomicReference> ref = new AtomicReference<>(); Function> cached = StableValue.newCachingFunction(inputs, _ -> ref.get()); ref.set(cached); - cached.apply(VALUE); + cached.apply(Value.FORTY_TWO); String toString = cached.toString(); assertTrue(toString.contains("(this " + cached.getClass().getSimpleName() + ")")); assertDoesNotThrow(cached::hashCode); @@ -151,17 +155,25 @@ void hashCodeStable(Set inputs) { Function f0 = StableValue.newCachingFunction(inputs, Value::asInt); assertEquals(System.identityHashCode(f0), f0.hashCode()); if (!inputs.isEmpty()) { - f0.apply(VALUE); + f0.apply(Value.FORTY_TWO); assertEquals(System.identityHashCode(f0), f0.hashCode()); } } + @Test + void usesOptimizedVersion() { + Function enumFunction = StableValue.newCachingFunction(EnumSet.of(Value.FORTY_TWO), Value::asInt); + assertEquals("jdk.internal.lang.stable.CachingEnumFunction", enumFunction.getClass().getName()); + Function emptyFunction = StableValue.newCachingFunction(Set.of(), Value::asInt); + assertEquals("jdk.internal.lang.stable.EmptyCachingFunction", emptyFunction.getClass().getName()); + } + private static Stream> nonEmptySets() { return Stream.of( - Set.of(VALUE, VALUE2), - linkedHashSet(VALUE, VALUE2), - treeSet(VALUE2, VALUE), - EnumSet.of(VALUE, VALUE2) + Set.of(Value.FORTY_TWO, Value.THIRTEEN), + linkedHashSet(Value.THIRTEEN, Value.FORTY_TWO), + treeSet(Value.FORTY_TWO, Value.THIRTEEN), + EnumSet.of(Value.FORTY_TWO, Value.THIRTEEN) ); } From 131cfa9e2406c519bfa0cf34d9a367f0fff277c0 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 30 Sep 2024 16:16:40 +0200 Subject: [PATCH 134/327] Require less inline space --- .../internal/lang/stable/StableValueImpl.java | 34 ++++++++----------- 1 file changed, 15 insertions(+), 19 deletions(-) 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 cda0ef7de478b..1b5fdd25d867f 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 @@ -26,6 +26,7 @@ package jdk.internal.lang.stable; import jdk.internal.misc.Unsafe; +import jdk.internal.vm.annotation.DontInline; import jdk.internal.vm.annotation.ForceInline; import jdk.internal.vm.annotation.Stable; @@ -83,20 +84,17 @@ public boolean trySet(T newValue) { @Override public T orElseThrow() { final Object t = wrappedValue; - if (t != null) { - return unwrap(t); + if (t == null) { + throw new NoSuchElementException("No data set"); } - throw new NoSuchElementException("No holder value set"); + return unwrap(t); } @ForceInline @Override public T orElse(T other) { final Object t = wrappedValue; - if (t != null) { - return unwrap(t); - } - return other; + return (t == null) ? other : unwrap(t); } @ForceInline @@ -105,24 +103,22 @@ public boolean isSet() { return wrappedValue != null; } - - @SuppressWarnings("unchecked") @ForceInline @Override public T computeIfUnset(Supplier supplier) { - Object t = wrappedValue; + final Object t = wrappedValue; + return (t == null) ? computeIfUnsetSlowPath(supplier) : unwrap(t); + } + + @DontInline + private synchronized T computeIfUnsetSlowPath(Supplier supplier) { + final Object t = wrappedValue; if (t != null) { return unwrap(t); } - synchronized (this) { - t = wrappedValue; - if (t != null) { - return unwrap(t); - } - final T newValue = supplier.get(); - // The mutex is reentrant so we need to check if the value was actually set. - return wrapAndCas(newValue) ? newValue : orElseThrow(); - } + final T newValue = supplier.get(); + // The mutex is reentrant so we need to check if the value was actually set. + return wrapAndCas(newValue) ? newValue : orElseThrow(); } // The methods equals() and hashCode() should be based on identity (defaults from Object) From 1932d07cdcaaba9eff6a86a1a6a7a7bcc9516faf Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 30 Sep 2024 17:01:17 +0200 Subject: [PATCH 135/327] Rename methods and classes --- .../share/classes/java/lang/StableValue.java | 131 +++++++++--------- .../java/util/ImmutableCollections.java | 32 ++--- .../access/JavaUtilCollectionAccess.java | 4 +- .../lang/stable/StableValueFactories.java | 13 +- .../internal/lang/stable/StableValueImpl.java | 10 +- .../lang/StableValue/CachingFunctionTest.java | 22 +-- .../StableValue/CachingIntFunctionTest.java | 20 ++- .../lang/StableValue/CachingSupplierTest.java | 17 +-- test/jdk/java/lang/StableValue/JepTest.java | 30 ++-- ...{LazyListTest.java => StableListTest.java} | 24 ++-- .../{LazyMapTest.java => StableMapTest.java} | 20 +-- .../lang/StableValue/StableValueTest.java | 34 +++-- .../StableValuesSafePublicationTest.java | 2 +- .../StableValue/TrustedFieldTypeTest.java | 16 +-- .../lang/stable/CachingFunctionBenchmark.java | 8 +- .../stable/CachingIntFunctionBenchmark.java | 8 +- .../lang/stable/CachingSupplierBenchmark.java | 16 +-- .../CustomCachingBiFunctionBenchmark.java | 20 +-- .../lang/stable/CustomCachingFunctions.java | 8 +- .../CustomCachingPredicateBenchmark.java | 2 +- .../lang/stable/StableValueBenchmark.java | 20 +-- 21 files changed, 223 insertions(+), 234 deletions(-) rename test/jdk/java/lang/StableValue/{LazyListTest.java => StableListTest.java} (94%) rename test/jdk/java/lang/StableValue/{LazyMapTest.java => StableMapTest.java} (94%) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 44f71cadcc258..cd0dc59056a2c 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -41,12 +41,12 @@ import java.util.function.Supplier; /** - * A thin, atomic, thread-safe, set-at-most-once, stable value holder eligible for - * certain JVM optimizations if set to a value. + * A thin, atomic, thread-safe, set-at-most-once, stable holder capable of holding + * underlying data, eligible for certain JVM optimizations if set to a value. *

    * A stable value is said to be monotonic because the state of a stable value can only go - * from unset to set and consequently, a value can only be set - * at most once. + * from unset to set and consequently, the underlying data can only be + * set at most once. *

    * StableValue is mainly intended to be a member of a holding class and is usually neither * exposed directly via accessors nor passed as a method parameter. @@ -55,55 +55,55 @@ *

    Factories

    *

    * To create a new fresh (unset) StableValue, use the - * {@linkplain StableValue#newInstance() StableValue::newInstance} factory. + * {@linkplain StableValue#of() StableValue::of} factory. *

    * This class also contains a number of convenience methods for creating constructs * involving stable values: *

      *
    • - * A caching (also called "memoized") Supplier, where a given {@code original} + * A stable (also called "cached" or "memoized") Supplier, where a given {@code original} * Supplier is guaranteed to be successfully invoked at most once even in a multithreaded * environment, can be created like this: - * {@snippet lang = java : - * Supplier cache = StableValue.newCachingSupplier(original); - * } + * {@snippet lang = java: + * Supplier stable = StableValue.ofSupplier(original); + *} *
    • * *
    • - * A caching (also called "memoized") IntFunction, for the allowed given {@code size} - * input values {@code [0, size)} and where the given {@code original} IntFunction is - * guaranteed to be successfully invoked at most once per inout index even in a - * multithreaded environment, can be created like this: + * A stable (also called "cached" or "memoized") IntFunction, for the allowed given + * {@code size} input values {@code [0, size)} and where the given {@code original} + * IntFunction is guaranteed to be successfully invoked at most once per inout index even + * in a multithreaded environment, can be created like this: * {@snippet lang = java: - * IntFunction cache = StableValue.newCachingIntFunction(size, original); + * IntFunction stable = StableValue.ofIntFunction(size, original); *} *
    • * *
    • - * A caching (also called "memoized") Function, for the given set of allowed {@code inputs} - * and where the given {@code original} function is guaranteed to be successfully invoked - * at most once per input value even in a multithreaded environment, can be created like - * this: + * A stable (also called "cached" or "memoized") Function, for the given set of allowed + * {@code inputs} and where the given {@code original} function is guaranteed to be + * successfully invoked at most once per input value even in a multithreaded environment, + * can be created like this: * {@snippet lang = java : - * Function cache = StableValue.newCachingFunction(inputs, original); + * Function stable = StableValue.stableFunction(inputs, original); * } *
    • * *
    • - * A lazy List of stable elements with a given {@code size} and given {@code mapper} can + * A stable List of stable elements with a given {@code size} and given {@code mapper} can * be created the following way: - * {@snippet lang = java : - * List lazyList = StableValue.lazyList(size, mapper); - * } + * {@snippet lang = java: + * List stableList = StableValue.ofList(size, mapper); + *} * The list can be used to model stable one-dimensional arrays. If two- or more * dimensional arrays are to be modeled, a List of List of ... of E can be used. *
    • * *
    • - * A lazy Map with a given set of {@code keys} and given {@code mapper} associated with + * A stable Map with a given set of {@code keys} and given {@code mapper} associated with * stable values can be created like this: * {@snippet lang = java : - * Map lazyMap = StableValue.lazyMap(keys, mapper); + * Map stableMap = StableValue.stableMap(keys, mapper); * } *
    • * @@ -113,20 +113,21 @@ * instances. * *

      Memory Consistency Properties

      - * Actions on a presumptive holder value in a thread prior to calling a method that sets - * the holder value are seen by any other thread that observes a set holder value. + * Actions on a presumptive underlying data in a thread prior to calling a method that + * sets the underlying data are seen by any other thread that observes a + * set underlying data. * * More generally, the action of attempting to interact (i.e. via load or store operations) * with a StableValue's holder value (e.g. via {@link StableValue#trySet} or * {@link StableValue#orElseThrow()}) forms a * happens-before - * relation between any other attempt to interact with the StableValue's holder value. + * relation between any other attempt to interact with the StableValue's underlying data. * *

      Nullability

      - * Except for a StableValue's holder value itself, all method parameters must be + * Except for a StableValue's underlying data itself, all method parameters must be * non-null or a {@link NullPointerException} will be thrown. * - * @implNote Implementing classes are free to synchronize on {@code this} and consequently, + * @implSpec Implementing classes are free to synchronize on {@code this} and consequently, * care should be taken whenever (directly or indirectly) synchronizing on * a StableValue. Failure to do this may lead to deadlock. * @@ -148,57 +149,57 @@ public sealed interface StableValue // Principal methods /** - * {@return {@code true} if the holder value was set to the provided {@code value}, + * {@return {@code true} if the underlying data was set to the provided {@code value}, * otherwise returns {@code false}} *

      - * When this method returns, a holder value is always set. + * When this method returns, the underlying data is always set. * * @param value to set (nullable) */ boolean trySet(T value); /** - * {@return the set holder value (nullable) if set, otherwise return the + * {@return the set underlying data (nullable) if set, otherwise return the * {@code other} value} * - * @param other to return if the stable holder value is not set + * @param other to return if the underlying data is not set */ T orElse(T other); /** - * {@return the set holder value if set, otherwise throws {@code NoSuchElementException}} + * {@return the underlying data if set, otherwise throws {@code NoSuchElementException}} * - * @throws NoSuchElementException if no value is set + * @throws NoSuchElementException if no underlying data is set */ T orElseThrow(); /** - * {@return {@code true} if a holder value is set, {@code false} otherwise} + * {@return {@code true} if the underlying data is set, {@code false} otherwise} */ boolean isSet(); // Convenience methods /** - * Sets the holder value to the provided {@code value}, or, if already set, + * Sets the underlying data to the provided {@code value}, or, if already set, * throws {@link IllegalStateException}} *

      - * When this method returns (or throws an Exception), a holder value is always set. + * When this method returns (or throws an Exception), the underlying data is always set. * * @param value to set (nullable) * @throws IllegalStateException if a holder value is already set */ default void setOrThrow(T value) { if (!trySet(value)) { - throw new IllegalStateException("Cannot set the holder value to " + value + - " because a holder value is alredy set: " + this); + throw new IllegalStateException("Cannot set the underlying data to " + value + + " because the underlying data is alredy set: " + this); } } /** - * {@return the set holder value if set, otherwise attempts to compute and set a - * new (nullable) value using the provided {@code supplier}, returning the - * (pre-existing or newly set) value} + * {@return the underlying data if set, otherwise attempts to compute and set a + * new (nullable) underlying data using the provided {@code supplier}, returning the + * (pre-existing or newly set) underlying data} *

      * The provided {@code supplier} is guaranteed to be invoked at most once if it * completes without throwing an exception. @@ -228,7 +229,7 @@ default void setOrThrow(T value) { * will only be invoked once even if invoked from several threads unless the * {@code supplier} throws an exception. * - * @param supplier to be used for computing a value + * @param supplier to be used for computing the underlying data * @throws StackOverflowError if the provided {@code supplier} recursively * invokes the provided {@code supplier} upon being invoked. */ @@ -237,16 +238,16 @@ default void setOrThrow(T value) { // Factories /** - * {@return a fresh stable value with an unset holder value} + * {@return a fresh stable value with no underlying data set} * * @param type of the holder value */ - static StableValue newInstance() { - return StableValueFactories.newInstance(); + static StableValue of() { + return StableValueFactories.of(); } /** - * {@return a new caching, thread-safe, stable, lazily computed + * {@return a new stable, thread-safe, caching, lazily computed * {@linkplain Supplier supplier} that records the value of the provided * {@code original} supplier upon being first accessed via * {@linkplain Supplier#get() Supplier::get}} @@ -267,13 +268,13 @@ static StableValue newInstance() { * @param original supplier used to compute a memoized value * @param the type of results supplied by the returned supplier */ - static Supplier newCachingSupplier(Supplier original) { + static Supplier ofSupplier(Supplier original) { Objects.requireNonNull(original); - return StableValueFactories.newCachingSupplier(original); + return StableValueFactories.ofSupplier(original); } /** - * {@return a new caching, thread-safe, stable, lazily computed + * {@return a new stable, thread-safe, caching, lazily computed * {@link IntFunction } that, for each allowed input, records the values of the * provided {@code original} IntFunction upon being first accessed via * {@linkplain IntFunction#apply(int) IntFunction::apply}} @@ -296,17 +297,17 @@ static Supplier newCachingSupplier(Supplier original) { * @param original IntFunction used to compute a memoized value * @param the type of results delivered by the returned IntFunction */ - static IntFunction newCachingIntFunction(int size, - IntFunction original) { + static IntFunction ofIntFunction(int size, + IntFunction original) { if (size < 0) { throw new IllegalArgumentException(); } Objects.requireNonNull(original); - return StableValueFactories.newCachingIntFunction(size, original); + return StableValueFactories.ofIntFunction(size, original); } /** - * {@return a new caching, thread-safe, stable, lazily computed {@link Function} + * {@return a new stable, thread-safe, caching, lazily computed {@link Function} * that, for each allowed input in the given set of {@code inputs}, records the * values of the provided {@code original} Function upon being first accessed via * {@linkplain Function#apply(Object) Function::apply}, or optionally via background @@ -335,15 +336,15 @@ static IntFunction newCachingIntFunction(int size, * @param the type of the input to the returned Function * @param the type of results delivered by the returned Function */ - static Function newCachingFunction(Set inputs, - Function original) { + static Function ofFunction(Set inputs, + Function original) { Objects.requireNonNull(inputs); Objects.requireNonNull(original); - return StableValueFactories.newCachingFunction(inputs, original); + return StableValueFactories.ofFunction(inputs, original); } /** - * {@return a lazy, shallowly immutable, stable List of the provided {@code size} + * {@return a shallowly immutable, lazy, stable List of the provided {@code size} * where the individual elements of the list are lazily computed via the provided * {@code mapper} whenever an element is first accessed (directly or indirectly), * for example via {@linkplain List#get(int) List::get}} @@ -366,16 +367,16 @@ static Function newCachingFunction(Set inputs, * @param mapper to invoke whenever an element is first accessed (may return null) * @param the {@code StableValue}s' element type */ - static List lazyList(int size, IntFunction mapper) { + static List ofList(int size, IntFunction mapper) { if (size < 0) { throw new IllegalArgumentException(); } Objects.requireNonNull(mapper); - return SharedSecrets.getJavaUtilCollectionAccess().lazyList(size, mapper); + return SharedSecrets.getJavaUtilCollectionAccess().stableList(size, mapper); } /** - * {@return a lazy, shallowly immutable, stable Map of the provided {@code keys} + * {@return a shallowly immutable, lazy, stable Map of the provided {@code keys} * where the associated values of the maps are lazily computed vio the provided * {@code mapper} whenever a value is first accessed (directly or indirectly), for * example via {@linkplain Map#get(Object) Map::get}} @@ -400,10 +401,10 @@ static List lazyList(int size, IntFunction mapper) { * @param the type of keys maintained by the returned map * @param the type of mapped values */ - static Map lazyMap(Set keys, Function mapper) { + static Map ofMap(Set keys, Function mapper) { Objects.requireNonNull(keys); Objects.requireNonNull(mapper); - return SharedSecrets.getJavaUtilCollectionAccess().lazyMap(keys, mapper); + return SharedSecrets.getJavaUtilCollectionAccess().stableMap(keys, mapper); } } diff --git a/src/java.base/share/classes/java/util/ImmutableCollections.java b/src/java.base/share/classes/java/util/ImmutableCollections.java index c1c1738920acd..ba91176d2362a 100644 --- a/src/java.base/share/classes/java/util/ImmutableCollections.java +++ b/src/java.base/share/classes/java/util/ImmutableCollections.java @@ -134,11 +134,11 @@ public List listFromTrustedArray(Object[] array) { public List listFromTrustedArrayNullsAllowed(Object[] array) { return ImmutableCollections.listFromTrustedArrayNullsAllowed(array); } - public List lazyList(int size, IntFunction mapper) { - return ImmutableCollections.lazyList(size, mapper); + public List stableList(int size, IntFunction mapper) { + return ImmutableCollections.stableList(size, mapper); } - public Map lazyMap(Set keys, Function mapper) { - return new LazyMap<>(keys, mapper); + public Map stableMap(Set keys, Function mapper) { + return new StableMap<>(keys, mapper); } }); } @@ -262,9 +262,9 @@ static List listFromTrustedArrayNullsAllowed(Object... input) { } } - static List lazyList(int size, IntFunction mapper) { + static List stableList(int size, IntFunction mapper) { // A lazy list is not Serializable so, we cannot return `List.of()` if size == 0 - return new LazyList<>(size, mapper); + return new StableList<>(size, mapper); } // ---------- List Implementations ---------- @@ -465,7 +465,7 @@ static final class SubList extends AbstractImmutableList private final int size; private SubList(AbstractImmutableList root, int offset, int size) { - assert root instanceof List12 || root instanceof ListN || root instanceof LazyList; + assert root instanceof List12 || root instanceof ListN || root instanceof StableList; this.root = root; this.offset = offset; this.size = size; @@ -517,7 +517,7 @@ private void rangeCheck(int index) { private boolean allowNulls() { return root instanceof ListN listN && listN.allowNulls - || root instanceof LazyList; + || root instanceof StableList; } @Override @@ -769,14 +769,14 @@ public int lastIndexOf(Object o) { } @jdk.internal.ValueBased - static final class LazyList extends AbstractImmutableList { + static final class StableList extends AbstractImmutableList { @Stable private final IntFunction mapper; @Stable private final StableValueImpl[] backing; - LazyList(int size, IntFunction mapper) { + StableList(int size, IntFunction mapper) { this.mapper = mapper; this.backing = StableValueFactories.ofArray(size); } @@ -1458,7 +1458,7 @@ private Object writeReplace() { } } - static final class LazyMap + static final class StableMap extends AbstractImmutableMap { @Stable @@ -1466,14 +1466,14 @@ static final class LazyMap @Stable private final Map> delegate; - LazyMap(Set keys, Function mapper) { + StableMap(Set keys, Function mapper) { this.mapper = mapper; this.delegate = StableValueFactories.ofMap(keys); } @Override public boolean containsKey(Object o) { return delegate.containsKey(o); } @Override public int size() { return delegate.size(); } - @Override public Set> entrySet() { return new LazyMapEntrySet(); } + @Override public Set> entrySet() { return new StableMapEntrySet(); } @ForceInline @Override @@ -1489,18 +1489,18 @@ public V get(Object key) { } @jdk.internal.ValueBased - final class LazyMapEntrySet extends AbstractImmutableSet> { + final class StableMapEntrySet extends AbstractImmutableSet> { @Stable private final Set>> delegateEntrySet; - LazyMapEntrySet() { + StableMapEntrySet() { this.delegateEntrySet = delegate.entrySet(); } @Override public Iterator> iterator() { return new LazyMapIterator(); } @Override public int size() { return delegateEntrySet.size(); } - @Override public int hashCode() { return LazyMap.this.hashCode(); } + @Override public int hashCode() { return StableMap.this.hashCode(); } @jdk.internal.ValueBased final class LazyMapIterator implements Iterator> { diff --git a/src/java.base/share/classes/jdk/internal/access/JavaUtilCollectionAccess.java b/src/java.base/share/classes/jdk/internal/access/JavaUtilCollectionAccess.java index 12bca3f0571b1..ca0cc2798f4a0 100644 --- a/src/java.base/share/classes/jdk/internal/access/JavaUtilCollectionAccess.java +++ b/src/java.base/share/classes/jdk/internal/access/JavaUtilCollectionAccess.java @@ -34,6 +34,6 @@ public interface JavaUtilCollectionAccess { List listFromTrustedArray(Object[] array); List listFromTrustedArrayNullsAllowed(Object[] array); - List lazyList(int size, IntFunction mapper); - Map lazyMap(Set keys, Function mapper); + List stableList(int size, IntFunction mapper); + Map stableMap(Set keys, Function mapper); } diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableValueFactories.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableValueFactories.java index 702db7172ec8b..1178f24ee540a 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableValueFactories.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableValueFactories.java @@ -4,7 +4,6 @@ import java.util.Map; import java.util.Objects; import java.util.Set; -import java.util.concurrent.ThreadFactory; import java.util.function.Function; import java.util.function.IntFunction; import java.util.function.Supplier; @@ -15,21 +14,21 @@ private StableValueFactories() {} // Factories - public static StableValueImpl newInstance() { + public static StableValueImpl of() { return StableValueImpl.newInstance(); } - public static Supplier newCachingSupplier(Supplier original) { + public static Supplier ofSupplier(Supplier original) { return CachingSupplier.of(original); } - public static IntFunction newCachingIntFunction(int size, - IntFunction original) { + public static IntFunction ofIntFunction(int size, + IntFunction original) { return CachingIntFunction.of(size, original); } - public static Function newCachingFunction(Set inputs, - Function original) { + public static Function ofFunction(Set inputs, + Function original) { if (inputs.isEmpty()) { return EmptyCachingFunction.of(original); } 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 1b5fdd25d867f..0c29fea45106a 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 @@ -113,12 +113,12 @@ public T computeIfUnset(Supplier supplier) { @DontInline private synchronized T computeIfUnsetSlowPath(Supplier supplier) { final Object t = wrappedValue; - if (t != null) { - return unwrap(t); + if (t == null) { + final T newValue = supplier.get(); + // The mutex is reentrant so we need to check if the value was actually set. + return wrapAndCas(newValue) ? newValue : orElseThrow(); } - final T newValue = supplier.get(); - // The mutex is reentrant so we need to check if the value was actually set. - return wrapAndCas(newValue) ? newValue : orElseThrow(); + return unwrap(t); } // The methods equals() and hashCode() should be based on identity (defaults from Object) diff --git a/test/jdk/java/lang/StableValue/CachingFunctionTest.java b/test/jdk/java/lang/StableValue/CachingFunctionTest.java index 76eced902b5ea..f65edb6f7d23e 100644 --- a/test/jdk/java/lang/StableValue/CachingFunctionTest.java +++ b/test/jdk/java/lang/StableValue/CachingFunctionTest.java @@ -72,8 +72,8 @@ int asInt() { @ParameterizedTest @MethodSource("allSets") void factoryInvariants(Set inputs) { - assertThrows(NullPointerException.class, () -> StableValue.newCachingFunction(null, MAPPER)); - assertThrows(NullPointerException.class, () -> StableValue.newCachingFunction(inputs, null)); + assertThrows(NullPointerException.class, () -> StableValue.ofFunction(null, MAPPER)); + assertThrows(NullPointerException.class, () -> StableValue.ofFunction(inputs, null)); } @ParameterizedTest @@ -85,7 +85,7 @@ void basic(Set inputs) { void basic(Set inputs, Function mapper) { StableTestUtil.CountingFunction cif = new StableTestUtil.CountingFunction<>(mapper); - var cached = StableValue.newCachingFunction(inputs, cif); + var cached = StableValue.ofFunction(inputs, cif); assertEquals(mapper.apply(Value.FORTY_TWO), cached.apply(Value.FORTY_TWO)); assertEquals(1, cif.cnt()); assertEquals(mapper.apply(Value.FORTY_TWO), cached.apply(Value.FORTY_TWO)); @@ -104,7 +104,7 @@ void basic(Set inputs, Function mapper) { @ParameterizedTest @MethodSource("emptySets") void empty(Set inputs) { - Function f0 = StableValue.newCachingFunction(inputs, Value::asInt); + Function f0 = StableValue.ofFunction(inputs, Value::asInt); assertTrue(f0.toString().contains("{}")); } @@ -114,7 +114,7 @@ void exception(Set inputs) { StableTestUtil.CountingFunction cif = new StableTestUtil.CountingFunction<>(_ -> { throw new UnsupportedOperationException(); }); - var cached = StableValue.newCachingFunction(inputs, cif); + var cached = StableValue.ofFunction(inputs, cif); assertThrows(UnsupportedOperationException.class, () -> cached.apply(Value.FORTY_TWO)); assertEquals(1, cif.cnt()); assertThrows(UnsupportedOperationException.class, () -> cached.apply(Value.FORTY_TWO)); @@ -130,7 +130,7 @@ void exception(Set inputs) { @MethodSource("nonEmptySets") void circular(Set inputs) { final AtomicReference> ref = new AtomicReference<>(); - Function> cached = StableValue.newCachingFunction(inputs, _ -> ref.get()); + Function> cached = StableValue.ofFunction(inputs, _ -> ref.get()); ref.set(cached); cached.apply(Value.FORTY_TWO); String toString = cached.toString(); @@ -143,8 +143,8 @@ void circular(Set inputs) { @MethodSource("allSets") void equality(Set inputs) { Function mapper = Value::asInt; - Function f0 = StableValue.newCachingFunction(inputs, mapper); - Function f1 = StableValue.newCachingFunction(inputs, mapper); + Function f0 = StableValue.ofFunction(inputs, mapper); + Function f1 = StableValue.ofFunction(inputs, mapper); // No function is equal to another function assertNotEquals(f0, f1); } @@ -152,7 +152,7 @@ void equality(Set inputs) { @ParameterizedTest @MethodSource("allSets") void hashCodeStable(Set inputs) { - Function f0 = StableValue.newCachingFunction(inputs, Value::asInt); + Function f0 = StableValue.ofFunction(inputs, Value::asInt); assertEquals(System.identityHashCode(f0), f0.hashCode()); if (!inputs.isEmpty()) { f0.apply(Value.FORTY_TWO); @@ -162,9 +162,9 @@ void hashCodeStable(Set inputs) { @Test void usesOptimizedVersion() { - Function enumFunction = StableValue.newCachingFunction(EnumSet.of(Value.FORTY_TWO), Value::asInt); + Function enumFunction = StableValue.ofFunction(EnumSet.of(Value.FORTY_TWO), Value::asInt); assertEquals("jdk.internal.lang.stable.CachingEnumFunction", enumFunction.getClass().getName()); - Function emptyFunction = StableValue.newCachingFunction(Set.of(), Value::asInt); + Function emptyFunction = StableValue.ofFunction(Set.of(), Value::asInt); assertEquals("jdk.internal.lang.stable.EmptyCachingFunction", emptyFunction.getClass().getName()); } diff --git a/test/jdk/java/lang/StableValue/CachingIntFunctionTest.java b/test/jdk/java/lang/StableValue/CachingIntFunctionTest.java index 4351cf26f54e3..a1c3d5a92da7b 100644 --- a/test/jdk/java/lang/StableValue/CachingIntFunctionTest.java +++ b/test/jdk/java/lang/StableValue/CachingIntFunctionTest.java @@ -29,11 +29,7 @@ import org.junit.jupiter.api.Test; -import java.util.Set; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Function; import java.util.function.IntFunction; import static org.junit.jupiter.api.Assertions.*; @@ -45,8 +41,8 @@ final class CachingIntFunctionTest { @Test void factoryInvariants() { - assertThrows(IllegalArgumentException.class, () -> StableValue.newCachingIntFunction(-1, MAPPER)); - assertThrows(NullPointerException.class, () -> StableValue.newCachingIntFunction(SIZE, null)); + assertThrows(IllegalArgumentException.class, () -> StableValue.ofIntFunction(-1, MAPPER)); + assertThrows(NullPointerException.class, () -> StableValue.ofIntFunction(SIZE, null)); } @Test @@ -57,7 +53,7 @@ void basic() { void basic(IntFunction mapper) { StableTestUtil.CountingIntFunction cif = new StableTestUtil.CountingIntFunction<>(mapper); - var cached = StableValue.newCachingIntFunction(SIZE, cif); + var cached = StableValue.ofIntFunction(SIZE, cif); assertEquals("CachingIntFunction[values=[.unset, .unset], original=" + cif + "]", cached.toString()); assertEquals(mapper.apply(1), cached.apply(1)); assertEquals(1, cif.cnt()); @@ -74,7 +70,7 @@ void exception() { StableTestUtil.CountingIntFunction cif = new StableTestUtil.CountingIntFunction<>(_ -> { throw new UnsupportedOperationException(); }); - var cached = StableValue.newCachingIntFunction(SIZE, cif); + var cached = StableValue.ofIntFunction(SIZE, cif); assertThrows(UnsupportedOperationException.class, () -> cached.apply(1)); assertEquals(1, cif.cnt()); assertThrows(UnsupportedOperationException.class, () -> cached.apply(1)); @@ -85,7 +81,7 @@ void exception() { @Test void circular() { final AtomicReference> ref = new AtomicReference<>(); - IntFunction> cached = StableValue.newCachingIntFunction(SIZE, _ -> ref.get()); + IntFunction> cached = StableValue.ofIntFunction(SIZE, _ -> ref.get()); ref.set(cached); cached.apply(0); String toString = cached.toString(); @@ -96,15 +92,15 @@ void circular() { @Test void equality() { - IntFunction f0 = StableValue.newCachingIntFunction(8, MAPPER); - IntFunction f1 = StableValue.newCachingIntFunction(8, MAPPER); + IntFunction f0 = StableValue.ofIntFunction(8, MAPPER); + IntFunction f1 = StableValue.ofIntFunction(8, MAPPER); // No function is equal to another function assertNotEquals(f0, f1); } @Test void hashCodeStable() { - IntFunction f0 = StableValue.newCachingIntFunction(8, MAPPER); + IntFunction f0 = StableValue.ofIntFunction(8, MAPPER); assertEquals(System.identityHashCode(f0), f0.hashCode()); f0.apply(4); assertEquals(System.identityHashCode(f0), f0.hashCode()); diff --git a/test/jdk/java/lang/StableValue/CachingSupplierTest.java b/test/jdk/java/lang/StableValue/CachingSupplierTest.java index 8a559c39a3c07..9eb7b2018d768 100644 --- a/test/jdk/java/lang/StableValue/CachingSupplierTest.java +++ b/test/jdk/java/lang/StableValue/CachingSupplierTest.java @@ -29,10 +29,7 @@ import org.junit.jupiter.api.Test; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; -import java.util.function.IntFunction; import java.util.function.Supplier; import static org.junit.jupiter.api.Assertions.*; @@ -43,7 +40,7 @@ final class CachingSupplierTest { @Test void factoryInvariants() { - assertThrows(NullPointerException.class, () -> StableValue.newCachingSupplier(null)); + assertThrows(NullPointerException.class, () -> StableValue.ofSupplier(null)); } @Test @@ -54,7 +51,7 @@ void basic() { void basic(Supplier supplier) { StableTestUtil.CountingSupplier cs = new StableTestUtil.CountingSupplier<>(supplier); - var cached = StableValue.newCachingSupplier(cs); + var cached = StableValue.ofSupplier(cs); assertEquals("CachingSupplier[value=.unset, original=" + cs + "]", cached.toString()); assertEquals(supplier.get(), cached.get()); assertEquals(1, cs.cnt()); @@ -68,7 +65,7 @@ void exception() { StableTestUtil.CountingSupplier cs = new StableTestUtil.CountingSupplier<>(() -> { throw new UnsupportedOperationException(); }); - var cached = StableValue.newCachingSupplier(cs); + var cached = StableValue.ofSupplier(cs); assertThrows(UnsupportedOperationException.class, cached::get); assertEquals(1, cs.cnt()); assertThrows(UnsupportedOperationException.class, cached::get); @@ -79,7 +76,7 @@ void exception() { @Test void circular() { final AtomicReference> ref = new AtomicReference<>(); - Supplier> cached = StableValue.newCachingSupplier(ref::get); + Supplier> cached = StableValue.ofSupplier(ref::get); ref.set(cached); cached.get(); String toString = cached.toString(); @@ -89,15 +86,15 @@ void circular() { @Test void equality() { - Supplier f0 = StableValue.newCachingSupplier(SUPPLIER); - Supplier f1 = StableValue.newCachingSupplier(SUPPLIER); + Supplier f0 = StableValue.ofSupplier(SUPPLIER); + Supplier f1 = StableValue.ofSupplier(SUPPLIER); // No function is equal to another function assertNotEquals(f0, f1); } @Test void hashCodeStable() { - Supplier f0 = StableValue.newCachingSupplier(SUPPLIER); + Supplier f0 = StableValue.ofSupplier(SUPPLIER); assertEquals(System.identityHashCode(f0), f0.hashCode()); f0.get(); assertEquals(System.identityHashCode(f0), f0.hashCode()); diff --git a/test/jdk/java/lang/StableValue/JepTest.java b/test/jdk/java/lang/StableValue/JepTest.java index 40259aeb98727..f1540688e5789 100644 --- a/test/jdk/java/lang/StableValue/JepTest.java +++ b/test/jdk/java/lang/StableValue/JepTest.java @@ -34,7 +34,6 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.AbstractList; -import java.util.EnumSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -46,7 +45,6 @@ import java.util.function.Supplier; import java.util.logging.Logger; import java.util.stream.Collectors; -import java.util.stream.IntStream; import java.util.stream.Stream; final class JepTest { @@ -134,7 +132,7 @@ public Logger logger(int i) { class Foo { // 1. Declare a Stable field - private static final StableValue LOGGER = StableValue.newInstance(); + private static final StableValue LOGGER = StableValue.of(); static Logger logger() { @@ -152,7 +150,7 @@ class Foo2 { // 1. Centrally declare a caching supplier and define how it should be computed private static final Supplier LOGGER = - StableValue.newCachingSupplier( () -> Logger.getLogger("com.company.Foo") ); + StableValue.ofSupplier( () -> Logger.getLogger("com.company.Foo") ); static Logger logger() { @@ -167,7 +165,7 @@ class MapDemo { // 1. Declare a lazy stable map of loggers with two allowable keys: // "com.company.Bar" and "com.company.Baz" static final Map LOGGERS = - StableValue.lazyMap(Set.of("com.company.Foo", "com.company.Bar"), Logger::getLogger); + StableValue.ofMap(Set.of("com.company.Foo", "com.company.Bar"), Logger::getLogger); // 2. Access the lazy map with as-declared-final performance // (evaluation made before the first access) @@ -180,7 +178,7 @@ static Logger logger(String name) { class CachedNum { // 1. Centrally declare a cached IntFunction backed by a list of StableValue elements private static final IntFunction LOGGERS = - StableValue.newCachingIntFunction(2, CachedNum::fromNumber); + StableValue.ofIntFunction(2, CachedNum::fromNumber); // 2. Define a function that is to be called the first // time a particular message number is referenced @@ -202,7 +200,7 @@ class Cached { // 1. Centrally declare a cached function backed by a map of stable values private static final Function LOGGERS = - StableValue.newCachingFunction(Set.of("com.company.Foo", "com.company.Bar"), + StableValue.ofFunction(Set.of("com.company.Foo", "com.company.Bar"), Logger::getLogger); private static final String NAME = "com.company.Foo"; @@ -220,7 +218,7 @@ class ErrorMessages { // 1. Centrally declare a cached IntFunction backed by a list of StableValue elements private static final IntFunction ERROR_FUNCTION = - StableValue.newCachingIntFunction(SIZE, ErrorMessages::readFromFile); + StableValue.ofIntFunction(SIZE, ErrorMessages::readFromFile); // 2. Define a function that is to be called the first // time a particular message number is referenced @@ -243,7 +241,7 @@ record CachingPredicate(Map> delegate, public CachingPredicate(Set inputs, Predicate original) { this(inputs.stream() - .collect(Collectors.toMap(Function.identity(), _ -> StableValue.newInstance())), + .collect(Collectors.toMap(Function.identity(), _ -> StableValue.of())), original::test ); } @@ -262,7 +260,7 @@ public boolean test(T t) { record CachingPredicate2(Map delegate) implements Predicate { public CachingPredicate2(Set inputs, Predicate original) { - this(StableValue.lazyMap(inputs, original::test)); + this(StableValue.ofMap(inputs, original::test)); } @Override @@ -273,7 +271,7 @@ public boolean test(T t) { public static void main(String[] args) { Predicate even = i -> i % 2 == 0; - Predicate cachingPredicate = StableValue.lazyMap(Set.of(1, 2), even::test)::get; + Predicate cachingPredicate = StableValue.ofMap(Set.of(1, 2), even::test)::get; } record Pair(L left, R right){} @@ -294,7 +292,7 @@ public R apply(T t, U u) { static CachingBiFunction of(Set> inputs, BiFunction original) { Map, StableValue> map = inputs.stream() - .collect(Collectors.toMap(Function.identity(), _ -> StableValue.newInstance())); + .collect(Collectors.toMap(Function.identity(), _ -> StableValue.of())); return new CachingBiFunction<>(map, pair -> original.apply(pair.left(), pair.right())); } @@ -317,7 +315,7 @@ public R apply(T t, U u) { static BiFunction of(Set> inputs, BiFunction original) { Map, StableValue> map = inputs.stream() - .collect(Collectors.toMap(Function.identity(), _ -> StableValue.newInstance())); + .collect(Collectors.toMap(Function.identity(), _ -> StableValue.of())); return new CachingBiFunction2<>(map, original); } @@ -326,7 +324,7 @@ static BiFunction of(Set> inputs, BiFunction LOGGER = StableValue.newInstance(); + private final StableValue LOGGER = StableValue.of(); public Logger getLogger() { return LOGGER.computeIfUnset(() -> Logger.getLogger("com.company.Foo")); @@ -337,7 +335,7 @@ record CachingIntPredicate(List> outputs, IntPredicate resultFunction) implements IntPredicate { CachingIntPredicate(int size, IntPredicate resultFunction) { - this(Stream.generate(StableValue::newInstance).limit(size).toList(), resultFunction); + this(Stream.generate(StableValue::of).limit(size).toList(), resultFunction); } @Override @@ -353,7 +351,7 @@ class FixedStableList extends AbstractList { private final StableValue[] elements; FixedStableList(int size) { - this.elements = Stream.generate(StableValue::newInstance) + this.elements = Stream.generate(StableValue::of) .limit(size) .toArray(StableValue[]::new); } diff --git a/test/jdk/java/lang/StableValue/LazyListTest.java b/test/jdk/java/lang/StableValue/StableListTest.java similarity index 94% rename from test/jdk/java/lang/StableValue/LazyListTest.java rename to test/jdk/java/lang/StableValue/StableListTest.java index 9901faf5a6e59..e84bbbfa082c3 100644 --- a/test/jdk/java/lang/StableValue/LazyListTest.java +++ b/test/jdk/java/lang/StableValue/StableListTest.java @@ -24,8 +24,8 @@ /* @test * @summary Basic tests for LazyList methods * @modules java.base/jdk.internal.lang.stable - * @compile --enable-preview -source ${jdk.version} LazyListTest.java - * @run junit/othervm --enable-preview LazyListTest + * @compile --enable-preview -source ${jdk.version} StableListTest.java + * @run junit/othervm --enable-preview StableListTest */ import jdk.internal.lang.stable.StableValueImpl; @@ -51,7 +51,7 @@ import static org.junit.jupiter.api.Assertions.*; -final class LazyListTest { +final class StableListTest { private static final int ZERO = 0; private static final int INDEX = 7; @@ -60,8 +60,8 @@ final class LazyListTest { @Test void factoryInvariants() { - assertThrows(NullPointerException.class, () -> StableValue.lazyList(SIZE, null)); - assertThrows(IllegalArgumentException.class, () -> StableValue.lazyList(-1, IDENTITY)); + assertThrows(NullPointerException.class, () -> StableValue.ofList(SIZE, null)); + assertThrows(IllegalArgumentException.class, () -> StableValue.ofList(-1, IDENTITY)); } @Test @@ -79,7 +79,7 @@ void size() { @Test void get() { StableTestUtil.CountingIntFunction cif = new StableTestUtil.CountingIntFunction<>(IDENTITY); - var lazy = StableValue.lazyList(SIZE, cif); + var lazy = StableValue.ofList(SIZE, cif); for (int i = 0; i < SIZE; i++) { assertEquals(i, lazy.get(i)); assertEquals(i + 1, cif.cnt()); @@ -93,7 +93,7 @@ void getException() { StableTestUtil.CountingIntFunction cif = new StableTestUtil.CountingIntFunction<>(_ -> { throw new UnsupportedOperationException(); }); - var lazy = StableValue.lazyList(SIZE, cif); + var lazy = StableValue.ofList(SIZE, cif); assertThrows(UnsupportedOperationException.class, () -> lazy.get(INDEX)); assertEquals(1, cif.cnt()); assertThrows(UnsupportedOperationException.class, () -> lazy.get(INDEX)); @@ -110,7 +110,7 @@ void toArray() { void toArrayWithArrayLarger() { Integer[] arr = new Integer[SIZE]; arr[INDEX] = 1; - assertSame(arr, StableValue.lazyList(INDEX, IDENTITY).toArray(arr)); + assertSame(arr, StableValue.ofList(INDEX, IDENTITY).toArray(arr)); assertNull(arr[INDEX]); } @@ -151,7 +151,7 @@ void lastIndex() { @Test void toStringTest() { assertEquals("[]", newEmptyList().toString()); - assertEquals("[0, 1]", StableValue.lazyList(2, IDENTITY).toString()); + assertEquals("[0, 1]", StableValue.ofList(2, IDENTITY).toString()); assertEquals(newRegularList().toString(), newList().toString()); } @@ -205,7 +205,7 @@ void iteratorPartial() { @Test void recursiveCall() { AtomicReference> ref = new AtomicReference<>(); - var lazy = StableValue.lazyList(SIZE, i -> ref.get().apply(i)); + var lazy = StableValue.ofList(SIZE, i -> ref.get().apply(i)); ref.set(lazy::get); assertThrows(StackOverflowError.class, () -> lazy.get(INDEX)); } @@ -332,11 +332,11 @@ static Stream unsupportedOperations() { } static List newList() { - return StableValue.lazyList(SIZE, IDENTITY); + return StableValue.ofList(SIZE, IDENTITY); } static List newEmptyList() { - return StableValue.lazyList(ZERO, IDENTITY); + return StableValue.ofList(ZERO, IDENTITY); } static List newRegularList() { diff --git a/test/jdk/java/lang/StableValue/LazyMapTest.java b/test/jdk/java/lang/StableValue/StableMapTest.java similarity index 94% rename from test/jdk/java/lang/StableValue/LazyMapTest.java rename to test/jdk/java/lang/StableValue/StableMapTest.java index 89b919243f145..8d7058a4064f0 100644 --- a/test/jdk/java/lang/StableValue/LazyMapTest.java +++ b/test/jdk/java/lang/StableValue/StableMapTest.java @@ -24,8 +24,8 @@ /* @test * @summary Basic tests for LazyMap methods * @modules java.base/jdk.internal.lang.stable - * @compile --enable-preview -source ${jdk.version} LazyMapTest.java - * @run junit/othervm --enable-preview LazyMapTest + * @compile --enable-preview -source ${jdk.version} StableMapTest.java + * @run junit/othervm --enable-preview StableMapTest */ import jdk.internal.lang.stable.StableValueImpl; @@ -47,7 +47,7 @@ import static org.junit.jupiter.api.Assertions.*; -final class LazyMapTest { +final class StableMapTest { private static final int NOT_PRESENT = 147; private static final int KEY = 7; @@ -57,8 +57,8 @@ final class LazyMapTest { @Test void factoryInvariants() { - assertThrows(NullPointerException.class, () -> StableValue.lazyMap(KEYS, null)); - assertThrows(NullPointerException.class, () -> StableValue.lazyMap(null, IDENTITY)); + assertThrows(NullPointerException.class, () -> StableValue.ofMap(KEYS, null)); + assertThrows(NullPointerException.class, () -> StableValue.ofMap(null, IDENTITY)); } @Test @@ -76,7 +76,7 @@ void size() { @Test void get() { StableTestUtil.CountingFunction cf = new StableTestUtil.CountingFunction<>(IDENTITY); - var lazy = StableValue.lazyMap(KEYS, cf); + var lazy = StableValue.ofMap(KEYS, cf); int cnt = 1; for (int i : KEYS) { assertEquals(i, lazy.get(i)); @@ -92,7 +92,7 @@ void getException() { StableTestUtil.CountingFunction cf = new StableTestUtil.CountingFunction<>(_ -> { throw new UnsupportedOperationException(); }); - var lazy = StableValue.lazyMap(KEYS, cf); + var lazy = StableValue.ofMap(KEYS, cf); assertThrows(UnsupportedOperationException.class, () -> lazy.get(KEY)); assertEquals(1, cf.cnt()); assertThrows(UnsupportedOperationException.class, () -> lazy.get(KEY)); @@ -131,7 +131,7 @@ void forEach() { @Test void toStringTest() { assertEquals("{}", newEmptyMap().toString()); - assertEquals("{" + KEY + "=" + KEY + "}", StableValue.lazyMap(Set.of(KEY), IDENTITY).toString()); + assertEquals("{" + KEY + "=" + KEY + "}", StableValue.ofMap(Set.of(KEY), IDENTITY).toString()); String actual = newMap().toString(); assertTrue(actual.startsWith("{")); for (int key:KEYS) { @@ -252,11 +252,11 @@ static Stream unsupportedOperations() { } static Map newMap() { - return StableValue.lazyMap(KEYS, IDENTITY); + return StableValue.ofMap(KEYS, IDENTITY); } static Map newEmptyMap() { - return StableValue.lazyMap(EMPTY, IDENTITY); + return StableValue.ofMap(EMPTY, IDENTITY); } static Map newRegularMap() { diff --git a/test/jdk/java/lang/StableValue/StableValueTest.java b/test/jdk/java/lang/StableValue/StableValueTest.java index 073245213be2b..69d2ac982514e 100644 --- a/test/jdk/java/lang/StableValue/StableValueTest.java +++ b/test/jdk/java/lang/StableValue/StableValueTest.java @@ -36,8 +36,6 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.LockSupport; import java.util.function.BiPredicate; -import java.util.function.Function; -import java.util.function.Supplier; import java.util.stream.IntStream; import static org.junit.jupiter.api.Assertions.*; @@ -55,7 +53,7 @@ void trySet() { } void trySet(Integer initial) { - StableValue stable = StableValue.newInstance(); + StableValue stable = StableValue.of(); assertTrue(stable.trySet(initial)); assertFalse(stable.trySet(null)); assertFalse(stable.trySet(VALUE)); @@ -65,7 +63,7 @@ void trySet(Integer initial) { @Test void orElse() { - StableValue stable = StableValue.newInstance(); + StableValue stable = StableValue.of(); assertEquals(VALUE, stable.orElse(VALUE)); stable.trySet(VALUE); assertEquals(VALUE, stable.orElse(VALUE2)); @@ -73,9 +71,9 @@ void orElse() { @Test void orElseThrow() { - StableValue stable = StableValue.newInstance(); + StableValue stable = StableValue.of(); var e = assertThrows(NoSuchElementException.class, stable::orElseThrow); - assertEquals("No holder value set", e.getMessage()); + assertEquals("No data set", e.getMessage()); stable.trySet(VALUE); assertEquals(VALUE, stable.orElseThrow()); } @@ -87,7 +85,7 @@ void isSet() { } void isSet(Integer initial) { - StableValue stable = StableValue.newInstance(); + StableValue stable = StableValue.of(); assertFalse(stable.isSet()); stable.trySet(initial); assertTrue(stable.isSet()); @@ -96,7 +94,7 @@ void isSet(Integer initial) { @Test void testComputeIfUnsetSupplier() { StableTestUtil.CountingSupplier cs = new StableTestUtil.CountingSupplier<>(() -> VALUE); - StableValue stable = StableValue.newInstance(); + StableValue stable = StableValue.of(); assertEquals(VALUE, stable.computeIfUnset(cs)); assertEquals(1, cs.cnt()); assertEquals(VALUE, stable.computeIfUnset(cs)); @@ -105,22 +103,22 @@ void testComputeIfUnsetSupplier() { @Test void testHashCode() { - StableValue stableValue = StableValue.newInstance(); + StableValue stableValue = StableValue.of(); // Should be Object::hashCode assertEquals(System.identityHashCode(stableValue), stableValue.hashCode()); } @Test void testEquals() { - StableValue s0 = StableValue.newInstance(); - StableValue s1 = StableValue.newInstance(); + StableValue s0 = StableValue.of(); + StableValue s1 = StableValue.of(); assertNotEquals(s0, s1); // Identity based s0.setOrThrow(42); s1.setOrThrow(42); assertNotEquals(s0, s1); assertNotEquals(s0, "a"); - StableValue null0 = StableValue.newInstance(); - StableValue null1 = StableValue.newInstance(); + StableValue null0 = StableValue.of(); + StableValue null1 = StableValue.of(); null0.setOrThrow(null); null1.setOrThrow(null); assertNotEquals(null0, null1); @@ -128,27 +126,27 @@ void testEquals() { @Test void toStringUnset() { - StableValue stable = StableValue.newInstance(); + StableValue stable = StableValue.of(); assertEquals("StableValue.unset", stable.toString()); } @Test void toStringNull() { - StableValue stable = StableValue.newInstance(); + StableValue stable = StableValue.of(); assertTrue(stable.trySet(null)); assertEquals("StableValue[null]", stable.toString()); } @Test void toStringNonNull() { - StableValue stable = StableValue.newInstance(); + StableValue stable = StableValue.of(); assertTrue(stable.trySet(VALUE)); assertEquals("StableValue[" + VALUE + "]", stable.toString()); } @Test void toStringCircular() { - StableValue> stable = StableValue.newInstance(); + StableValue> stable = StableValue.of(); stable.trySet(stable); String toString = stable.toString(); assertEquals(toString, "(this StableValue)"); @@ -189,7 +187,7 @@ void raceMixed() { void race(BiPredicate, Integer> winnerPredicate) { int noThreads = 10; CountDownLatch starter = new CountDownLatch(1); - StableValue stable = StableValue.newInstance(); + StableValue stable = StableValue.of(); BitSet winner = new BitSet(noThreads); List threads = IntStream.range(0, noThreads).mapToObj(i -> new Thread(() -> { try { diff --git a/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java b/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java index d997a4de286ab..3531793ab9bfa 100644 --- a/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java +++ b/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java @@ -52,7 +52,7 @@ static StableValue[] stables() { @SuppressWarnings("unchecked") StableValue[] stables = (StableValue[]) new StableValue[SIZE]; for (int i = 0; i < SIZE; i++) { - stables[i] = StableValue.newInstance(); + stables[i] = StableValue.of(); } return stables; } diff --git a/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java b/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java index 64d0e327a35d7..f078b6630052e 100644 --- a/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java +++ b/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java @@ -42,10 +42,10 @@ final class TrustedFieldTypeTest { @Test void reflection() throws NoSuchFieldException, IllegalAccessException { final class Holder { - private final StableValue value = StableValue.newInstance(); + private final StableValue value = StableValue.of(); } final class HolderNonFinal { - private StableValue value = StableValue.newInstance(); + private StableValue value = StableValue.of(); } final class ArrayHolder { private final StableValue[] array = (StableValue[]) new StableValue[]{}; @@ -58,7 +58,7 @@ final class ArrayHolder { Object read = valueField.get(holder); // We should NOT be able to write to the StableValue field assertThrows(IllegalAccessException.class, () -> - valueField.set(holder, StableValue.newInstance()) + valueField.set(holder, StableValue.of()) ); Field valueNonFinal = HolderNonFinal.class.getDeclaredField("value"); @@ -66,7 +66,7 @@ final class ArrayHolder { HolderNonFinal holderNonFinal = new HolderNonFinal(); // As the field is not final, both read and write should be ok (not trusted) Object readNonFinal = valueNonFinal.get(holderNonFinal); - valueNonFinal.set(holderNonFinal, StableValue.newInstance()); + valueNonFinal.set(holderNonFinal, StableValue.of()); Field arrayField = ArrayHolder.class.getDeclaredField("array"); arrayField.setAccessible(true); @@ -87,7 +87,7 @@ void sunMiscUnsafe() throws NoSuchFieldException, IllegalAccessException { sun.misc.Unsafe unsafe = (sun.misc.Unsafe)unsafeField.get(null); final class Holder { - private final StableValue value = StableValue.newInstance(); + private final StableValue value = StableValue.of(); } final class ArrayHolder { private final StableValue[] array = (StableValue[]) new StableValue[]{}; @@ -110,7 +110,7 @@ final class ArrayHolder { void varHandle() throws NoSuchFieldException, IllegalAccessException { MethodHandles.Lookup lookup = MethodHandles.lookup(); - StableValue originalValue = StableValue.newInstance(); + StableValue originalValue = StableValue.of(); @SuppressWarnings("unchecked") StableValue[] originalArrayValue = new StableValue[10]; @@ -126,11 +126,11 @@ final class ArrayHolder { Holder holder = new Holder(); assertThrows(UnsupportedOperationException.class, () -> - valueVarHandle.set(holder, StableValue.newInstance()) + valueVarHandle.set(holder, StableValue.of()) ); assertThrows(UnsupportedOperationException.class, () -> - valueVarHandle.compareAndSet(holder, originalValue, StableValue.newInstance()) + valueVarHandle.compareAndSet(holder, originalValue, StableValue.of()) ); VarHandle arrayVarHandle = lookup.findVarHandle(ArrayHolder.class, "array", StableValue[].class); diff --git a/test/micro/org/openjdk/bench/java/lang/stable/CachingFunctionBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/CachingFunctionBenchmark.java index 274e165df4ccb..24c88df3a2a04 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/CachingFunctionBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/CachingFunctionBenchmark.java @@ -60,11 +60,11 @@ public class CachingFunctionBenchmark { private static final int SIZE = 100; private static final Set SET = IntStream.range(0, SIZE).boxed().collect(Collectors.toSet()); - private static final Map STABLE = StableValue.lazyMap(SET, Function.identity()); - private static final Function FUNCTION = StableValue.newCachingFunction(SET, Function.identity()); + private static final Map STABLE = StableValue.ofMap(SET, Function.identity()); + private static final Function FUNCTION = StableValue.ofFunction(SET, Function.identity()); - private final Map stable = StableValue.lazyMap(SET, Function.identity()); - private final Function function = StableValue.newCachingFunction(SET, Function.identity()); + private final Map stable = StableValue.ofMap(SET, Function.identity()); + private final Function function = StableValue.ofFunction(SET, Function.identity()); @Benchmark public int stable() { diff --git a/test/micro/org/openjdk/bench/java/lang/stable/CachingIntFunctionBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/CachingIntFunctionBenchmark.java index c1d3d06f45d82..f8ad68bfaff73 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/CachingIntFunctionBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/CachingIntFunctionBenchmark.java @@ -57,11 +57,11 @@ public class CachingIntFunctionBenchmark { private static final int SIZE = 100; private static final IntFunction IDENTITY = i -> i; - private static final List STABLE = StableValue.lazyList(SIZE, IDENTITY); - private static final IntFunction INT_FUNCTION = StableValue.newCachingIntFunction(SIZE, IDENTITY); + private static final List STABLE = StableValue.ofList(SIZE, IDENTITY); + private static final IntFunction INT_FUNCTION = StableValue.ofIntFunction(SIZE, IDENTITY); - private final List stable = StableValue.lazyList(SIZE, IDENTITY); - private final IntFunction intFunction = StableValue.newCachingIntFunction(SIZE, IDENTITY); + private final List stable = StableValue.ofList(SIZE, IDENTITY); + private final IntFunction intFunction = StableValue.ofIntFunction(SIZE, IDENTITY); @Benchmark public int stable() { diff --git a/test/micro/org/openjdk/bench/java/lang/stable/CachingSupplierBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/CachingSupplierBenchmark.java index 0039e5efc732a..166a78771b6e2 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/CachingSupplierBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/CachingSupplierBenchmark.java @@ -56,15 +56,15 @@ public class CachingSupplierBenchmark { private static final int VALUE = 42; private static final int VALUE2 = 23; - private static final StableValue STABLE = init(StableValue.newInstance(), VALUE); - private static final StableValue STABLE2 = init(StableValue.newInstance(), VALUE2); - private static final Supplier SUPPLIER = StableValue.newCachingSupplier(() -> VALUE); - private static final Supplier SUPPLIER2 = StableValue.newCachingSupplier(() -> VALUE); + private static final StableValue STABLE = init(StableValue.of(), VALUE); + private static final StableValue STABLE2 = init(StableValue.of(), VALUE2); + private static final Supplier SUPPLIER = StableValue.ofSupplier(() -> VALUE); + private static final Supplier SUPPLIER2 = StableValue.ofSupplier(() -> VALUE); - private final StableValue stable = init(StableValue.newInstance(), VALUE); - private final StableValue stable2 = init(StableValue.newInstance(), VALUE2); - private final Supplier supplier = StableValue.newCachingSupplier(() -> VALUE); - private final Supplier supplier2 = StableValue.newCachingSupplier(() -> VALUE2); + private final StableValue stable = init(StableValue.of(), VALUE); + private final StableValue stable2 = init(StableValue.of(), VALUE2); + private final Supplier supplier = StableValue.ofSupplier(() -> VALUE); + private final Supplier supplier2 = StableValue.ofSupplier(() -> VALUE2); @Benchmark public int stable() { diff --git a/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingBiFunctionBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingBiFunctionBenchmark.java index 5a35d85841632..970c7cbfe9127 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingBiFunctionBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingBiFunctionBenchmark.java @@ -90,8 +90,8 @@ public class CustomCachingBiFunctionBenchmark { private static final BiFunction function = cachingBiFunction(SET, ORIGINAL);; private static final BiFunction function2 = cachingBiFunction(SET, ORIGINAL);; - private static final StableValue STABLE_VALUE = StableValue.newInstance(); - private static final StableValue STABLE_VALUE2 = StableValue.newInstance(); + private static final StableValue STABLE_VALUE = StableValue.of(); + private static final StableValue STABLE_VALUE2 = StableValue.of(); static { STABLE_VALUE.trySet(ORIGINAL.apply(VALUE, VALUE2)); @@ -121,7 +121,7 @@ public int staticStableValue() { // Pair seams to not work that well... static BiFunction cachingBiFunction2(Set> inputs, BiFunction original) { - final Function, R> delegate = StableValue.newCachingFunction(inputs, p -> original.apply(p.left(), p.right())); + final Function, R> delegate = StableValue.ofFunction(inputs, p -> original.apply(p.left(), p.right())); return (T t, U u) -> delegate.apply(new Pair<>(t, u)); } @@ -132,7 +132,7 @@ static BiFunction cachingBiFunction2(Set> inputs, record CachingBiFunction2(Function, R> delegate) implements BiFunction { public CachingBiFunction2(Set> inputs, BiFunction original) { - this(StableValue.newCachingFunction(inputs, (Pair p) -> original.apply(p.left(), p.right()))); + this(StableValue.ofFunction(inputs, (Pair p) -> original.apply(p.left(), p.right()))); } @Override @@ -153,7 +153,7 @@ record CachingBiFunction( public CachingBiFunction(Set> inputs, BiFunction original) { this(Map.copyOf(inputs.stream() - .collect(Collectors.toMap(Function.identity(), _ -> StableValue.newInstance()))), + .collect(Collectors.toMap(Function.identity(), _ -> StableValue.of()))), original ); } @@ -196,8 +196,8 @@ static Map>> delegate(Set>> map = inputs.stream() .collect(Collectors.groupingBy(Pair::left, Collectors.groupingBy(Pair::right, - Collectors.mapping((Function, ? extends StableValue>) _ -> StableValue.newInstance(), - Collectors.reducing(StableValue.newInstance(), _ -> StableValue.newInstance(), (StableValue a, StableValue b) -> a))))); + Collectors.mapping((Function, ? extends StableValue>) _ -> StableValue.of(), + Collectors.reducing(StableValue.of(), _ -> StableValue.of(), (StableValue a, StableValue b) -> a))))); @SuppressWarnings("unchecked") Map>> copy = Map.ofEntries(map.entrySet().stream() @@ -248,12 +248,12 @@ static Map> delegate(Set>> map = inputs.stream() .collect(Collectors.groupingBy(Pair::left, Collectors.groupingBy(Pair::right, - Collectors.mapping((Function, ? extends StableValue>) _ -> StableValue.newInstance(), - Collectors.reducing(StableValue.newInstance(), _ -> StableValue.newInstance(), (StableValue a, StableValue b) -> b))))); + Collectors.mapping((Function, ? extends StableValue>) _ -> StableValue.of(), + Collectors.reducing(StableValue.of(), _ -> StableValue.of(), (StableValue a, StableValue b) -> b))))); @SuppressWarnings("unchecked") Map> copy = Map.ofEntries(map.entrySet().stream() - .map(e -> Map.entry(e.getKey(), StableValue.newCachingFunction( e.getValue().keySet(), (U u) -> original.apply(e.getKey(), u)))) + .map(e -> Map.entry(e.getKey(), StableValue.ofFunction( e.getValue().keySet(), (U u) -> original.apply(e.getKey(), u)))) .toArray(Map.Entry[]::new)); return copy; diff --git a/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingFunctions.java b/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingFunctions.java index 7531de6973ffa..7af5ae130d291 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingFunctions.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingFunctions.java @@ -43,7 +43,7 @@ record Pair(L left, R right){} static Predicate cachingPredicate(Set inputs, Predicate original) { - final Function delegate = StableValue.newCachingFunction(inputs, original::test); + final Function delegate = StableValue.ofFunction(inputs, original::test); return delegate::apply; } @@ -56,7 +56,7 @@ static BiFunction cachingBiFunction(Set> inputs, // Collectors.toUnmodifiableMap() is crucial! final Map> map = tToUs.entrySet().stream() - .map(e -> Map.entry(e.getKey(), StableValue.newCachingFunction(e.getValue(), (U u) -> original.apply(e.getKey(), u)))) + .map(e -> Map.entry(e.getKey(), StableValue.ofFunction(e.getValue(), (U u) -> original.apply(e.getKey(), u)))) .collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue)); return (T t, U u) -> { @@ -83,13 +83,13 @@ static BinaryOperator cachingBinaryOperator(Set> inputs, static UnaryOperator cachingUnaryOperator(Set inputs, UnaryOperator original) { - final Function function = StableValue.newCachingFunction(inputs, original); + final Function function = StableValue.ofFunction(inputs, original); return function::apply; } static IntSupplier cachingIntSupplier(IntSupplier original) { - final Supplier delegate = StableValue.newCachingSupplier(original::getAsInt); + final Supplier delegate = StableValue.ofSupplier(original::getAsInt); return delegate::get; } diff --git a/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingPredicateBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingPredicateBenchmark.java index 40e53b65a84e7..e09ef94cea7b0 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingPredicateBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingPredicateBenchmark.java @@ -95,7 +95,7 @@ record CachingPredicate(Map> delegate, public CachingPredicate(Set inputs, Predicate original) { this(inputs.stream() - .collect(Collectors.toUnmodifiableMap(Function.identity(), _ -> StableValue.newInstance())), + .collect(Collectors.toUnmodifiableMap(Function.identity(), _ -> StableValue.of())), original ); } diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java index 7e8fcd9434590..d8eec559e4e2c 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java @@ -47,19 +47,19 @@ public class StableValueBenchmark { private static final int VALUE = 42; private static final int VALUE2 = 23; - private static final StableValue STABLE = init(StableValue.newInstance(), VALUE); - private static final StableValue STABLE2 = init(StableValue.newInstance(), VALUE2); - private static final StableValue DCL = init(StableValue.newInstance(), VALUE); - private static final StableValue DCL2 = init(StableValue.newInstance(), VALUE2); + private static final StableValue STABLE = init(StableValue.of(), VALUE); + private static final StableValue STABLE2 = init(StableValue.of(), VALUE2); + private static final StableValue DCL = init(StableValue.of(), VALUE); + private static final StableValue DCL2 = init(StableValue.of(), VALUE2); private static final AtomicReference ATOMIC = new AtomicReference<>(VALUE); private static final AtomicReference ATOMIC2 = new AtomicReference<>(VALUE2); private static final Holder HOLDER = new Holder(VALUE); private static final Holder HOLDER2 = new Holder(VALUE2); - private final StableValue stable = init(StableValue.newInstance(), VALUE); - private final StableValue stable2 = init(StableValue.newInstance(), VALUE2); - private final StableValue stableNull = StableValue.newInstance(); - private final StableValue stableNull2 = StableValue.newInstance(); + private final StableValue stable = init(StableValue.of(), VALUE); + private final StableValue stable2 = init(StableValue.of(), VALUE2); + private final StableValue stableNull = StableValue.of(); + private final StableValue stableNull2 = StableValue.of(); private final Supplier dcl = new Dcl<>(() -> VALUE); private final Supplier dcl2 = new Dcl<>(() -> VALUE2); private final AtomicReference atomic = new AtomicReference<>(VALUE); @@ -78,7 +78,7 @@ public void setup() { final int v = i; Dcl dclX = new Dcl<>(() -> v); sum += dclX.get(); - StableValue stableX = StableValue.newInstance(); + StableValue stableX = StableValue.of(); stableX.trySet(i); sum += stableX.orElseThrow(); } @@ -141,7 +141,7 @@ private static StableValue init(StableValue m, Integer value) // because StableValue fields have a special meaning. private static final class Holder { - private final StableValue delegate = StableValue.newInstance(); + private final StableValue delegate = StableValue.of(); Holder(int value) { delegate.setOrThrow(value); From dadc3d1e9ca7621ed9c81393ae9b4f6bfe758f7c Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 14 Oct 2024 16:22:09 +0200 Subject: [PATCH 136/327] Update docs and rename field --- .../share/classes/java/lang/StableValue.java | 62 +++++++++---------- .../internal/lang/stable/StableValueImpl.java | 28 ++++----- .../lang/StableValue/StableValueTest.java | 2 +- .../StableValue/TrustedFieldTypeTest.java | 22 +++++++ 4 files changed, 68 insertions(+), 46 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index cd0dc59056a2c..4f311c9937ed0 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -118,7 +118,7 @@ * set underlying data. * * More generally, the action of attempting to interact (i.e. via load or store operations) - * with a StableValue's holder value (e.g. via {@link StableValue#trySet} or + * with a StableValue's underlying data (e.g. via {@link StableValue#trySet} or * {@link StableValue#orElseThrow()}) forms a * happens-before * relation between any other attempt to interact with the StableValue's underlying data. @@ -132,13 +132,13 @@ * a StableValue. Failure to do this may lead to deadlock. * * @implNote Instance fields explicitly declared as StableValue or one-dimensional arrays - * thereof are eligible for certain JVM optimizations compared to normal - * instance fields. This comes with restrictions on reflective modifications. + * thereof are eligible for certain JVM optimizations where normal instance + * fields are not. This comes with restrictions on reflective modifications. * Although most ways of reflective modification of such fields are disabled, * it is strongly discouraged to circumvent these protection means as * reflectively modifying such fields may lead to unspecified behavior. * - * @param type of the holder value + * @param type of the underlying data * * @since 24 */ @@ -159,7 +159,7 @@ public sealed interface StableValue boolean trySet(T value); /** - * {@return the set underlying data (nullable) if set, otherwise return the + * {@return the underlying data (nullable) if set, otherwise return the * {@code other} value} * * @param other to return if the underlying data is not set @@ -178,35 +178,17 @@ public sealed interface StableValue */ boolean isSet(); - // Convenience methods - - /** - * Sets the underlying data to the provided {@code value}, or, if already set, - * throws {@link IllegalStateException}} - *

      - * When this method returns (or throws an Exception), the underlying data is always set. - * - * @param value to set (nullable) - * @throws IllegalStateException if a holder value is already set - */ - default void setOrThrow(T value) { - if (!trySet(value)) { - throw new IllegalStateException("Cannot set the underlying data to " + value + - " because the underlying data is alredy set: " + this); - } - } - /** - * {@return the underlying data if set, otherwise attempts to compute and set a + * {@return the underlying data if set, otherwise attempts to compute and set * new (nullable) underlying data using the provided {@code supplier}, returning the - * (pre-existing or newly set) underlying data} + * newly set underlying data} *

      * The provided {@code supplier} is guaranteed to be invoked at most once if it * completes without throwing an exception. *

      * If the supplier throws an (unchecked) exception, the exception is rethrown, and no - * value is set. The most common usage is to construct a new object serving as a - * lazily computed value or memoized result, as in: + * underlying data is set. The most common usage is to construct a new object serving + * as a lazily computed value or memoized result, as in: * *

       {@code
            * Value witness = stable.computeIfUnset(Value::new);
      @@ -235,6 +217,24 @@ default void setOrThrow(T value) {
            */
           T computeIfUnset(Supplier supplier);
       
      +    // Convenience methods
      +
      +    /**
      +     * Sets the underlying data to the provided {@code value}, or, if already set,
      +     * throws {@link IllegalStateException}}
      +     * 

      + * When this method returns (or throws an Exception), the underlying data is always set. + * + * @param value to set (nullable) + * @throws IllegalStateException if the underlying data is already set + */ + default void setOrThrow(T value) { + if (!trySet(value)) { + throw new IllegalStateException("Cannot set the underlying data to " + value + + " because the underlying data is already set: " + this); + } + } + // Factories /** @@ -361,11 +361,11 @@ static Function ofFunction(Set inputs, * If the provided {@code mapper} IntFunction throws an exception, it is relayed * to the initial caller and no element is computed. *

      - * The returned List is not {@link Serializable} + * The returned List is not {@link Serializable}. * * @param size the size of the returned list * @param mapper to invoke whenever an element is first accessed (may return null) - * @param the {@code StableValue}s' element type + * @param the type of elements in the returned list */ static List ofList(int size, IntFunction mapper) { if (size < 0) { @@ -393,13 +393,13 @@ static List ofList(int size, IntFunction mapper) { * If the provided {@code mapper} Function throws an exception, it is relayed * to the initial caller and no value is computed. *

      - * The returned Map is not {@link Serializable} + * The returned Map is not {@link Serializable}. * * @param keys the keys in the returned map * @param mapper to invoke whenever an associated value is first accessed * (may return null) * @param the type of keys maintained by the returned map - * @param the type of mapped values + * @param the type of mapped values in the returned map */ static Map ofMap(Set keys, Function mapper) { Objects.requireNonNull(keys); 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 0c29fea45106a..a8cfe7536355d 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 @@ -39,7 +39,7 @@ * @implNote This implementation can be used early in the boot sequence as it does not * rely on reflection, MethodHandles, Streams etc. * - * @param type of the holder value + * @param type of the underlying data */ public final class StableValueImpl implements StableValue { @@ -47,8 +47,8 @@ public final class StableValueImpl implements StableValue { static final Unsafe UNSAFE = Unsafe.getUnsafe(); // Unsafe offsets for direct field access - private static final long VALUE_OFFSET = - UNSAFE.objectFieldOffset(StableValueImpl.class, "wrappedValue"); + private static final long UNDERLYING_DATA_OFFSET = + UNSAFE.objectFieldOffset(StableValueImpl.class, "underlyingData"); // Generally, fields annotated with `@Stable` are accessed by the JVM using special // memory semantics rules (see `parse.hpp` and `parse(1|2|3).cpp`). @@ -62,7 +62,7 @@ public final class StableValueImpl implements StableValue { // | other | Set(other) | // @Stable - private volatile Object wrappedValue; + private volatile Object underlyingData; // Only allow creation via the factory `StableValueImpl::newInstance` private StableValueImpl() {} @@ -70,7 +70,7 @@ private StableValueImpl() {} @ForceInline @Override public boolean trySet(T newValue) { - if (wrappedValue != null) { + if (underlyingData != null) { return false; } // Mutual exclusion is required here as `computeIfUnset` might also @@ -83,9 +83,9 @@ public boolean trySet(T newValue) { @ForceInline @Override public T orElseThrow() { - final Object t = wrappedValue; + final Object t = underlyingData; if (t == null) { - throw new NoSuchElementException("No data set"); + throw new NoSuchElementException("No underlying data set"); } return unwrap(t); } @@ -93,26 +93,26 @@ public T orElseThrow() { @ForceInline @Override public T orElse(T other) { - final Object t = wrappedValue; + final Object t = underlyingData; return (t == null) ? other : unwrap(t); } @ForceInline @Override public boolean isSet() { - return wrappedValue != null; + return underlyingData != null; } @ForceInline @Override public T computeIfUnset(Supplier supplier) { - final Object t = wrappedValue; + final Object t = underlyingData; return (t == null) ? computeIfUnsetSlowPath(supplier) : unwrap(t); } @DontInline private synchronized T computeIfUnsetSlowPath(Supplier supplier) { - final Object t = wrappedValue; + final Object t = underlyingData; if (t == null) { final T newValue = supplier.get(); // The mutex is reentrant so we need to check if the value was actually set. @@ -125,7 +125,7 @@ private synchronized T computeIfUnsetSlowPath(Supplier supplier) { @Override public String toString() { - final Object t = wrappedValue; + final Object t = underlyingData; return t == this ? "(this StableValue)" : "StableValue" + renderWrapped(t); @@ -135,7 +135,7 @@ public String toString() { @ForceInline public Object wrappedValue() { - return wrappedValue; + return underlyingData; } static String renderWrapped(Object t) { @@ -147,7 +147,7 @@ static String renderWrapped(Object t) { @ForceInline private boolean wrapAndCas(Object value) { // This upholds the invariant, a `@Stable` field is written to at most once - return UNSAFE.compareAndSetReference(this, VALUE_OFFSET, null, wrap(value)); + return UNSAFE.compareAndSetReference(this, UNDERLYING_DATA_OFFSET, null, wrap(value)); } // Used to indicate a holder value is `null` (see field `value` below) diff --git a/test/jdk/java/lang/StableValue/StableValueTest.java b/test/jdk/java/lang/StableValue/StableValueTest.java index 69d2ac982514e..08d996adbf2ad 100644 --- a/test/jdk/java/lang/StableValue/StableValueTest.java +++ b/test/jdk/java/lang/StableValue/StableValueTest.java @@ -73,7 +73,7 @@ void orElse() { void orElseThrow() { StableValue stable = StableValue.of(); var e = assertThrows(NoSuchElementException.class, stable::orElseThrow); - assertEquals("No data set", e.getMessage()); + assertEquals("No underlying data set", e.getMessage()); stable.trySet(VALUE); assertEquals(VALUE, stable.orElseThrow()); } diff --git a/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java b/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java index f078b6630052e..a4de715bf3054 100644 --- a/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java +++ b/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java @@ -25,10 +25,12 @@ * @summary Basic tests for TrustedFieldType implementations * @modules jdk.unsupported/sun.misc * @modules java.base/jdk.internal.lang.stable + * @modules java.base/jdk.internal.misc * @compile --enable-preview -source ${jdk.version} TrustedFieldTypeTest.java * @run junit/othervm --enable-preview TrustedFieldTypeTest */ +import jdk.internal.misc.Unsafe; import org.junit.jupiter.api.Test; import java.lang.invoke.MethodHandles; @@ -104,6 +106,11 @@ final class ArrayHolder { unsafe.objectFieldOffset(arrayField) ); + // Test direct access + StableValue stableValue = StableValue.of(); + Class clazz = stableValue.getClass(); + System.out.println("clazz = " + clazz); + assertThrows(NoSuchFieldException.class, () -> clazz.getField("underlyingData")); } @Test @@ -146,4 +153,19 @@ final class ArrayHolder { } + @Test + void updateStableValueUnderlyingData() { + StableValue stableValue = StableValue.of(); + stableValue.trySet(42); + jdk.internal.misc.Unsafe unsafe = Unsafe.getUnsafe(); + + long offset = unsafe.objectFieldOffset(stableValue.getClass(), "underlyingData"); + assertTrue(offset > 0); + + // Unfortunately, it is possible to update the underlying data via jdk.internal.misc.Unsafe + Object oldData = unsafe.getAndSetReference(stableValue, offset, 13); + assertEquals(42, oldData); + assertEquals(13, stableValue.orElseThrow()); + } + } From eaf3fe23021ccb030ba7a6807935d5e57b15ac9b Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 14 Oct 2024 16:35:31 +0200 Subject: [PATCH 137/327] Rename stable functions and tests --- ...ngFunction.java => EmptyStableFunction.java} | 8 ++++---- ...numFunction.java => StableEnumFunction.java} | 17 ++++++++--------- ...CachingFunction.java => StableFunction.java} | 16 ++++++++-------- ...gIntFunction.java => StableIntFunction.java} | 14 +++++++------- ...CachingSupplier.java => StableSupplier.java} | 10 +++++----- .../lang/stable/StableValueFactories.java | 12 ++++++------ ...unctionTest.java => StableFunctionTest.java} | 12 ++++++------ ...tionTest.java => StableIntFunctionTest.java} | 16 ++++++++-------- ...upplierTest.java => StableSupplierTest.java} | 16 ++++++++-------- 9 files changed, 60 insertions(+), 61 deletions(-) rename src/java.base/share/classes/jdk/internal/lang/stable/{EmptyCachingFunction.java => EmptyStableFunction.java} (88%) rename src/java.base/share/classes/jdk/internal/lang/stable/{CachingEnumFunction.java => StableEnumFunction.java} (84%) rename src/java.base/share/classes/jdk/internal/lang/stable/{CachingFunction.java => StableFunction.java} (84%) rename src/java.base/share/classes/jdk/internal/lang/stable/{CachingIntFunction.java => StableIntFunction.java} (87%) rename src/java.base/share/classes/jdk/internal/lang/stable/{CachingSupplier.java => StableSupplier.java} (81%) rename test/jdk/java/lang/StableValue/{CachingFunctionTest.java => StableFunctionTest.java} (94%) rename test/jdk/java/lang/StableValue/{CachingIntFunctionTest.java => StableIntFunctionTest.java} (84%) rename test/jdk/java/lang/StableValue/{CachingSupplierTest.java => StableSupplierTest.java} (83%) diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/EmptyCachingFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/EmptyStableFunction.java similarity index 88% rename from src/java.base/share/classes/jdk/internal/lang/stable/EmptyCachingFunction.java rename to src/java.base/share/classes/jdk/internal/lang/stable/EmptyStableFunction.java index f82ccbc3643ae..61585541b0e4d 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/EmptyCachingFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/EmptyStableFunction.java @@ -30,7 +30,7 @@ import java.util.function.Function; /** - * An empty caching function with no allowed inputs + * An empty stable function with no allowed inputs * * @implNote This implementation can be used early in the boot sequence as it does not * rely on reflection, MethodHandles, Streams etc. @@ -39,7 +39,7 @@ * @param the type of the input to the function * @param the type of the result of the function */ -record EmptyCachingFunction(Function original) implements Function { +record EmptyStableFunction(Function original) implements Function { @ForceInline @Override @@ -59,11 +59,11 @@ public boolean equals(Object obj) { @Override public String toString() { - return "EmptyCachingFunction[values={}, original=" + original + "]"; + return "EmptyStableFunction[values={}, original=" + original + "]"; } static Function of(Function original) { - return new EmptyCachingFunction<>(original); + return new EmptyStableFunction<>(original); } } diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/CachingEnumFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java similarity index 84% rename from src/java.base/share/classes/jdk/internal/lang/stable/CachingEnumFunction.java rename to src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java index 579d5455c4a47..cfb1b5f336efb 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/CachingEnumFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java @@ -28,13 +28,12 @@ import jdk.internal.vm.annotation.ForceInline; import jdk.internal.vm.annotation.Stable; -import java.util.Objects; import java.util.Set; import java.util.function.Function; import java.util.function.Supplier; /** - * Optimized implementation of a cached Function with enums as keys. + * Optimized implementation of a stable Function with enums as keys. * * @implNote This implementation can be used early in the boot sequence as it does not * rely on reflection, MethodHandles, Streams etc. @@ -45,10 +44,10 @@ * @param the type of the input to the function * @param the type of the result of the function */ -record CachingEnumFunction, R>(Class enumType, - int firstOrdinal, - @Stable StableValueImpl[] delegates, - Function original) implements Function { +record StableEnumFunction, R>(Class enumType, + int firstOrdinal, + @Stable StableValueImpl[] delegates, + Function original) implements Function { @ForceInline @Override public R apply(E value) { @@ -74,7 +73,7 @@ public boolean equals(Object obj) { @Override public String toString() { - return "CachingEnumFunction[values=" + renderElements() + ", original=" + original + "]"; + return "StableEnumFunction[values=" + renderElements() + ", original=" + original + "]"; } private String renderElements() { @@ -88,7 +87,7 @@ private String renderElements() { final Object value = delegates[i].wrappedValue(); sb.append(enumElements[ordinal++]).append('='); if (value == this) { - sb.append("(this CachingEnumFunction)"); + sb.append("(this StableEnumFunction)"); } else { sb.append(StableValueImpl.renderWrapped(value)); } @@ -109,7 +108,7 @@ static , R> Function of(Set inputs, } final int size = max - min + 1; final Class enumType = (Class)inputs.iterator().next().getClass(); - return (Function) new CachingEnumFunction(enumType, min, StableValueFactories.ofArray(size), (Function) original); + return (Function) new StableEnumFunction(enumType, min, StableValueFactories.ofArray(size), (Function) original); } } diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/CachingFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableFunction.java similarity index 84% rename from src/java.base/share/classes/jdk/internal/lang/stable/CachingFunction.java rename to src/java.base/share/classes/jdk/internal/lang/stable/StableFunction.java index fd85f345b425d..89fcc1ea586cb 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/CachingFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableFunction.java @@ -36,7 +36,7 @@ // instead of this class but explicitly providing a class like this provides better // debug capability, exception handling, and may provide better performance. /** - * Implementation of a cached Function. + * Implementation of a stable Function. * * @implNote This implementation can be used early in the boot sequence as it does not * rely on reflection, MethodHandles, Streams etc. @@ -46,8 +46,8 @@ * @param the type of the input to the function * @param the type of the result of the function */ -record CachingFunction(Map> values, - Function original) implements Function { +record StableFunction(Map> values, + Function original) implements Function { @ForceInline @Override public R apply(T value) { @@ -71,7 +71,7 @@ public boolean equals(Object obj) { @Override public String toString() { - return "CachingFunction[values=" + renderMappings() + ", original=" + original + "]"; + return "StableFunction[values=" + renderMappings() + ", original=" + original + "]"; } private String renderMappings() { @@ -83,7 +83,7 @@ private String renderMappings() { final Object value = e.getValue().wrappedValue(); sb.append(e.getKey()).append('='); if (value == this) { - sb.append("(this CachingFunction)"); + sb.append("(this StableFunction)"); } else { sb.append(StableValueImpl.renderWrapped(value)); } @@ -92,9 +92,9 @@ private String renderMappings() { return sb.toString(); } - static CachingFunction of(Set inputs, - Function original) { - return new CachingFunction<>(StableValueFactories.ofMap(inputs), original); + static StableFunction of(Set inputs, + Function original) { + return new StableFunction<>(StableValueFactories.ofMap(inputs), original); } } diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/CachingIntFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableIntFunction.java similarity index 87% rename from src/java.base/share/classes/jdk/internal/lang/stable/CachingIntFunction.java rename to src/java.base/share/classes/jdk/internal/lang/stable/StableIntFunction.java index b6e5173b0a411..45c5401c3fd93 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/CachingIntFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableIntFunction.java @@ -35,7 +35,7 @@ // class but explicitly providing a class like this provides better // debug capability, exception handling, and may provide better performance. /** - * Implementation of a cached IntFunction. + * Implementation of a stable IntFunction. *

      * For performance reasons (~10%), we are not delegating to a StableList but are using * the more primitive functions in StableValueUtil that are shared with StableList/StableValueImpl. @@ -45,8 +45,8 @@ * * @param the return type */ -record CachingIntFunction(@Stable StableValueImpl[] delegates, - IntFunction original) implements IntFunction { +record StableIntFunction(@Stable StableValueImpl[] delegates, + IntFunction original) implements IntFunction { @ForceInline @Override @@ -72,7 +72,7 @@ public boolean equals(Object obj) { @Override public String toString() { - return "CachingIntFunction[values=" + + return "StableIntFunction[values=" + renderElements() + ", original=" + original + ']'; } @@ -85,7 +85,7 @@ private String renderElements() { if (first) { first = false; } else { sb.append(", "); }; final Object value = delegates[i].wrappedValue(); if (value == this) { - sb.append("(this CachingIntFunction)"); + sb.append("(this StableIntFunction)"); } else { sb.append(StableValueImpl.renderWrapped(value)); } @@ -94,8 +94,8 @@ private String renderElements() { return sb.toString(); } - static CachingIntFunction of(int size, IntFunction original) { - return new CachingIntFunction<>(StableValueFactories.ofArray(size), original); + static StableIntFunction of(int size, IntFunction original) { + return new StableIntFunction<>(StableValueFactories.ofArray(size), original); } } diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/CachingSupplier.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableSupplier.java similarity index 81% rename from src/java.base/share/classes/jdk/internal/lang/stable/CachingSupplier.java rename to src/java.base/share/classes/jdk/internal/lang/stable/StableSupplier.java index 787e808e63a74..c1d05c459fa20 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/CachingSupplier.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableSupplier.java @@ -30,14 +30,14 @@ import java.util.function.Supplier; /** - * Implementation of a cached supplier. + * Implementation of a stable supplier. *

      * @implNote This implementation can be used early in the boot sequence as it does not * rely on reflection, MethodHandles, Streams etc. * * @param the return type */ -record CachingSupplier(StableValueImpl delegate, Supplier original) implements Supplier { +record StableSupplier(StableValueImpl delegate, Supplier original) implements Supplier { @ForceInline @Override @@ -58,11 +58,11 @@ public boolean equals(Object obj) { @Override public String toString() { final Object t = delegate.wrappedValue(); - return "CachingSupplier[value=" + (t == this ? "(this CachingSupplier)" : StableValueImpl.renderWrapped(t)) + ", original=" + original + "]"; + return "StableSupplier[value=" + (t == this ? "(this StableSupplier)" : StableValueImpl.renderWrapped(t)) + ", original=" + original + "]"; } - static CachingSupplier of(Supplier original) { - return new CachingSupplier<>(StableValueImpl.newInstance(), original); + static StableSupplier of(Supplier original) { + return new StableSupplier<>(StableValueImpl.newInstance(), original); } } diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableValueFactories.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableValueFactories.java index 1178f24ee540a..436b6845561a4 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableValueFactories.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableValueFactories.java @@ -19,22 +19,22 @@ public static StableValueImpl of() { } public static Supplier ofSupplier(Supplier original) { - return CachingSupplier.of(original); + return StableSupplier.of(original); } public static IntFunction ofIntFunction(int size, IntFunction original) { - return CachingIntFunction.of(size, original); + return StableIntFunction.of(size, original); } public static Function ofFunction(Set inputs, Function original) { if (inputs.isEmpty()) { - return EmptyCachingFunction.of(original); + return EmptyStableFunction.of(original); } return inputs instanceof EnumSet - ? CachingEnumFunction.of(inputs, original) - : CachingFunction.of(inputs, original); + ? StableEnumFunction.of(inputs, original) + : StableFunction.of(inputs, original); } @SuppressWarnings("unchecked") @@ -45,7 +45,7 @@ private static > EnumSet asEnumSet(Set orig public static , R> Function newCachingEnumFunction(EnumSet inputs, Function original) { - return CachingEnumFunction.of(inputs, original); + return StableEnumFunction.of(inputs, original); } public static StableValueImpl[] ofArray(int size) { diff --git a/test/jdk/java/lang/StableValue/CachingFunctionTest.java b/test/jdk/java/lang/StableValue/StableFunctionTest.java similarity index 94% rename from test/jdk/java/lang/StableValue/CachingFunctionTest.java rename to test/jdk/java/lang/StableValue/StableFunctionTest.java index f65edb6f7d23e..c57b7fcdf45a8 100644 --- a/test/jdk/java/lang/StableValue/CachingFunctionTest.java +++ b/test/jdk/java/lang/StableValue/StableFunctionTest.java @@ -22,9 +22,9 @@ */ /* @test - * @summary Basic tests for CachingFunction methods - * @compile --enable-preview -source ${jdk.version} CachingFunctionTest.java - * @run junit/othervm --enable-preview CachingFunctionTest + * @summary Basic tests for StableFunctionTest methods + * @compile --enable-preview -source ${jdk.version} StableFunctionTest.java + * @run junit/othervm --enable-preview StableFunctionTest */ import org.junit.jupiter.api.Test; @@ -43,7 +43,7 @@ import static org.junit.jupiter.api.Assertions.*; -final class CachingFunctionTest { +final class StableFunctionTest { enum Value { // Zero is here so that we have enums with ordinals before the first one @@ -163,9 +163,9 @@ void hashCodeStable(Set inputs) { @Test void usesOptimizedVersion() { Function enumFunction = StableValue.ofFunction(EnumSet.of(Value.FORTY_TWO), Value::asInt); - assertEquals("jdk.internal.lang.stable.CachingEnumFunction", enumFunction.getClass().getName()); + assertEquals("jdk.internal.lang.stable.StableEnumFunction", enumFunction.getClass().getName()); Function emptyFunction = StableValue.ofFunction(Set.of(), Value::asInt); - assertEquals("jdk.internal.lang.stable.EmptyCachingFunction", emptyFunction.getClass().getName()); + assertEquals("jdk.internal.lang.stable.EmptyStableFunction", emptyFunction.getClass().getName()); } private static Stream> nonEmptySets() { diff --git a/test/jdk/java/lang/StableValue/CachingIntFunctionTest.java b/test/jdk/java/lang/StableValue/StableIntFunctionTest.java similarity index 84% rename from test/jdk/java/lang/StableValue/CachingIntFunctionTest.java rename to test/jdk/java/lang/StableValue/StableIntFunctionTest.java index a1c3d5a92da7b..0141f92a91fb8 100644 --- a/test/jdk/java/lang/StableValue/CachingIntFunctionTest.java +++ b/test/jdk/java/lang/StableValue/StableIntFunctionTest.java @@ -22,9 +22,9 @@ */ /* @test - * @summary Basic tests for CachingIntFunction methods - * @compile --enable-preview -source ${jdk.version} CachingIntFunctionTest.java - * @run junit/othervm --enable-preview CachingIntFunctionTest + * @summary Basic tests for StableIntFunctionTest methods + * @compile --enable-preview -source ${jdk.version} StableIntFunctionTest.java + * @run junit/othervm --enable-preview StableIntFunctionTest */ import org.junit.jupiter.api.Test; @@ -34,7 +34,7 @@ import static org.junit.jupiter.api.Assertions.*; -final class CachingIntFunctionTest { +final class StableIntFunctionTest { private static final int SIZE = 2; private static final IntFunction MAPPER = i -> i; @@ -54,12 +54,12 @@ void basic() { void basic(IntFunction mapper) { StableTestUtil.CountingIntFunction cif = new StableTestUtil.CountingIntFunction<>(mapper); var cached = StableValue.ofIntFunction(SIZE, cif); - assertEquals("CachingIntFunction[values=[.unset, .unset], original=" + cif + "]", cached.toString()); + assertEquals("StableIntFunction[values=[.unset, .unset], original=" + cif + "]", cached.toString()); assertEquals(mapper.apply(1), cached.apply(1)); assertEquals(1, cif.cnt()); assertEquals(mapper.apply(1), cached.apply(1)); assertEquals(1, cif.cnt()); - assertEquals("CachingIntFunction[values=[.unset, [" + mapper.apply(1) + "]], original=" + cif + "]", cached.toString()); + assertEquals("StableIntFunction[values=[.unset, [" + mapper.apply(1) + "]], original=" + cif + "]", cached.toString()); assertThrows(IllegalArgumentException.class, () -> cached.apply(SIZE)); assertThrows(IllegalArgumentException.class, () -> cached.apply(-1)); assertThrows(IllegalArgumentException.class, () -> cached.apply(1_000_000)); @@ -75,7 +75,7 @@ void exception() { assertEquals(1, cif.cnt()); assertThrows(UnsupportedOperationException.class, () -> cached.apply(1)); assertEquals(2, cif.cnt()); - assertEquals("CachingIntFunction[values=[.unset, .unset], original=" + cif + "]", cached.toString()); + assertEquals("StableIntFunction[values=[.unset, .unset], original=" + cif + "]", cached.toString()); } @Test @@ -85,7 +85,7 @@ void circular() { ref.set(cached); cached.apply(0); String toString = cached.toString(); - assertTrue(toString.startsWith("CachingIntFunction[values=[(this CachingIntFunction), .unset], original=")); + assertTrue(toString.startsWith("StableIntFunction[values=[(this StableIntFunction), .unset], original=")); assertDoesNotThrow(cached::hashCode); assertDoesNotThrow((() -> cached.equals(cached))); } diff --git a/test/jdk/java/lang/StableValue/CachingSupplierTest.java b/test/jdk/java/lang/StableValue/StableSupplierTest.java similarity index 83% rename from test/jdk/java/lang/StableValue/CachingSupplierTest.java rename to test/jdk/java/lang/StableValue/StableSupplierTest.java index 9eb7b2018d768..e4fbd7729c16f 100644 --- a/test/jdk/java/lang/StableValue/CachingSupplierTest.java +++ b/test/jdk/java/lang/StableValue/StableSupplierTest.java @@ -22,9 +22,9 @@ */ /* @test - * @summary Basic tests for CachingSupplier methods - * @compile --enable-preview -source ${jdk.version} CachingSupplierTest.java - * @run junit/othervm --enable-preview CachingSupplierTest + * @summary Basic tests for StableSupplierTest methods + * @compile --enable-preview -source ${jdk.version} StableSupplierTest.java + * @run junit/othervm --enable-preview StableSupplierTest */ import org.junit.jupiter.api.Test; @@ -34,7 +34,7 @@ import static org.junit.jupiter.api.Assertions.*; -final class CachingSupplierTest { +final class StableSupplierTest { private static final Supplier SUPPLIER = () -> 42; @@ -52,12 +52,12 @@ void basic() { void basic(Supplier supplier) { StableTestUtil.CountingSupplier cs = new StableTestUtil.CountingSupplier<>(supplier); var cached = StableValue.ofSupplier(cs); - assertEquals("CachingSupplier[value=.unset, original=" + cs + "]", cached.toString()); + assertEquals("StableSupplier[value=.unset, original=" + cs + "]", cached.toString()); assertEquals(supplier.get(), cached.get()); assertEquals(1, cs.cnt()); assertEquals(supplier.get(), cached.get()); assertEquals(1, cs.cnt()); - assertEquals("CachingSupplier[value=[" + supplier.get() + "], original=" + cs + "]", cached.toString()); + assertEquals("StableSupplier[value=[" + supplier.get() + "], original=" + cs + "]", cached.toString()); } @Test @@ -70,7 +70,7 @@ void exception() { assertEquals(1, cs.cnt()); assertThrows(UnsupportedOperationException.class, cached::get); assertEquals(2, cs.cnt()); - assertEquals("CachingSupplier[value=.unset, original=" + cs + "]", cached.toString()); + assertEquals("StableSupplier[value=.unset, original=" + cs + "]", cached.toString()); } @Test @@ -80,7 +80,7 @@ void circular() { ref.set(cached); cached.get(); String toString = cached.toString(); - assertTrue(toString.startsWith("CachingSupplier[value=(this CachingSupplier), original=")); + assertTrue(toString.startsWith("StableSupplier[value=(this StableSupplier), original=")); assertDoesNotThrow(cached::hashCode); } From 017af0d5a4f6f69aa48d178aec7f1d6f91a50ab6 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 16 Oct 2024 14:24:00 +0200 Subject: [PATCH 138/327] Improve docs --- .../share/classes/java/lang/StableValue.java | 207 +++++++++++------- .../internal/lang/stable/StableValueImpl.java | 6 +- 2 files changed, 135 insertions(+), 78 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 4f311c9937ed0..f3319b1bee819 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -41,89 +41,144 @@ import java.util.function.Supplier; /** - * A thin, atomic, thread-safe, set-at-most-once, stable holder capable of holding - * underlying data, eligible for certain JVM optimizations if set to a value. - *

      - * A stable value is said to be monotonic because the state of a stable value can only go - * from unset to set and consequently, the underlying data can only be - * set at most once. - *

      - * StableValue is mainly intended to be a member of a holding class and is usually neither - * exposed directly via accessors nor passed as a method parameter. - * - * - *

      Factories

      - *

      - * To create a new fresh (unset) StableValue, use the - * {@linkplain StableValue#of() StableValue::of} factory. - *

      - * This class also contains a number of convenience methods for creating constructs - * involving stable values: + * Stable values are objects that represent immutable data and are treated as constants + * by the JVM, enabling the same performance optimizations that are possible by marking + * a field final. Yet, stable values offer greater flexibility as to the timing of + * initialization compared to final fields. + *

      + * There are several variants of stable value objects: *

        - *
      • - * A stable (also called "cached" or "memoized") Supplier, where a given {@code original} - * Supplier is guaranteed to be successfully invoked at most once even in a multithreaded - * environment, can be created like this: + *
      • Stable value: {@linkplain StableValue {@code StableValue}}
      • + *
      • Stable supplier: {@linkplain Supplier {@code Supplier}}
      • + *
      • Stable int function: {@linkplain IntFunction {@code IntFunction}}
      • + *
      • Stable list: {@linkplain List {@code List}}
      • + *
      • Stable function: {@linkplain Function {@code Function}}
      • + *
      • Stable map: {@linkplain Map {@code Map}}
      • + *
      + *

      + * A stable value ({@linkplain StableValue}) can be used to defer initialization + * of data while retaining the same performance as if a field was declared {@code final}: * {@snippet lang = java: - * Supplier stable = StableValue.ofSupplier(original); + * class Component { + * + * private final StableValue logger = StableValue.of(); + * + * Logger getLogger() { + * return logger.computeIfUnset( () -> Logger.getLogger("org.app.Component") ); + * } + * + * void process() { + * getLogger().info("Process started"); + * // ... + * } + * + * } *} - * + *

      + * A {@linkplain StableValue} is mainly intended to be a member of a holding class and is + * usually neither exposed directly via accessors nor passed as a method parameter. + *

      + * A stable supplier allows a more convenient construct where a field declaration + * can also specify how the underlying data of a stable value is to be initialized, but + * without actually initializing its underlying data upfront: + * {@snippet lang = java: + * class Component { * - *

    • - * A stable (also called "cached" or "memoized") IntFunction, for the allowed given - * {@code size} input values {@code [0, size)} and where the given {@code original} - * IntFunction is guaranteed to be successfully invoked at most once per inout index even - * in a multithreaded environment, can be created like this: + * private final Supplier logger = + * StableValue.ofSupplier( () -> Logger.getLogger("org.app.Component") ); + * + * void process() { + * logger.get().info("Process started"); + * // ... + * } + * } + *} + * This also allows the stable supplier to be accessed directly, without going through + * an accessor method like {@code getLogger()}. + *

      + * A stable int function stores values in an array of stable values where + * the underlying values are computed the first time a particular input value is seen. + * When the stable int function is first created -- + * via the {@linkplain StableValue#ofIntFunction(int, IntFunction)} factory -- the input + * range (i.e. {@code [0, size)}) is specified together with an original + * {@linkplain IntFunction} which is invoked at most once per input value. In effect, + * the stable int function will act like a cache for the original {@code IntFunction}: * {@snippet lang = java: - * IntFunction stable = StableValue.ofIntFunction(size, original); + * class SqrtUtil { + * + * private static final IntFunction SQRT = + * StableValue.ofIntFunction(10, Math::sqrt); + * + * double sqrt9() { + * return SQRT.apply(9); // Constant folds to 3.0 + * } + * + * } *} - *

    • + *

      + * a stable list is similar to a stable int function, but provides a full + * implementation of an immutable {@linkplain List}. This is useful when interacting with + * collection based methods. Here is an example how a stable list can be used to hold + * a pool of order controllers: + * {@snippet lang = java: + * class Application { + * static final List ORDER_CONTROLLERS = + * StableValue.ofList(POOL_SIZE, OrderController::new); * - *

    • - * A stable (also called "cached" or "memoized") Function, for the given set of allowed - * {@code inputs} and where the given {@code original} function is guaranteed to be - * successfully invoked at most once per input value even in a multithreaded environment, - * can be created like this: - * {@snippet lang = java : - * Function stable = StableValue.stableFunction(inputs, original); + * public static OrderController orderController() { + * long index = Thread.currentThread().threadId() % POOL_SIZE; + * return ORDER_CONTROLLERS.get((int) index); + * } + * } * } - *
    • - * - *
    • - * A stable List of stable elements with a given {@code size} and given {@code mapper} can - * be created the following way: + *

      + * A stable function stores values in an array of stable values where + * the underlying values are computed the first time a particular input value is seen. + * When the stable function is first created -- + * via the {@linkplain StableValue#ofFunction(Set, Function)} factory -- the input set + * is specified together with an original {@linkplain Function} which is invoked + * at most once per input value. In effect, the stable function will act like a + * cache for the original {@code Function}: * {@snippet lang = java: - * List stableList = StableValue.ofList(size, mapper); + * class SqrtUtil { + * + * private static final Function SQRT = + * StableValue.ofFunction(Set.of(1, 2, 4, 8, 16, 32), Math::sqrt); + * + * double sqrt16() { + * return SQRT.apply(16); // Constant folds to 4.0 + * } + * + * } *} - * The list can be used to model stable one-dimensional arrays. If two- or more - * dimensional arrays are to be modeled, a List of List of ... of E can be used. - *

    • + *

      + * a stable map is similar to a stable function, but provides a full + * implementation of an immutable {@linkplain Map}. This is useful when interacting with + * collections. Here is how a stable map can be used as a cache for square root values: + * {@snippet lang = java: + * class SqrtUtil { * - *

    • - * A stable Map with a given set of {@code keys} and given {@code mapper} associated with - * stable values can be created like this: - * {@snippet lang = java : - * Map stableMap = StableValue.stableMap(keys, mapper); - * } - *
    • + * private static final Map SQRT = + * StableValue.ofMap(Set.of(1, 2, 4, 8, 16, 32), Math::sqrt); * - *
    - *

    - * The constructs above are eligible for similar JVM optimizations as StableValue - * instances. + * double sqrt16() { + * return SQRT.apply(16); // Constant folds to 4.0 + * } + * + * } + *} * *

    Memory Consistency Properties

    * Actions on a presumptive underlying data in a thread prior to calling a method that * sets the underlying data are seen by any other thread that observes a * set underlying data. - * + *

    * More generally, the action of attempting to interact (i.e. via load or store operations) * with a StableValue's underlying data (e.g. via {@link StableValue#trySet} or * {@link StableValue#orElseThrow()}) forms a * happens-before * relation between any other attempt to interact with the StableValue's underlying data. - * - *

    Nullability

    + *

    * Except for a StableValue's underlying data itself, all method parameters must be * non-null or a {@link NullPointerException} will be thrown. * @@ -149,17 +204,17 @@ public sealed interface StableValue // Principal methods /** - * {@return {@code true} if the underlying data was set to the provided {@code value}, - * otherwise returns {@code false}} + * {@return {@code true} if the underlying data was set to the provided + * {@code underlyingData}, otherwise returns {@code false}} *

    * When this method returns, the underlying data is always set. * - * @param value to set (nullable) + * @param underlyingData to set */ - boolean trySet(T value); + boolean trySet(T underlyingData); /** - * {@return the underlying data (nullable) if set, otherwise return the + * {@return the underlying data if set, otherwise return the * {@code other} value} * * @param other to return if the underlying data is not set @@ -180,7 +235,7 @@ public sealed interface StableValue /** * {@return the underlying data if set, otherwise attempts to compute and set - * new (nullable) underlying data using the provided {@code supplier}, returning the + * new underlying data using the provided {@code supplier}, returning the * newly set underlying data} *

    * The provided {@code supplier} is guaranteed to be invoked at most once if it @@ -220,17 +275,17 @@ public sealed interface StableValue // Convenience methods /** - * Sets the underlying data to the provided {@code value}, or, if already set, - * throws {@link IllegalStateException}} + * Sets the underlying data to the provided {@code underlyingData}, or, + * if already set, throws {@link IllegalStateException}} *

    * When this method returns (or throws an Exception), the underlying data is always set. * - * @param value to set (nullable) + * @param underlyingData to set * @throws IllegalStateException if the underlying data is already set */ - default void setOrThrow(T value) { - if (!trySet(value)) { - throw new IllegalStateException("Cannot set the underlying data to " + value + + default void setOrThrow(T underlyingData) { + if (!trySet(underlyingData)) { + throw new IllegalStateException("Cannot set the underlying data to " + underlyingData + " because the underlying data is already set: " + this); } } @@ -238,9 +293,11 @@ default void setOrThrow(T value) { // Factories /** - * {@return a fresh stable value with no underlying data set} + * {@return a new thin, atomic, thread-safe, set-at-most-once, {@linkplain StableValue} + * with no underlying data eligible for certain JVM optimizations once the + * underlying data is set} * - * @param type of the holder value + * @param type of the underlying data */ static StableValue of() { return StableValueFactories.of(); 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 a8cfe7536355d..e757224647551 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 @@ -69,14 +69,14 @@ private StableValueImpl() {} @ForceInline @Override - public boolean trySet(T newValue) { - if (underlyingData != null) { + public boolean trySet(T underlyingData) { + if (this.underlyingData != null) { return false; } // Mutual exclusion is required here as `computeIfUnset` might also // attempt to modify the `wrappedValue` synchronized (this) { - return wrapAndCas(newValue); + return wrapAndCas(underlyingData); } } From 765ed48d3615ac226e7c36a0439e978740d07861 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 21 Oct 2024 10:27:54 +0200 Subject: [PATCH 139/327] Improve docs --- .../share/classes/java/lang/StableValue.java | 276 +++++++++++++----- 1 file changed, 197 insertions(+), 79 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index f3319b1bee819..7572970e46b29 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -45,19 +45,34 @@ * by the JVM, enabling the same performance optimizations that are possible by marking * a field final. Yet, stable values offer greater flexibility as to the timing of * initialization compared to final fields. - *

    - * There are several variants of stable value objects: + *

    + * There are three categories of stable values and six variants of stable value objects : *

      - *
    • Stable value: {@linkplain StableValue {@code StableValue}}
    • - *
    • Stable supplier: {@linkplain Supplier {@code Supplier}}
    • - *
    • Stable int function: {@linkplain IntFunction {@code IntFunction}}
    • - *
    • Stable list: {@linkplain List {@code List}}
    • - *
    • Stable function: {@linkplain Function {@code Function}}
    • - *
    • Stable map: {@linkplain Map {@code Map}}
    • + *
    • {@linkplain StableValue##stable-holder Stable Holder} + *
        + *
      • Stable value: {@linkplain StableValue {@code StableValue}}
      • + *
      + *
    • + *
    • {@linkplain StableValue##stable-functions Stable Functions} + *
        + *
      • Stable supplier: {@linkplain Supplier {@code Supplier}}
      • + *
      • Stable int function: {@linkplain IntFunction {@code IntFunction}}
      • + *
      • Stable function: {@linkplain Function {@code Function}}
      • + *
      + *
    • + *
    • {@linkplain StableValue##stable-collections Stable Collections} + *
        + *
      • Stable list: {@linkplain List {@code List}}
      • + *
      • Stable map: {@linkplain Map {@code Map}}
      • + *
      + *
    • *
    - *

    - * A stable value ({@linkplain StableValue}) can be used to defer initialization - * of data while retaining the same performance as if a field was declared {@code final}: + * + *

    Stable Holder

    + * A stable value is of type {@linkplain StableValue {@code StableValue}} and + * is a holder of underlying data of type {@code T}. It can be used to defer + * initialization of its underlying data while retaining the same performance as if + * a field of type {@code T} was declared {@code final}: * {@snippet lang = java: * class Component { * @@ -74,13 +89,101 @@ * * } *} - *

    - * A {@linkplain StableValue} is mainly intended to be a member of a holding class and is - * usually neither exposed directly via accessors nor passed as a method parameter. - *

    - * A stable supplier allows a more convenient construct where a field declaration - * can also specify how the underlying data of a stable value is to be initialized, but - * without actually initializing its underlying data upfront: + *

    + * A {@linkplain StableValue} is mainly intended to be a member of a holding class (as + * shown in the example above) and is usually neither exposed directly via accessors nor + * passed as a method parameter. + * + *

    Initializing the underlying data

    + * A {@linkplain StableValue}'s underlying data can be initialized to a {@code value} of + * type {@code T} via three instance methods: + *
      + *
    • {@linkplain StableValue#trySet(Object) {@code trySet(T value)}}
    • + *
    • {@linkplain StableValue#setOrThrow(Object) {@code setOrThrow(T value)}}
    • + *
    • {@linkplain StableValue#computeIfUnset(Supplier) {@code computeIfUnset(Supplier} supplier)}
    • + *
    + *

    + * Here is an example of how the underlying data can be initialized imperatively + * using a pre-computed value (here {@code 42}) using the method + * {@linkplain StableValue#trySet(Object) trySet()}. The method will return {@code true} + * if the underlying data was indeed initialized, or it will return {@code false} if + * the underlying data was already initialized: + * + * {@snippet lang=java : + * StableValue stableValue = StableValue.of(); + * // ... + * if (stableValue.trySet(42)) { + * System.out.println("The underlying data was initialized to 42."); + * } else { + * System.out.println("The value was already initialized."); + * } + * } + *

    + * A similar way to initialize the underlying data imperatively using a pre-computed + * value, is to invoke the method {@linkplain StableValue#setOrThrow(Object) + * setOrThrow()}. The method will initialize the underlying data if uninitialized, + * otherwise it will throw {@linkplain IllegalStateException}: + * + * {@snippet lang=java : + * StableValue stableValue = StableValue.of(); + * // ... + * // Throws IllegalStateException if the underlying data is already initialized + * int val = stableValue.setOrThrow(42); // 42 + * } + *

    + * Finally, the underlying data can be initialized via + * {@linkplain StableValue#computeIfUnset(Supplier) {@code computeIfUnset(Supplier} supplier)} + * as shown in the {@linkplain StableValue##stable-holder initial example} in this class. + * It should be noted that a {@linkplain StableValue} guarantees the provided + * {@code supplier} is invoked at most once if it did not throw an exception even in + * a multithreaded environment. + * + *

    Retrieving the underlying data

    + * The underlying data of a stable value can be retrieved using two instance methods: + *
      + *
    • {@linkplain StableValue#orElse(Object) {@code orElse(T other)}}
    • + *
    • {@linkplain StableValue#orElseThrow() {@code orElseThrow()}}
    • + *
    + *

    + * By invoking the method {@linkplain StableValue#orElse(Object) orElse(other)}. The + * method will return the underlying data if initialized, otherwise it will return + * the provided {@code other} value: + * + * {@snippet lang=java : + * StableValue stableValue = StableValue.of(); + * // ... + * int val = stableValue.orElse(13); // The underlying data if set, otherwise 13 + * } + * Another way is by means of the {@linkplain StableValue#orElseThrow() orElseThrow()} + * method which will retrieve the underlying data if initialized or throw + * {@linkplain NoSuchElementException} if the underlying data was not initialized: + * + * {@snippet lang=java : + * StableValue stableValue = StableValue.of(); + * // ... + * int val = stableValue.orElseThrow(); // The underlying data, else throws + * } + * + *

    Determining the presence of underlying data

    + * The presence of underlying data can be examined using the method + * {@linkplain StableValue#isSet() isSet()}. The method will return {@code true} if the + * underlying data is initialized, otherwise it will return {@code false}. + * + *

    Stable Functions

    + * There are three stable functions: + *
      + *
    • Stable supplier
    • + *
    • Stable int function
    • + *
    • Stable function
    • + *
    + *

    + * All the stable functions guarantee the provided {@code original} function is invoked + * successfully at most once per valid input even in a multithreaded environment. + *

    + * A stable supplier allows a more convenient construct compared to a + * {@linkplain StableValue} in the sense that a field declaration can also specify how + * the underlying data of a stable value is to be initialized, but without actually + * initializing its underlying data upfront: * {@snippet lang = java: * class Component { * @@ -94,15 +197,16 @@ * } *} * This also allows the stable supplier to be accessed directly, without going through - * an accessor method like {@code getLogger()}. - *

    + * an accessor method like {@code getLogger()} in the first example of this class. + *

    * A stable int function stores values in an array of stable values where - * the underlying values are computed the first time a particular input value is seen. + * the underlying values are computed the first time a particular input value is provided. * When the stable int function is first created -- - * via the {@linkplain StableValue#ofIntFunction(int, IntFunction)} factory -- the input - * range (i.e. {@code [0, size)}) is specified together with an original - * {@linkplain IntFunction} which is invoked at most once per input value. In effect, - * the stable int function will act like a cache for the original {@code IntFunction}: + * via the {@linkplain StableValue#ofIntFunction(int, IntFunction) + * StableValue.ofIntFunction()} factory -- the input range (i.e. {@code [0, size)}) is + * specified together with an original {@linkplain IntFunction} which is invoked + * at most once per input value. In effect, the stable int function will act like a cache + * for the original {@code IntFunction}: * {@snippet lang = java: * class SqrtUtil { * @@ -115,30 +219,14 @@ * * } *} - *

    - * a stable list is similar to a stable int function, but provides a full - * implementation of an immutable {@linkplain List}. This is useful when interacting with - * collection based methods. Here is an example how a stable list can be used to hold - * a pool of order controllers: - * {@snippet lang = java: - * class Application { - * static final List ORDER_CONTROLLERS = - * StableValue.ofList(POOL_SIZE, OrderController::new); - * - * public static OrderController orderController() { - * long index = Thread.currentThread().threadId() % POOL_SIZE; - * return ORDER_CONTROLLERS.get((int) index); - * } - * } - * } - *

    + *

    * A stable function stores values in an array of stable values where - * the underlying values are computed the first time a particular input value is seen. + * the underlying values are computed the first time a particular input value is provided. * When the stable function is first created -- - * via the {@linkplain StableValue#ofFunction(Set, Function)} factory -- the input set - * is specified together with an original {@linkplain Function} which is invoked - * at most once per input value. In effect, the stable function will act like a - * cache for the original {@code Function}: + * via the {@linkplain StableValue#ofFunction(Set, Function) StableValue.ofFunction()} + * factory -- the input set is specified together with an original {@linkplain Function} + * which is invoked at most once per input value. In effect, the stable function will act + * like a cache for the original {@code Function}: * {@snippet lang = java: * class SqrtUtil { * @@ -151,10 +239,37 @@ * * } *} - *

    - * a stable map is similar to a stable function, but provides a full + * + *

    Stable Collections

    + * There are two stable collections: + *
      + *
    • Stable list
    • + *
    • Stable map
    • + *
    + *

    + * All the stable collections guarantee the provided {@code original} function is + * invoked successfully at most once per valid input even in a multithreaded environment. + *

    + * A stable list is similar to a stable int function, but provides a full + * implementation of an immutable {@linkplain List}. This is useful when interacting with + * collection-based methods. Here is an example how a stable list can be used to hold + * a pool of order controllers: + * {@snippet lang = java: + * class Application { + * static final List ORDER_CONTROLLERS = + * StableValue.ofList(POOL_SIZE, OrderController::new); + * + * public static OrderController orderController() { + * long index = Thread.currentThread().threadId() % POOL_SIZE; + * return ORDER_CONTROLLERS.get((int) index); + * } + * } + * } + *

    + * A stable map is similar to a stable function, but provides a full * implementation of an immutable {@linkplain Map}. This is useful when interacting with - * collections. Here is how a stable map can be used as a cache for square root values: + * collection-based methods. Here is how a stable map can be used as a cache for + * square root values: * {@snippet lang = java: * class SqrtUtil { * @@ -169,7 +284,7 @@ *} * *

    Memory Consistency Properties

    - * Actions on a presumptive underlying data in a thread prior to calling a method that + * Actions on the presumptive underlying data in a thread prior to calling a method that * sets the underlying data are seen by any other thread that observes a * set underlying data. *

    @@ -177,14 +292,21 @@ * with a StableValue's underlying data (e.g. via {@link StableValue#trySet} or * {@link StableValue#orElseThrow()}) forms a * happens-before - * relation between any other attempt to interact with the StableValue's underlying data. + * relation between any other action of attempting to interact with the + * StableValue's underlying data. The same happens-before guarantees extend to + * stable functions and stable collections; any action of attempting to interact via a + * valid input value {@code I} happens-before any subsequent action of attempting + * to interact via a valid input value {@code J} if, and only if, {@code I} and {@code J} + * {@linkplain Object#equals(Object) equals() } *

    * Except for a StableValue's underlying data itself, all method parameters must be * non-null or a {@link NullPointerException} will be thrown. * - * @implSpec Implementing classes are free to synchronize on {@code this} and consequently, - * care should be taken whenever (directly or indirectly) synchronizing on - * a StableValue. Failure to do this may lead to deadlock. + * @implSpec Implementing classes of {@linkplain StableValue} are free to synchronize on + * {@code this} and consequently, care should be taken whenever + * (directly or indirectly) synchronizing on a StableValue. Failure to do this + * may lead to deadlock. Stable functional constructs and collections on + * the other hand are guaranteed not to not synchronize on {@code this}. * * @implNote Instance fields explicitly declared as StableValue or one-dimensional arrays * thereof are eligible for certain JVM optimizations where normal instance @@ -278,7 +400,8 @@ public sealed interface StableValue * Sets the underlying data to the provided {@code underlyingData}, or, * if already set, throws {@link IllegalStateException}} *

    - * When this method returns (or throws an Exception), the underlying data is always set. + * When this method returns (or throws an Exception), the underlying data is + * always set. * * @param underlyingData to set * @throws IllegalStateException if the underlying data is already set @@ -316,11 +439,11 @@ static StableValue of() { * computing thread. *

    * If the {@code original} Supplier invokes the returned Supplier recursively, - * a StackOverflowError will be thrown when the returned + * a {@linkplain StackOverflowError} will be thrown when the returned * Supplier's {@linkplain Supplier#get() Supplier::get} method is invoked. *

    * If the provided {@code original} supplier throws an exception, it is relayed - * to the initial caller. + * to the initial caller and no value is recorded. * * @param original supplier used to compute a memoized value * @param the type of results supplied by the returned supplier @@ -343,12 +466,12 @@ static Supplier ofSupplier(Supplier original) { * an exception is thrown by the computing thread. *

    * If the {@code original} IntFunction invokes the returned IntFunction recursively - * for a particular input value, a StackOverflowError will be thrown when the returned - * IntFunction's {@linkplain IntFunction#apply(int) IntFunction::apply} method is - * invoked. + * for a particular input value, a {@linkplain StackOverflowError} will be thrown when + * the returned IntFunction's {@linkplain IntFunction#apply(int) IntFunction::apply} + * method is invoked. *

    * If the provided {@code original} IntFunction throws an exception, it is relayed - * to the initial caller. + * to the initial caller and no value is recorded. * * @param size the size of the allowed inputs in {@code [0, size)} * @param original IntFunction used to compute a memoized value @@ -367,8 +490,7 @@ static IntFunction ofIntFunction(int size, * {@return a new stable, thread-safe, caching, lazily computed {@link Function} * that, for each allowed input in the given set of {@code inputs}, records the * values of the provided {@code original} Function upon being first accessed via - * {@linkplain Function#apply(Object) Function::apply}, or optionally via background - * threads created from the provided {@code factory} (if non-null)} + * {@linkplain Function#apply(Object) Function::apply}} *

    * The provided {@code original} Function is guaranteed to be successfully invoked * at most once per allowed input, even in a multi-threaded environment. Competing @@ -377,16 +499,12 @@ static IntFunction ofIntFunction(int size, * an exception is thrown by the computing thread. *

    * If the {@code original} Function invokes the returned Function recursively - * for a particular input value, a StackOverflowError will be thrown when the returned - * Function's {@linkplain Function#apply(Object) Function::apply} method is invoked. + * for a particular input value, a {@linkplain StackOverflowError} will be thrown when + * the returned Function's {@linkplain Function#apply(Object) Function::apply} method + * is invoked. *

    * If the provided {@code original} Function throws an exception, it is relayed - * to the initial caller. If the memoized Function is computed by a background - * thread, exceptions from the provided {@code original} Function will be relayed to - * the background thread's {@linkplain Thread#getUncaughtExceptionHandler() uncaught - * exception handler}. - *

    - * The order in which background threads are started is unspecified. + * to the initial caller and no value is recorded. * * @param inputs the set of allowed input values * @param original Function used to compute a memoized value @@ -412,11 +530,11 @@ static Function ofFunction(Set inputs, * is computed or an exception is thrown by the computing thread. *

    * If the {@code mapper} IntFunction invokes the returned IntFunction recursively - * for a particular index, a StackOverflowError will be thrown when the returned - * List's {@linkplain List#get(int) List::get} method is invoked. + * for a particular index, a {@linkplain StackOverflowError} will be thrown when the + * returned List's {@linkplain List#get(int) List::get} method is invoked. *

    * If the provided {@code mapper} IntFunction throws an exception, it is relayed - * to the initial caller and no element is computed. + * to the initial caller and no element is recorded. *

    * The returned List is not {@link Serializable}. * @@ -434,7 +552,7 @@ static List ofList(int size, IntFunction mapper) { /** * {@return a shallowly immutable, lazy, stable Map of the provided {@code keys} - * where the associated values of the maps are lazily computed vio the provided + * where the associated values of the maps are lazily computed via the provided * {@code mapper} whenever a value is first accessed (directly or indirectly), for * example via {@linkplain Map#get(Object) Map::get}} *

    @@ -444,11 +562,11 @@ static List ofList(int size, IntFunction mapper) { * an associated value is computed or an exception is thrown by the computing thread. *

    * If the {@code mapper} Function invokes the returned Map recursively - * for a particular key, a StackOverflowError will be thrown when the returned - * Map's {@linkplain Map#get(Object) Map::get}} method is invoked. + * for a particular key, a {@linkplain StackOverflowError} will be thrown when the + * returned Map's {@linkplain Map#get(Object) Map::get}} method is invoked. *

    * If the provided {@code mapper} Function throws an exception, it is relayed - * to the initial caller and no value is computed. + * to the initial caller and no value is recorded. *

    * The returned Map is not {@link Serializable}. * From 58b808a70b5570bf4441d97ee91bef28670baa20 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 21 Oct 2024 14:11:16 +0200 Subject: [PATCH 140/327] Update docs --- .../share/classes/java/lang/StableValue.java | 94 ++++++++++--------- .../internal/lang/stable/StableValueImpl.java | 9 ++ 2 files changed, 60 insertions(+), 43 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 7572970e46b29..313bae0efdbae 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -127,8 +127,8 @@ * {@snippet lang=java : * StableValue stableValue = StableValue.of(); * // ... - * // Throws IllegalStateException if the underlying data is already initialized - * int val = stableValue.setOrThrow(42); // 42 + * // Throws IllegalStateException if the underlying data was already initialized + * stableValue.setOrThrow(42); * } *

    * Finally, the underlying data can be initialized via @@ -145,23 +145,23 @@ *

  • {@linkplain StableValue#orElseThrow() {@code orElseThrow()}}
  • * *

    - * By invoking the method {@linkplain StableValue#orElse(Object) orElse(other)}. The - * method will return the underlying data if initialized, otherwise it will return - * the provided {@code other} value: + * The method {@linkplain StableValue#orElse(Object) orElse(other)} will return + * the underlying data if initialized, otherwise it will return the provided + * {@code other} value: * * {@snippet lang=java : * StableValue stableValue = StableValue.of(); * // ... * int val = stableValue.orElse(13); // The underlying data if set, otherwise 13 * } - * Another way is by means of the {@linkplain StableValue#orElseThrow() orElseThrow()} - * method which will retrieve the underlying data if initialized or throw + * The other method {@linkplain StableValue#orElseThrow() orElseThrow()} will retrieve + * the underlying data if initialized or throw * {@linkplain NoSuchElementException} if the underlying data was not initialized: * * {@snippet lang=java : * StableValue stableValue = StableValue.of(); * // ... - * int val = stableValue.orElseThrow(); // The underlying data, else throws + * int val = stableValue.orElseThrow(); // The underlying data if set, else throws * } * *

    Determining the presence of underlying data

    @@ -188,7 +188,7 @@ * class Component { * * private final Supplier logger = - * StableValue.ofSupplier( () -> Logger.getLogger("org.app.Component") ); + * StableValue.ofSupplier( () -> Logger.getLogger("org.app.Component") ); * * void process() { * logger.get().info("Process started"); @@ -197,10 +197,12 @@ * } *} * This also allows the stable supplier to be accessed directly, without going through - * an accessor method like {@code getLogger()} in the first example of this class. + * an accessor method like {@code getLogger()} in the + * {@linkplain StableValue##stable-holder initial example} of this class. *

    * A stable int function stores values in an array of stable values where - * the underlying values are computed the first time a particular input value is provided. + * the elements` underlying data are computed the first time a particular input value + * is provided. * When the stable int function is first created -- * via the {@linkplain StableValue#ofIntFunction(int, IntFunction) * StableValue.ofIntFunction()} factory -- the input range (i.e. {@code [0, size)}) is @@ -211,7 +213,7 @@ * class SqrtUtil { * * private static final IntFunction SQRT = - * StableValue.ofIntFunction(10, Math::sqrt); + * StableValue.ofIntFunction(10, Math::sqrt); * * double sqrt9() { * return SQRT.apply(9); // Constant folds to 3.0 @@ -221,7 +223,8 @@ *} *

    * A stable function stores values in an array of stable values where - * the underlying values are computed the first time a particular input value is provided. + * the elements`s underlying data are computed the first time a particular input value + * is provided. * When the stable function is first created -- * via the {@linkplain StableValue#ofFunction(Set, Function) StableValue.ofFunction()} * factory -- the input set is specified together with an original {@linkplain Function} @@ -231,7 +234,7 @@ * class SqrtUtil { * * private static final Function SQRT = - * StableValue.ofFunction(Set.of(1, 2, 4, 8, 16, 32), Math::sqrt); + * StableValue.ofFunction(Set.of(1, 2, 4, 8, 16, 32), Math::sqrt); * * double sqrt16() { * return SQRT.apply(16); // Constant folds to 4.0 @@ -266,15 +269,18 @@ * } * } *

    + * Note: In the example above, there is a constructor in {@code OrderController} that + * takes an {@code int} parameter. + *

    * A stable map is similar to a stable function, but provides a full * implementation of an immutable {@linkplain Map}. This is useful when interacting with * collection-based methods. Here is how a stable map can be used as a cache for - * square root values: + * square roots for certain input values given at creation: * {@snippet lang = java: * class SqrtUtil { * * private static final Map SQRT = - * StableValue.ofMap(Set.of(1, 2, 4, 8, 16, 32), Math::sqrt); + * StableValue.ofMap(Set.of(1, 2, 4, 8, 16, 32), Math::sqrt); * * double sqrt16() { * return SQRT.apply(16); // Constant folds to 4.0 @@ -284,9 +290,9 @@ *} * *

    Memory Consistency Properties

    - * Actions on the presumptive underlying data in a thread prior to calling a method that - * sets the underlying data are seen by any other thread that observes a - * set underlying data. + * Actions on presumptive underlying data in a thread prior to calling a method that + * sets the underlying data are seen by any other thread that observes the + * underlying data. *

    * More generally, the action of attempting to interact (i.e. via load or store operations) * with a StableValue's underlying data (e.g. via {@link StableValue#trySet} or @@ -297,7 +303,7 @@ * stable functions and stable collections; any action of attempting to interact via a * valid input value {@code I} happens-before any subsequent action of attempting * to interact via a valid input value {@code J} if, and only if, {@code I} and {@code J} - * {@linkplain Object#equals(Object) equals() } + * {@linkplain Object#equals(Object) equals()}. *

    * Except for a StableValue's underlying data itself, all method parameters must be * non-null or a {@link NullPointerException} will be thrown. @@ -305,8 +311,8 @@ * @implSpec Implementing classes of {@linkplain StableValue} are free to synchronize on * {@code this} and consequently, care should be taken whenever * (directly or indirectly) synchronizing on a StableValue. Failure to do this - * may lead to deadlock. Stable functional constructs and collections on - * the other hand are guaranteed not to not synchronize on {@code this}. + * may lead to deadlock. Stable functions and collections on the other hand are + * guaranteed not to synchronize on {@code this}. * * @implNote Instance fields explicitly declared as StableValue or one-dimensional arrays * thereof are eligible for certain JVM optimizations where normal instance @@ -370,9 +376,11 @@ public sealed interface StableValue *

     {@code
          * Value witness = stable.computeIfUnset(Value::new);
          * }
    + *

    + * When this method returns successfully, the underlying data is always set. * * @implSpec The implementation logic is equivalent to the following steps for this - * {@code stable}: + * {@code stable}: * *

     {@code
          * if (stable.isSet()) {
    @@ -398,7 +406,7 @@ public sealed interface StableValue
     
         /**
          * Sets the underlying data to the provided {@code underlyingData}, or,
    -     * if already set, throws {@link IllegalStateException}}
    +     * if already set, throws {@link IllegalStateException}.
          * 

    * When this method returns (or throws an Exception), the underlying data is * always set. @@ -406,12 +414,7 @@ public sealed interface StableValue * @param underlyingData to set * @throws IllegalStateException if the underlying data is already set */ - default void setOrThrow(T underlyingData) { - if (!trySet(underlyingData)) { - throw new IllegalStateException("Cannot set the underlying data to " + underlyingData + - " because the underlying data is already set: " + this); - } - } + void setOrThrow(T underlyingData); // Factories @@ -430,7 +433,7 @@ static StableValue of() { * {@return a new stable, thread-safe, caching, lazily computed * {@linkplain Supplier supplier} that records the value of the provided * {@code original} supplier upon being first accessed via - * {@linkplain Supplier#get() Supplier::get}} + * the returned supplier's {@linkplain Supplier#get() Supplier::get get()} method} *

    * The provided {@code original} supplier is guaranteed to be successfully invoked * at most once even in a multi-threaded environment. Competing threads invoking the @@ -445,7 +448,7 @@ static StableValue of() { * If the provided {@code original} supplier throws an exception, it is relayed * to the initial caller and no value is recorded. * - * @param original supplier used to compute a memoized value + * @param original supplier used to compute a cached value * @param the type of results supplied by the returned supplier */ static Supplier ofSupplier(Supplier original) { @@ -457,7 +460,7 @@ static Supplier ofSupplier(Supplier original) { * {@return a new stable, thread-safe, caching, lazily computed * {@link IntFunction } that, for each allowed input, records the values of the * provided {@code original} IntFunction upon being first accessed via - * {@linkplain IntFunction#apply(int) IntFunction::apply}} + * the returned IntFunction's {@linkplain IntFunction#apply(int) apply()} method} *

    * The provided {@code original} IntFunction is guaranteed to be successfully invoked * at most once per allowed input, even in a multi-threaded environment. Competing @@ -474,7 +477,7 @@ static Supplier ofSupplier(Supplier original) { * to the initial caller and no value is recorded. * * @param size the size of the allowed inputs in {@code [0, size)} - * @param original IntFunction used to compute a memoized value + * @param original IntFunction used to compute cached values * @param the type of results delivered by the returned IntFunction */ static IntFunction ofIntFunction(int size, @@ -490,7 +493,7 @@ static IntFunction ofIntFunction(int size, * {@return a new stable, thread-safe, caching, lazily computed {@link Function} * that, for each allowed input in the given set of {@code inputs}, records the * values of the provided {@code original} Function upon being first accessed via - * {@linkplain Function#apply(Object) Function::apply}} + * the returned Function's {@linkplain Function#apply(Object) apply()} method} *

    * The provided {@code original} Function is guaranteed to be successfully invoked * at most once per allowed input, even in a multi-threaded environment. Competing @@ -507,7 +510,7 @@ static IntFunction ofIntFunction(int size, * to the initial caller and no value is recorded. * * @param inputs the set of allowed input values - * @param original Function used to compute a memoized value + * @param original Function used to compute cached values * @param the type of the input to the returned Function * @param the type of results delivered by the returned Function */ @@ -536,13 +539,16 @@ static Function ofFunction(Set inputs, * If the provided {@code mapper} IntFunction throws an exception, it is relayed * to the initial caller and no element is recorded. *

    - * The returned List is not {@link Serializable}. + * The returned List is not {@link Serializable} as this would require the provided + * {@code mapper} to be {@link Serializable} as well which would create security + * concerns. * * @param size the size of the returned list - * @param mapper to invoke whenever an element is first accessed (may return null) - * @param the type of elements in the returned list + * @param mapper to invoke whenever an element is first accessed + * (may return {@code null}) + * @param the type of elements in the returned list */ - static List ofList(int size, IntFunction mapper) { + static List ofList(int size, IntFunction mapper) { if (size < 0) { throw new IllegalArgumentException(); } @@ -551,7 +557,7 @@ static List ofList(int size, IntFunction mapper) { } /** - * {@return a shallowly immutable, lazy, stable Map of the provided {@code keys} + * {@return a shallowly immutable, lazy, stable Map with the provided {@code keys} * where the associated values of the maps are lazily computed via the provided * {@code mapper} whenever a value is first accessed (directly or indirectly), for * example via {@linkplain Map#get(Object) Map::get}} @@ -568,11 +574,13 @@ static List ofList(int size, IntFunction mapper) { * If the provided {@code mapper} Function throws an exception, it is relayed * to the initial caller and no value is recorded. *

    - * The returned Map is not {@link Serializable}. + * The returned Map is not {@link Serializable} as this would require the provided + * {@code mapper} to be {@link Serializable} as well which would create security + * concerns. * * @param keys the keys in the returned map * @param mapper to invoke whenever an associated value is first accessed - * (may return null) + * (may return {@code null}) * @param the type of keys maintained by the returned map * @param the type of mapped values in the returned map */ 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 e757224647551..f3aa0680953f1 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 @@ -80,6 +80,15 @@ public boolean trySet(T underlyingData) { } } + @ForceInline + @Override + public void setOrThrow(T underlyingData) { + if (!trySet(underlyingData)) { + throw new IllegalStateException("Cannot set the underlying data to " + underlyingData + + " because the underlying data is already set: " + this); + } + } + @ForceInline @Override public T orElseThrow() { From d929bc433fbea06affe0ebc64b3af5b8fa644108 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Fri, 25 Oct 2024 09:56:51 +0200 Subject: [PATCH 141/327] Improve javadocs --- .../share/classes/java/lang/StableValue.java | 314 ++++++++++-------- 1 file changed, 173 insertions(+), 141 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 313bae0efdbae..e1ff299f8581e 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -41,14 +41,76 @@ import java.util.function.Supplier; /** - * Stable values are objects that represent immutable data and are treated as constants - * by the JVM, enabling the same performance optimizations that are possible by marking - * a field final. Yet, stable values offer greater flexibility as to the timing of - * initialization compared to final fields. + * A value that has the same performance as a final field but can be freely initialized. + *

    + * In the Java programming language, a field could either be immutable (i.e. declared + * {@code final}) or mutable (i.e. not declared {@code final}): + *

    + * Immutable fields must be set eagerly either by the constructor + * (for instance fields) or during class initialization (for {@code static} fields). + * Moreover, the order in which {@code final} fields are initialized is determined by + * the textual order in which the fields are declared. A {@code final} field is + * initialized exactly once by the same thread that instantiated an object or that + * first loaded a class. Because the field requires initialization upfront, the use + * of them often increases the application startup time. + * As the field can never change after being initialized, the JVM is able to reason about + * constantness and can trust a {@code final} is stable and the field may therefor be + * eligible for certain JVM optimization such as constant folding. + *

    + * Mutable fields, on the other hand, can be initialized at any time, by any + * thread and may subsequently be updated arbitrarily. A mutable field can be lazily + * initialized and can be used to reduce application startup time. However, as the field + * can change at any time, the JVM is not able to reason about constantness and cannot + * trust the value remains stable. Hence, it is not able to perform optimizations like + * constant folding. + * + *

    Stable Values

    + * Stable values are objects that represent deferred immutable data and are treated + * as constants by the JVM, enabling the same performance optimizations that are + * possible by marking a field final. Yet, stable values offer greater flexibility as to + * the timing of initialization compared to {@code final} fields: + *
    + *
    + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
    Storage kind comparison
    Storage kind#UpdatesUpdate locationConstant foldingConcurrent updated
    Mutable (non-{@code final})[0, ∞)AnywhereNoYes
    Stable (StableValue)[0, 1]AnywhereYes, after updateYes, by winner
    Immutable ({@code final})1Constructor or static initializerYesNo
    + *
    *

    * There are three categories of stable values and six variants of stable value objects : *

      - *
    • {@linkplain StableValue##stable-holder Stable Holder} + *
    • {@linkplain StableValue##stable-value Stable Value} *
        *
      • Stable value: {@linkplain StableValue {@code StableValue}}
      • *
      @@ -68,106 +130,94 @@ *
    • *
    * - *

    Stable Holder

    - * A stable value is of type {@linkplain StableValue {@code StableValue}} and - * is a holder of underlying data of type {@code T}. It can be used to defer + *

    Stable Value

    + * A stable value is an object of type {@linkplain StableValue {@code StableValue}} + * and is a holder of underlying data of type {@code T}. It can be used to defer * initialization of its underlying data while retaining the same performance as if - * a field of type {@code T} was declared {@code final}: - * {@snippet lang = java: - * class Component { - * - * private final StableValue logger = StableValue.of(); - * - * Logger getLogger() { - * return logger.computeIfUnset( () -> Logger.getLogger("org.app.Component") ); - * } - * - * void process() { - * getLogger().info("Process started"); - * // ... - * } - * - * } - *} + * a field of type {@code T} was declared {@code final}. Once the underlying data has + * been initialized, the stable value usually acts as a constant for the rest of the + * program's execution. In effect, a stable value provides a way of achieving deferred + * immutability. *

    - * A {@linkplain StableValue} is mainly intended to be a member of a holding class (as - * shown in the example above) and is usually neither exposed directly via accessors nor - * passed as a method parameter. - * - *

    Initializing the underlying data

    - * A {@linkplain StableValue}'s underlying data can be initialized to a {@code value} of - * type {@code T} via three instance methods: - *
      - *
    • {@linkplain StableValue#trySet(Object) {@code trySet(T value)}}
    • - *
    • {@linkplain StableValue#setOrThrow(Object) {@code setOrThrow(T value)}}
    • - *
    • {@linkplain StableValue#computeIfUnset(Supplier) {@code computeIfUnset(Supplier} supplier)}
    • - *
    - *

    - * Here is an example of how the underlying data can be initialized imperatively - * using a pre-computed value (here {@code 42}) using the method + * A stable value can be used imperatively or functionally as outlined + * hereunder. + *

    Imperative use

    + * Imperative use often entails more low-level usage, interacting directly with the + * underlying data. Here is an example of how the underlying data can be initialized + * imperatively using a pre-computed value (here {@code 42}) using the method * {@linkplain StableValue#trySet(Object) trySet()}. The method will return {@code true} * if the underlying data was indeed initialized, or it will return {@code false} if * the underlying data was already initialized: * * {@snippet lang=java : + * // Creates a new stable value with no underlying data * StableValue stableValue = StableValue.of(); - * // ... + * // ... logic that may or may not set the underlying data of stableValue * if (stableValue.trySet(42)) { * System.out.println("The underlying data was initialized to 42."); * } else { - * System.out.println("The value was already initialized."); + * System.out.println("The value was already initialized to " + stableValue.orElseThrow()); * } * } + * Note that the underlying data can only be initialized at most once, even when several + * threads are racing to initialize the underlying data. Only one thread is selected as + * the winner. *

    - * A similar way to initialize the underlying data imperatively using a pre-computed - * value, is to invoke the method {@linkplain StableValue#setOrThrow(Object) - * setOrThrow()}. The method will initialize the underlying data if uninitialized, - * otherwise it will throw {@linkplain IllegalStateException}: + * Here is another example where the underlying data of a stable value is retrieved + * using the method {@linkplain StableValue#orElseThrow() orElseThrow()}. The method will + * throw {@linkplain NoSuchElementException} if no underlying data exists: * * {@snippet lang=java : * StableValue stableValue = StableValue.of(); - * // ... - * // Throws IllegalStateException if the underlying data was already initialized - * stableValue.setOrThrow(42); - * } - *

    - * Finally, the underlying data can be initialized via - * {@linkplain StableValue#computeIfUnset(Supplier) {@code computeIfUnset(Supplier} supplier)} - * as shown in the {@linkplain StableValue##stable-holder initial example} in this class. - * It should be noted that a {@linkplain StableValue} guarantees the provided - * {@code supplier} is invoked at most once if it did not throw an exception even in - * a multithreaded environment. - * - *

    Retrieving the underlying data

    - * The underlying data of a stable value can be retrieved using two instance methods: - *
      - *
    • {@linkplain StableValue#orElse(Object) {@code orElse(T other)}}
    • - *
    • {@linkplain StableValue#orElseThrow() {@code orElseThrow()}}
    • - *
    - *

    - * The method {@linkplain StableValue#orElse(Object) orElse(other)} will return - * the underlying data if initialized, otherwise it will return the provided - * {@code other} value: - * - * {@snippet lang=java : - * StableValue stableValue = StableValue.of(); - * // ... - * int val = stableValue.orElse(13); // The underlying data if set, otherwise 13 - * } - * The other method {@linkplain StableValue#orElseThrow() orElseThrow()} will retrieve - * the underlying data if initialized or throw - * {@linkplain NoSuchElementException} if the underlying data was not initialized: - * - * {@snippet lang=java : - * StableValue stableValue = StableValue.of(); - * // ... + * // ... logic that may set the underlying data of stableValue * int val = stableValue.orElseThrow(); // The underlying data if set, else throws * } - * - *

    Determining the presence of underlying data

    + *

    * The presence of underlying data can be examined using the method * {@linkplain StableValue#isSet() isSet()}. The method will return {@code true} if the * underlying data is initialized, otherwise it will return {@code false}. + + *

    Functional use

    + * Functional use of a stable value entails providing a + * {@linkplain Supplier {@code Supplier}} to the instance method + * {@linkplain StableValue#computeIfUnset(Supplier) computeIfUnset()} whereby the + * provided supplier is automatically invoked if there is no underlying data as shown in + * this example: + * + * {@snippet lang = java: + * class Component { + * + * private final StableValue logger = StableValue.of(); + * + * Logger getLogger() { + * return logger.computeIfUnset( () -> Logger.getLogger("org.app.Component") ); + * } + * + * void process() { + * getLogger().info("Process started"); + * // ... + * } + * + * } + *} + * The {@code getLogger()} method calls {@code logger.computeIfUnset()} on the + * stable value to retrieve its underlying data. If the stable value is unset, then + * {@code computeIfUnset()} initializes the underlying data, causing the stable value to + * become set; the underlying data is then returned to the client. In other words, + * {@code computeIfUnset()} guarantees that a stable value's underlying data is + * initialized before it is used. + *

    + * Even though the stable value, once set, is immutable, its underlying data is not + * required to be initialized upfront. Rather, it can be initialized on demand. Furthermore, + * {@code computeIfUnset()} guarantees that the lambda expression provided is evaluated + * only once, even when {@code logger.computeIfUnset()} is invoked concurrently. + * This property is crucial as evaluation of the lambda expression may have side effects, + * e.g., the call above to {@code Logger.getLogger()} may result in storage resources + * being prepared. + *

    + * A {@linkplain StableValue} is mainly intended to be a member of a holding class (as + * shown in the examples above) and is usually neither exposed directly via accessors nor + * passed as a method parameter. * *

    Stable Functions

    * There are three stable functions: @@ -198,7 +248,7 @@ *} * This also allows the stable supplier to be accessed directly, without going through * an accessor method like {@code getLogger()} in the - * {@linkplain StableValue##stable-holder initial example} of this class. + * {@linkplain StableValue##functional-use functional example} above. *

    * A stable int function stores values in an array of stable values where * the elements` underlying data are computed the first time a particular input value @@ -304,7 +354,8 @@ * valid input value {@code I} happens-before any subsequent action of attempting * to interact via a valid input value {@code J} if, and only if, {@code I} and {@code J} * {@linkplain Object#equals(Object) equals()}. - *

    + * + *

    Miscellaneous

    * Except for a StableValue's underlying data itself, all method parameters must be * non-null or a {@link NullPointerException} will be thrown. * @@ -323,7 +374,7 @@ * * @param type of the underlying data * - * @since 24 + * @since 25 */ @PreviewFeature(feature = PreviewFeature.Feature.STABLE_VALUES) public sealed interface StableValue @@ -333,7 +384,7 @@ public sealed interface StableValue /** * {@return {@code true} if the underlying data was set to the provided - * {@code underlyingData}, otherwise returns {@code false}} + * {@code underlyingData}, otherwise returns {@code false}} *

    * When this method returns, the underlying data is always set. * @@ -342,8 +393,9 @@ public sealed interface StableValue boolean trySet(T underlyingData); /** - * {@return the underlying data if set, otherwise return the - * {@code other} value} + * {@return the underlying data if set, otherwise return the provided + * {@code other} value} + * * * @param other to return if the underlying data is not set */ @@ -363,8 +415,8 @@ public sealed interface StableValue /** * {@return the underlying data if set, otherwise attempts to compute and set - * new underlying data using the provided {@code supplier}, returning the - * newly set underlying data} + * ew underlying data using the provided {@code supplier}, returning the + * newly set underlying data} *

    * The provided {@code supplier} is guaranteed to be invoked at most once if it * completes without throwing an exception. @@ -397,8 +449,6 @@ public sealed interface StableValue * {@code supplier} throws an exception. * * @param supplier to be used for computing the underlying data - * @throws StackOverflowError if the provided {@code supplier} recursively - * invokes the provided {@code supplier} upon being invoked. */ T computeIfUnset(Supplier supplier); @@ -420,8 +470,8 @@ public sealed interface StableValue /** * {@return a new thin, atomic, thread-safe, set-at-most-once, {@linkplain StableValue} - * with no underlying data eligible for certain JVM optimizations once the - * underlying data is set} + * with no underlying data eligible for certain JVM optimizations once the + * underlying data is set} * * @param type of the underlying data */ @@ -431,19 +481,15 @@ static StableValue of() { /** * {@return a new stable, thread-safe, caching, lazily computed - * {@linkplain Supplier supplier} that records the value of the provided - * {@code original} supplier upon being first accessed via - * the returned supplier's {@linkplain Supplier#get() Supplier::get get()} method} + * {@linkplain Supplier supplier} that records the value of the provided + * {@code original} supplier upon being first accessed via + * the returned supplier's {@linkplain Supplier#get() get()} method} *

    * The provided {@code original} supplier is guaranteed to be successfully invoked * at most once even in a multi-threaded environment. Competing threads invoking the - * {@linkplain Supplier#get() Supplier::get} method when a value is already under - * computation will block until a value is computed or an exception is thrown by the - * computing thread. - *

    - * If the {@code original} Supplier invokes the returned Supplier recursively, - * a {@linkplain StackOverflowError} will be thrown when the returned - * Supplier's {@linkplain Supplier#get() Supplier::get} method is invoked. + * returned supplier's {@linkplain Supplier#get() get()} method when a value is + * already under computation will block until a value is computed or an exception is + * thrown by the computing thread. *

    * If the provided {@code original} supplier throws an exception, it is relayed * to the initial caller and no value is recorded. @@ -458,20 +504,17 @@ static Supplier ofSupplier(Supplier original) { /** * {@return a new stable, thread-safe, caching, lazily computed - * {@link IntFunction } that, for each allowed input, records the values of the - * provided {@code original} IntFunction upon being first accessed via - * the returned IntFunction's {@linkplain IntFunction#apply(int) apply()} method} + * {@link IntFunction } that, for each allowed input, records the values of + * the provided {@code original} IntFunction upon being first accessed via + * the returned IntFunction's {@linkplain IntFunction#apply(int) apply()} + * method} *

    * The provided {@code original} IntFunction is guaranteed to be successfully invoked * at most once per allowed input, even in a multi-threaded environment. Competing - * threads invoking the {@linkplain IntFunction#apply(int) IntFunction::apply} method - * when a value is already under computation will block until a value is computed or - * an exception is thrown by the computing thread. - *

    - * If the {@code original} IntFunction invokes the returned IntFunction recursively - * for a particular input value, a {@linkplain StackOverflowError} will be thrown when - * the returned IntFunction's {@linkplain IntFunction#apply(int) IntFunction::apply} - * method is invoked. + * threads invoking the returned IntFunction's + * {@linkplain IntFunction#apply(int) apply()} method when a value is already under + * computation will block until a value is computed or an exception is thrown by + * the computing thread. *

    * If the provided {@code original} IntFunction throws an exception, it is relayed * to the initial caller and no value is recorded. @@ -491,20 +534,16 @@ static IntFunction ofIntFunction(int size, /** * {@return a new stable, thread-safe, caching, lazily computed {@link Function} - * that, for each allowed input in the given set of {@code inputs}, records the - * values of the provided {@code original} Function upon being first accessed via - * the returned Function's {@linkplain Function#apply(Object) apply()} method} + * that, for each allowed input in the given set of {@code inputs}, records + * the values of the provided {@code original} Function upon being first + * accessed via the returned Function's + * {@linkplain Function#apply(Object) apply()} method} *

    * The provided {@code original} Function is guaranteed to be successfully invoked * at most once per allowed input, even in a multi-threaded environment. Competing - * threads invoking the {@linkplain Function#apply(Object) Function::apply} method - * when a value is already under computation will block until a value is computed or - * an exception is thrown by the computing thread. - *

    - * If the {@code original} Function invokes the returned Function recursively - * for a particular input value, a {@linkplain StackOverflowError} will be thrown when - * the returned Function's {@linkplain Function#apply(Object) Function::apply} method - * is invoked. + * threads invoking the returned function's {@linkplain Function#apply(Object) apply()} + * method when a value is already under computation will block until a value is + * computed or an exception is thrown by the computing thread. *

    * If the provided {@code original} Function throws an exception, it is relayed * to the initial caller and no value is recorded. @@ -523,19 +562,16 @@ static Function ofFunction(Set inputs, /** * {@return a shallowly immutable, lazy, stable List of the provided {@code size} - * where the individual elements of the list are lazily computed via the provided - * {@code mapper} whenever an element is first accessed (directly or indirectly), - * for example via {@linkplain List#get(int) List::get}} + * where the individual elements of the list are lazily computed via the + * provided {@code mapper} whenever an element is first accessed + * (directly or indirectly), for example via + * {@linkplain List#get(int) List::get}} *

    * The provided {@code mapper} IntFunction is guaranteed to be successfully invoked * at most once per list index, even in a multi-threaded environment. Competing * threads accessing an element already under computation will block until an element * is computed or an exception is thrown by the computing thread. *

    - * If the {@code mapper} IntFunction invokes the returned IntFunction recursively - * for a particular index, a {@linkplain StackOverflowError} will be thrown when the - * returned List's {@linkplain List#get(int) List::get} method is invoked. - *

    * If the provided {@code mapper} IntFunction throws an exception, it is relayed * to the initial caller and no element is recorded. *

    @@ -558,19 +594,15 @@ static List ofList(int size, IntFunction mapper) { /** * {@return a shallowly immutable, lazy, stable Map with the provided {@code keys} - * where the associated values of the maps are lazily computed via the provided - * {@code mapper} whenever a value is first accessed (directly or indirectly), for - * example via {@linkplain Map#get(Object) Map::get}} + * where the associated values of the maps are lazily computed via + * the provided {@code mapper} whenever a value is first accessed + * (directly or indirectly), for example via {@linkplain Map#get(Object) Map::get}} *

    * The provided {@code mapper} Function is guaranteed to be successfully invoked * at most once per key, even in a multi-threaded environment. Competing * threads accessing an associated value already under computation will block until * an associated value is computed or an exception is thrown by the computing thread. *

    - * If the {@code mapper} Function invokes the returned Map recursively - * for a particular key, a {@linkplain StackOverflowError} will be thrown when the - * returned Map's {@linkplain Map#get(Object) Map::get}} method is invoked. - *

    * If the provided {@code mapper} Function throws an exception, it is relayed * to the initial caller and no value is recorded. *

    From ebe151b836ed73d12a28a8f0fafaed49ee6e9a1b Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Fri, 25 Oct 2024 10:13:37 +0200 Subject: [PATCH 142/327] Make small updates to doc --- src/java.base/share/classes/java/lang/StableValue.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index e1ff299f8581e..54c7fb6953f72 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -68,10 +68,12 @@ * Stable values are objects that represent deferred immutable data and are treated * as constants by the JVM, enabling the same performance optimizations that are * possible by marking a field final. Yet, stable values offer greater flexibility as to - * the timing of initialization compared to {@code final} fields: + * the timing of initialization compared to {@code final} fields. + *

    + * The characteristics of the various kinds of storage are summarized in the following table: *
    *

    - * + *
    * * * @@ -305,7 +307,7 @@ *

    * A stable list is similar to a stable int function, but provides a full * implementation of an immutable {@linkplain List}. This is useful when interacting with - * collection-based methods. Here is an example how a stable list can be used to hold + * collection-based methods. Here is an example of how a stable list can be used to hold * a pool of order controllers: * {@snippet lang = java: * class Application { From b6e445e41928f57726d11cb22607bd7376e16ae2 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 28 Oct 2024 14:50:03 +0100 Subject: [PATCH 143/327] Review documentation after comments --- .../share/classes/java/lang/StableValue.java | 223 ++++++++---------- 1 file changed, 93 insertions(+), 130 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 54c7fb6953f72..13dc17c7b9e5f 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -46,29 +46,10 @@ * In the Java programming language, a field could either be immutable (i.e. declared * {@code final}) or mutable (i.e. not declared {@code final}): *

    - * Immutable fields must be set eagerly either by the constructor - * (for instance fields) or during class initialization (for {@code static} fields). - * Moreover, the order in which {@code final} fields are initialized is determined by - * the textual order in which the fields are declared. A {@code final} field is - * initialized exactly once by the same thread that instantiated an object or that - * first loaded a class. Because the field requires initialization upfront, the use - * of them often increases the application startup time. - * As the field can never change after being initialized, the JVM is able to reason about - * constantness and can trust a {@code final} is stable and the field may therefor be - * eligible for certain JVM optimization such as constant folding. - *

    - * Mutable fields, on the other hand, can be initialized at any time, by any - * thread and may subsequently be updated arbitrarily. A mutable field can be lazily - * initialized and can be used to reduce application startup time. However, as the field - * can change at any time, the JVM is not able to reason about constantness and cannot - * trust the value remains stable. Hence, it is not able to perform optimizations like - * constant folding. - * - *

    Stable Values

    * Stable values are objects that represent deferred immutable data and are treated * as constants by the JVM, enabling the same performance optimizations that are - * possible by marking a field final. Yet, stable values offer greater flexibility as to - * the timing of initialization compared to {@code final} fields. + * possible by marking a field {@code final}. Yet, stable values offer greater flexibility + * as to the timing of initialization compared to {@code final} fields. *

    * The characteristics of the various kinds of storage are summarized in the following table: *
    @@ -110,29 +91,6 @@ *

    Storage kind comparison
    *
    *

    - * There are three categories of stable values and six variants of stable value objects : - *

      - *
    • {@linkplain StableValue##stable-value Stable Value} - *
        - *
      • Stable value: {@linkplain StableValue {@code StableValue}}
      • - *
      - *
    • - *
    • {@linkplain StableValue##stable-functions Stable Functions} - *
        - *
      • Stable supplier: {@linkplain Supplier {@code Supplier}}
      • - *
      • Stable int function: {@linkplain IntFunction {@code IntFunction}}
      • - *
      • Stable function: {@linkplain Function {@code Function}}
      • - *
      - *
    • - *
    • {@linkplain StableValue##stable-collections Stable Collections} - *
        - *
      • Stable list: {@linkplain List {@code List}}
      • - *
      • Stable map: {@linkplain Map {@code Map}}
      • - *
      - *
    • - *
    - * - *

    Stable Value

    * A stable value is an object of type {@linkplain StableValue {@code StableValue}} * and is a holder of underlying data of type {@code T}. It can be used to defer * initialization of its underlying data while retaining the same performance as if @@ -143,7 +101,7 @@ *

    * A stable value can be used imperatively or functionally as outlined * hereunder. - *

    Imperative use

    + *

    Imperative use

    * Imperative use often entails more low-level usage, interacting directly with the * underlying data. Here is an example of how the underlying data can be initialized * imperatively using a pre-computed value (here {@code 42}) using the method @@ -164,22 +122,8 @@ * Note that the underlying data can only be initialized at most once, even when several * threads are racing to initialize the underlying data. Only one thread is selected as * the winner. - *

    - * Here is another example where the underlying data of a stable value is retrieved - * using the method {@linkplain StableValue#orElseThrow() orElseThrow()}. The method will - * throw {@linkplain NoSuchElementException} if no underlying data exists: * - * {@snippet lang=java : - * StableValue stableValue = StableValue.of(); - * // ... logic that may set the underlying data of stableValue - * int val = stableValue.orElseThrow(); // The underlying data if set, else throws - * } - *

    - * The presence of underlying data can be examined using the method - * {@linkplain StableValue#isSet() isSet()}. The method will return {@code true} if the - * underlying data is initialized, otherwise it will return {@code false}. - - *

    Functional use

    + *

    Functional use

    * Functional use of a stable value entails providing a * {@linkplain Supplier {@code Supplier}} to the instance method * {@linkplain StableValue#computeIfUnset(Supplier) computeIfUnset()} whereby the @@ -220,16 +164,30 @@ * A {@linkplain StableValue} is mainly intended to be a member of a holding class (as * shown in the examples above) and is usually neither exposed directly via accessors nor * passed as a method parameter. - * - *

    Stable Functions

    - * There are three stable functions: + *

    + * There are two additional categories of stable values: *

      - *
    • Stable supplier
    • - *
    • Stable int function
    • - *
    • Stable function
    • + *
    • {@linkplain StableValue##stable-functions Stable Functions} + *
        + *
      • Stable supplier: {@linkplain Supplier {@code Supplier}}
      • + *
      • Stable int function: {@linkplain IntFunction {@code IntFunction}}
      • + *
      • Stable function: {@linkplain Function {@code Function}}
      • + *
      + *
    • + *
    • {@linkplain StableValue##stable-collections Stable Collections} + *
        + *
      • Stable list: {@linkplain List {@code List}}
      • + *
      • Stable map: {@linkplain Map {@code Map}}
      • + *
      + *
    • *
    + * + *

    Stable Functions

    + * Stable functions are thread-safe, caching, lazily computed functions that, for each + * allowed input (if any), record the values of some original function upon being first + * accessed via the stable function's sole abstract method. *

    - * All the stable functions guarantee the provided {@code original} function is invoked + * All the stable functions guarantee the provided original function is invoked * successfully at most once per valid input even in a multithreaded environment. *

    * A stable supplier allows a more convenient construct compared to a @@ -253,7 +211,7 @@ * {@linkplain StableValue##functional-use functional example} above. *

    * A stable int function stores values in an array of stable values where - * the elements` underlying data are computed the first time a particular input value + * the elements' underlying data are computed the first time a particular input value * is provided. * When the stable int function is first created -- * via the {@linkplain StableValue#ofIntFunction(int, IntFunction) @@ -275,7 +233,7 @@ *} *

    * A stable function stores values in an array of stable values where - * the elements`s underlying data are computed the first time a particular input value + * the elements' underlying data are computed the first time a particular input value * is provided. * When the stable function is first created -- * via the {@linkplain StableValue#ofFunction(Set, Function) StableValue.ofFunction()} @@ -296,21 +254,21 @@ *} * *

    Stable Collections

    - * There are two stable collections: - *
      - *
    • Stable list
    • - *
    • Stable map
    • - *
    + * Stable collections are thread-safe, caching, lazily computed, shallowly immutable + * collections that, for each allowed input, record the values of some original mapper + * upon being first accessed (directly or indirectly) via the collection's get method. *

    - * All the stable collections guarantee the provided {@code original} function is - * invoked successfully at most once per valid input even in a multithreaded environment. + * All the stable collections guarantee the provided original mapper is invoked + * successfully at most once per valid input even in a multithreaded environment. *

    - * A stable list is similar to a stable int function, but provides a full + * A stable list is similar to a stable int function but provides a full * implementation of an immutable {@linkplain List}. This is useful when interacting with * collection-based methods. Here is an example of how a stable list can be used to hold * a pool of order controllers: * {@snippet lang = java: * class Application { + * static final int POOL_SIZE = 16; + * * static final List ORDER_CONTROLLERS = * StableValue.ofList(POOL_SIZE, OrderController::new); * @@ -321,10 +279,10 @@ * } * } *

    - * Note: In the example above, there is a constructor in {@code OrderController} that - * takes an {@code int} parameter. + * Note: In the example above, there is a constructor in the {@code OrderController} + * class that takes an {@code int} parameter. *

    - * A stable map is similar to a stable function, but provides a full + * A stable map is similar to a stable function but provides a full * implementation of an immutable {@linkplain Map}. This is useful when interacting with * collection-based methods. Here is how a stable map can be used as a cache for * square roots for certain input values given at creation: @@ -342,11 +300,10 @@ *} * *

    Memory Consistency Properties

    - * Actions on presumptive underlying data in a thread prior to calling a method that - * sets the underlying data are seen by any other thread that observes the - * underlying data. + * All stores made to underlying data before being set are guaranteed to be seen by + * any thread that obtains the data via a stable value. *

    - * More generally, the action of attempting to interact (i.e. via load or store operations) + * More formally, the action of attempting to interact (i.e. via load or store operations) * with a StableValue's underlying data (e.g. via {@link StableValue#trySet} or * {@link StableValue#orElseThrow()}) forms a * happens-before @@ -395,7 +352,7 @@ public sealed interface StableValue boolean trySet(T underlyingData); /** - * {@return the underlying data if set, otherwise return the provided + * {@return the underlying data if set, otherwise, return the provided * {@code other} value} * * @@ -404,7 +361,7 @@ public sealed interface StableValue T orElse(T other); /** - * {@return the underlying data if set, otherwise throws {@code NoSuchElementException}} + * {@return the underlying data if set, otherwise, throws {@code NoSuchElementException}} * * @throws NoSuchElementException if no underlying data is set */ @@ -416,9 +373,8 @@ public sealed interface StableValue boolean isSet(); /** - * {@return the underlying data if set, otherwise attempts to compute and set - * ew underlying data using the provided {@code supplier}, returning the - * newly set underlying data} + * {@return the underlying data; if not set, first attempts to compute the + * underlying data using the provided {@code supplier}} *

    * The provided {@code supplier} is guaranteed to be invoked at most once if it * completes without throwing an exception. @@ -471,9 +427,11 @@ public sealed interface StableValue // Factories /** - * {@return a new thin, atomic, thread-safe, set-at-most-once, {@linkplain StableValue} - * with no underlying data eligible for certain JVM optimizations once the - * underlying data is set} + * {@return a new stable value with no underlying data} + *

    + * The returned {@linkplain StableValue stable value} is a thin, atomic, thread-safe, + * set-at-most-once, stable value with no underlying data eligible for certain JVM + * optimizations once the underlying data is set. * * @param type of the underlying data */ @@ -482,10 +440,12 @@ static StableValue of() { } /** - * {@return a new stable, thread-safe, caching, lazily computed - * {@linkplain Supplier supplier} that records the value of the provided - * {@code original} supplier upon being first accessed via - * the returned supplier's {@linkplain Supplier#get() get()} method} + * {@return a new stable supplier with no underlying data} + *

    + * The returned stable {@linkplain Supplier supplier} is a thread-safe, caching, + * lazily computed supplier that records the value of the provided {@code original} + * supplier upon being first accessed via the returned supplier's + * {@linkplain Supplier#get() get()} method. *

    * The provided {@code original} supplier is guaranteed to be successfully invoked * at most once even in a multi-threaded environment. Competing threads invoking the @@ -505,20 +465,21 @@ static Supplier ofSupplier(Supplier original) { } /** - * {@return a new stable, thread-safe, caching, lazily computed - * {@link IntFunction } that, for each allowed input, records the values of - * the provided {@code original} IntFunction upon being first accessed via - * the returned IntFunction's {@linkplain IntFunction#apply(int) apply()} - * method} + * {@return a new stable int function with no underlying data} + *

    + * The returned stable {@link IntFunction int function} is a thread-safe, caching, + * lazily computed int function that, for each allowed input, records the values of + * the provided {@code original} int function upon being first accessed via the + * returned int function's {@linkplain IntFunction#apply(int) apply()} method. *

    - * The provided {@code original} IntFunction is guaranteed to be successfully invoked + * The provided {@code original} int function is guaranteed to be successfully invoked * at most once per allowed input, even in a multi-threaded environment. Competing - * threads invoking the returned IntFunction's + * threads invoking the returned inr function's * {@linkplain IntFunction#apply(int) apply()} method when a value is already under * computation will block until a value is computed or an exception is thrown by * the computing thread. *

    - * If the provided {@code original} IntFunction throws an exception, it is relayed + * If the provided {@code original} int function throws an exception, it is relayed * to the initial caller and no value is recorded. * * @param size the size of the allowed inputs in {@code [0, size)} @@ -535,20 +496,22 @@ static IntFunction ofIntFunction(int size, } /** - * {@return a new stable, thread-safe, caching, lazily computed {@link Function} - * that, for each allowed input in the given set of {@code inputs}, records - * the values of the provided {@code original} Function upon being first - * accessed via the returned Function's - * {@linkplain Function#apply(Object) apply()} method} + * {@return a new stable function with no underlying data} + *

    + * The returned stable {@link Function function} is a stable, thread-safe, + * caching, lazily computed function that, for each allowed input in the given + * set of {@code inputs}, records the values of the provided {@code original} function + * upon being first accessed via the returned function's + * {@linkplain Function#apply(Object) apply()} method. *

    - * The provided {@code original} Function is guaranteed to be successfully invoked + * The provided {@code original} function is guaranteed to be successfully invoked * at most once per allowed input, even in a multi-threaded environment. Competing * threads invoking the returned function's {@linkplain Function#apply(Object) apply()} * method when a value is already under computation will block until a value is * computed or an exception is thrown by the computing thread. *

    - * If the provided {@code original} Function throws an exception, it is relayed - * to the initial caller and no value is recorded. + * If the provided {@code original} function throws an exception, it is relayed to + * the initial caller and no value is recorded. * * @param inputs the set of allowed input values * @param original Function used to compute cached values @@ -563,19 +526,21 @@ static Function ofFunction(Set inputs, } /** - * {@return a shallowly immutable, lazy, stable List of the provided {@code size} - * where the individual elements of the list are lazily computed via the - * provided {@code mapper} whenever an element is first accessed - * (directly or indirectly), for example via - * {@linkplain List#get(int) List::get}} + * {@return a new stable list of the provided {@code size} with no underlying data} + *

    + * The returned {@linkplain List list} is a stable, thread-safe, caching, + * lazily computed, shallowly immutable list where the individual elements of the list + * are lazily computed via the provided {@code mapper} whenever an element is first + * accessed (directly or indirectly), for example via the returned list's + * {@linkplain List#get(int) get()} method. *

    - * The provided {@code mapper} IntFunction is guaranteed to be successfully invoked + * The provided {@code mapper} int function is guaranteed to be successfully invoked * at most once per list index, even in a multi-threaded environment. Competing * threads accessing an element already under computation will block until an element * is computed or an exception is thrown by the computing thread. *

    - * If the provided {@code mapper} IntFunction throws an exception, it is relayed - * to the initial caller and no element is recorded. + * If the provided {@code mapper} throws an exception, it is relayed to the initial + * caller and no element is recorded. *

    * The returned List is not {@link Serializable} as this would require the provided * {@code mapper} to be {@link Serializable} as well which would create security @@ -595,18 +560,16 @@ static List ofList(int size, IntFunction mapper) { } /** - * {@return a shallowly immutable, lazy, stable Map with the provided {@code keys} - * where the associated values of the maps are lazily computed via - * the provided {@code mapper} whenever a value is first accessed - * (directly or indirectly), for example via {@linkplain Map#get(Object) Map::get}} + * {@return a new stable map with the provided {@code keys} with no underlying data} *

    - * The provided {@code mapper} Function is guaranteed to be successfully invoked - * at most once per key, even in a multi-threaded environment. Competing - * threads accessing an associated value already under computation will block until - * an associated value is computed or an exception is thrown by the computing thread. + * The returned {@linkplain Map map} is a stable, thread-safe, caching, + * lazily computed, shallowly immutable map where the individual values of the map + * are lazily computed via the provided {@code mapper} whenever an element is first + * accessed (directly or indirectly), for example via the returned map's + * {@linkplain Map#get(Object)} get()} method. *

    - * If the provided {@code mapper} Function throws an exception, it is relayed - * to the initial caller and no value is recorded. + * If the provided {@code mapper} throws an exception, it is relayed to the initial + * caller and no value is recorded. *

    * The returned Map is not {@link Serializable} as this would require the provided * {@code mapper} to be {@link Serializable} as well which would create security From 05c99bfff766640d75abe202dddc4700ec7a722c Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 28 Oct 2024 16:03:42 +0100 Subject: [PATCH 144/327] Make minor doc updates --- src/java.base/share/classes/java/lang/StableValue.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 13dc17c7b9e5f..413800f9a8908 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -343,7 +343,7 @@ public sealed interface StableValue /** * {@return {@code true} if the underlying data was set to the provided - * {@code underlyingData}, otherwise returns {@code false}} + * {@code underlyingData}, {@code false} otherwise} *

    * When this method returns, the underlying data is always set. * @@ -352,7 +352,7 @@ public sealed interface StableValue boolean trySet(T underlyingData); /** - * {@return the underlying data if set, otherwise, return the provided + * {@return the underlying data if set, otherwise, returns the provided * {@code other} value} * * @@ -566,7 +566,7 @@ static List ofList(int size, IntFunction mapper) { * lazily computed, shallowly immutable map where the individual values of the map * are lazily computed via the provided {@code mapper} whenever an element is first * accessed (directly or indirectly), for example via the returned map's - * {@linkplain Map#get(Object)} get()} method. + * {@linkplain Map#get(Object) get()} method. *

    * If the provided {@code mapper} throws an exception, it is relayed to the initial * caller and no value is recorded. From d82e37d80e880ee76e525b9fadf1eb61f68f5138 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 6 Nov 2024 09:48:18 +0100 Subject: [PATCH 145/327] Add methods to StableValueFactories --- .../share/classes/java/lang/StableValue.java | 4 ++-- .../lang/stable/StableValueFactories.java | 16 +++++++++------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 413800f9a8908..7e2cfe47da4ec 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -556,7 +556,7 @@ static List ofList(int size, IntFunction mapper) { throw new IllegalArgumentException(); } Objects.requireNonNull(mapper); - return SharedSecrets.getJavaUtilCollectionAccess().stableList(size, mapper); + return StableValueFactories.ofList(size, mapper); } /** @@ -584,7 +584,7 @@ static List ofList(int size, IntFunction mapper) { static Map ofMap(Set keys, Function mapper) { Objects.requireNonNull(keys); Objects.requireNonNull(mapper); - return SharedSecrets.getJavaUtilCollectionAccess().stableMap(keys, mapper); + return StableValueFactories.ofMap(keys, mapper); } } diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableValueFactories.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableValueFactories.java index 436b6845561a4..3c18c461cbb9d 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableValueFactories.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableValueFactories.java @@ -1,6 +1,9 @@ package jdk.internal.lang.stable; +import jdk.internal.access.SharedSecrets; + import java.util.EnumSet; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; @@ -37,17 +40,16 @@ public static Function ofFunction(Set inputs, : StableFunction.of(inputs, original); } - @SuppressWarnings("unchecked") - private static > EnumSet asEnumSet(Set original) { - return (EnumSet) original; + public static List ofList(int size, IntFunction mapper) { + return SharedSecrets.getJavaUtilCollectionAccess().stableList(size, mapper); } - public static , R> Function newCachingEnumFunction(EnumSet inputs, - Function original) { - - return StableEnumFunction.of(inputs, original); + public static Map ofMap(Set keys, Function mapper) { + return SharedSecrets.getJavaUtilCollectionAccess().stableMap(keys, mapper); } + // Supporting methods + public static StableValueImpl[] ofArray(int size) { if (size < 0) { throw new IllegalArgumentException(); From 053ffcbebf1096e61d53b653eaba83bf57ae202b Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Fri, 8 Nov 2024 15:40:59 +0100 Subject: [PATCH 146/327] Update docs --- .../share/classes/java/lang/StableValue.java | 242 +++++++++--------- .../internal/lang/stable/StableValueImpl.java | 30 +-- .../StableValue/TrustedFieldTypeTest.java | 4 +- 3 files changed, 133 insertions(+), 143 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 7e2cfe47da4ec..f45e33bd87ed2 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -25,7 +25,6 @@ package java.lang; -import jdk.internal.access.SharedSecrets; import jdk.internal.javac.PreviewFeature; import jdk.internal.lang.stable.StableValueImpl; import jdk.internal.lang.stable.StableValueFactories; @@ -41,17 +40,15 @@ import java.util.function.Supplier; /** - * A value that has the same performance as a final field but can be freely initialized. - *

    - * In the Java programming language, a field could either be immutable (i.e. declared - * {@code final}) or mutable (i.e. not declared {@code final}): - *

    - * Stable values are objects that represent deferred immutable data and are treated - * as constants by the JVM, enabling the same performance optimizations that are + * A stable value is an object that represents deferred immutable data. Stable values are + * treated as constants by the JVM, enabling the same performance optimizations that are * possible by marking a field {@code final}. Yet, stable values offer greater flexibility * as to the timing of initialization compared to {@code final} fields. *

    - * The characteristics of the various kinds of storage are summarized in the following table: + * In the Java programming language, a field could either be immutable (i.e. declared + * {@code final}) or mutable (i.e. not declared {@code final}). Stable values fill + * the gap between immutable ({@code final}) fields and mutable (non-{@code final}): + *

    *
    *

    * @@ -91,43 +88,53 @@ *
    *
    *

    - * A stable value is an object of type {@linkplain StableValue {@code StableValue}} - * and is a holder of underlying data of type {@code T}. It can be used to defer - * initialization of its underlying data while retaining the same performance as if - * a field of type {@code T} was declared {@code final}. Once the underlying data has - * been initialized, the stable value usually acts as a constant for the rest of the - * program's execution. In effect, a stable value provides a way of achieving deferred - * immutability. + * As such, stable values offers many of the advantages from both the other types and + * hence effectively provides a way of achieving deferred immutability. + *

    + * An instance of {@code StableValue} is an immutable holder for a value of + * {@code type T}. Initially, an instance of {@code StableValue} is unset, + * which means it holds no value. It can be set by passing a value to + * {@linkplain StableValue#trySet(Object) trySet()}, + * {@linkplain StableValue#setOrThrow(Object) setOrThrow()}, or + * {@linkplain StableValue#computeIfUnset(Supplier) computeIfUnset()}. Once set, the + * value held by a {@code StableValue} can never change and can be retrieved by calling + * {@linkplain StableValue#orElseThrow() orElseThrow()}, + * {@linkplain StableValue#orElse(Object) orElse()}, or + * {@linkplain StableValue#computeIfUnset(Supplier) computeIfUnset()}. The Java + * Virtual Machine treats a {@code StableValue} that is set as a constant, equal to + * the value that it holds. + *

    + * A stable value can be used imperatively -- by interacting directly with the + * holder value -- or functionally whereby a supplier is provided that will be + * used to calculate the holder value, should it be unset. *

    - * A stable value can be used imperatively or functionally as outlined - * hereunder. - *

    Imperative use

    - * Imperative use often entails more low-level usage, interacting directly with the - * underlying data. Here is an example of how the underlying data can be initialized - * imperatively using a pre-computed value (here {@code 42}) using the method - * {@linkplain StableValue#trySet(Object) trySet()}. The method will return {@code true} - * if the underlying data was indeed initialized, or it will return {@code false} if - * the underlying data was already initialized: + * Imperative use + *

    + * Imperative use often entails more low-level usage. Here is an example of how the + * holder value can be set imperatively using a pre-computed value (here {@code 42}) + * using the method {@linkplain StableValue#trySet(Object) trySet()}. + * The method will return {@code true} if the holder value was indeed set, or + * it will return {@code false} if the holder value was already set: * * {@snippet lang=java : - * // Creates a new stable value with no underlying data + * // Creates a new stable value with no holder value * StableValue stableValue = StableValue.of(); - * // ... logic that may or may not set the underlying data of stableValue + * // ... logic that may or may not set the holder value of stableValue * if (stableValue.trySet(42)) { - * System.out.println("The underlying data was initialized to 42."); + * System.out.println("The holder value was set to 42."); * } else { - * System.out.println("The value was already initialized to " + stableValue.orElseThrow()); + * System.out.println("The holder value was already set to " + stableValue.orElseThrow()); * } * } - * Note that the underlying data can only be initialized at most once, even when several - * threads are racing to initialize the underlying data. Only one thread is selected as - * the winner. - * - *

    Functional use

    + * Note that the holder value can only be set at most once, even when several threads are + * racing to set the holder value. Only one thread is selected as the winner. + *

    + * Functional use + *

    * Functional use of a stable value entails providing a * {@linkplain Supplier {@code Supplier}} to the instance method * {@linkplain StableValue#computeIfUnset(Supplier) computeIfUnset()} whereby the - * provided supplier is automatically invoked if there is no underlying data as shown in + * provided supplier is automatically invoked if no holder value is set, as shown in * this example: * * {@snippet lang = java: @@ -147,14 +154,13 @@ * } *} * The {@code getLogger()} method calls {@code logger.computeIfUnset()} on the - * stable value to retrieve its underlying data. If the stable value is unset, then - * {@code computeIfUnset()} initializes the underlying data, causing the stable value to - * become set; the underlying data is then returned to the client. In other words, - * {@code computeIfUnset()} guarantees that a stable value's underlying data is - * initialized before it is used. + * stable value to retrieve its holder value. If the stable value is unset, then + * {@code computeIfUnset()} evaluates and sets the holder value; the holder value is then + * returned to the client. In other words, {@code computeIfUnset()} guarantees that a + * stable value's holder value is set before it is used. *

    - * Even though the stable value, once set, is immutable, its underlying data is not - * required to be initialized upfront. Rather, it can be initialized on demand. Furthermore, + * Even though the stable value, once set, is immutable, its holder value is not + * required to be set upfront. Rather, it can be set on demand. Furthermore, * {@code computeIfUnset()} guarantees that the lambda expression provided is evaluated * only once, even when {@code logger.computeIfUnset()} is invoked concurrently. * This property is crucial as evaluation of the lambda expression may have side effects, @@ -164,26 +170,9 @@ * A {@linkplain StableValue} is mainly intended to be a member of a holding class (as * shown in the examples above) and is usually neither exposed directly via accessors nor * passed as a method parameter. - *

    - * There are two additional categories of stable values: - *

      - *
    • {@linkplain StableValue##stable-functions Stable Functions} - *
        - *
      • Stable supplier: {@linkplain Supplier {@code Supplier}}
      • - *
      • Stable int function: {@linkplain IntFunction {@code IntFunction}}
      • - *
      • Stable function: {@linkplain Function {@code Function}}
      • - *
      - *
    • - *
    • {@linkplain StableValue##stable-collections Stable Collections} - *
        - *
      • Stable list: {@linkplain List {@code List}}
      • - *
      • Stable map: {@linkplain Map {@code Map}}
      • - *
      - *
    • - *
    * *

    Stable Functions

    - * Stable functions are thread-safe, caching, lazily computed functions that, for each + * Stable functions are thread-safe, caching, and lazily computed functions that, for each * allowed input (if any), record the values of some original function upon being first * accessed via the stable function's sole abstract method. *

    @@ -192,8 +181,8 @@ *

    * A stable supplier allows a more convenient construct compared to a * {@linkplain StableValue} in the sense that a field declaration can also specify how - * the underlying data of a stable value is to be initialized, but without actually - * initializing its underlying data upfront: + * the holder value of a stable value is to be set, but without actually setting its + * holder value upfront: * {@snippet lang = java: * class Component { * @@ -211,7 +200,7 @@ * {@linkplain StableValue##functional-use functional example} above. *

    * A stable int function stores values in an array of stable values where - * the elements' underlying data are computed the first time a particular input value + * the elements' holder value are computed the first time a particular input value * is provided. * When the stable int function is first created -- * via the {@linkplain StableValue#ofIntFunction(int, IntFunction) @@ -233,7 +222,7 @@ *} *

    * A stable function stores values in an array of stable values where - * the elements' underlying data are computed the first time a particular input value + * the elements' holder value are computed the first time a particular input value * is provided. * When the stable function is first created -- * via the {@linkplain StableValue#ofFunction(Set, Function) StableValue.ofFunction()} @@ -300,38 +289,38 @@ *} * *

    Memory Consistency Properties

    - * All stores made to underlying data before being set are guaranteed to be seen by - * any thread that obtains the data via a stable value. + * All stores made to a holder value before being set are guaranteed to be seen by + * any thread that obtains the holder value via a stable value. *

    * More formally, the action of attempting to interact (i.e. via load or store operations) - * with a StableValue's underlying data (e.g. via {@link StableValue#trySet} or + * with a StableValue's holder value (e.g. via {@link StableValue#trySet} or * {@link StableValue#orElseThrow()}) forms a * happens-before * relation between any other action of attempting to interact with the - * StableValue's underlying data. The same happens-before guarantees extend to + * StableValue's holder value. The same happens-before guarantees extend to * stable functions and stable collections; any action of attempting to interact via a * valid input value {@code I} happens-before any subsequent action of attempting * to interact via a valid input value {@code J} if, and only if, {@code I} and {@code J} * {@linkplain Object#equals(Object) equals()}. * *

    Miscellaneous

    - * Except for a StableValue's underlying data itself, all method parameters must be + * Except for a StableValue's holder value itself, all method parameters must be * non-null or a {@link NullPointerException} will be thrown. * * @implSpec Implementing classes of {@linkplain StableValue} are free to synchronize on * {@code this} and consequently, care should be taken whenever - * (directly or indirectly) synchronizing on a StableValue. Failure to do this - * may lead to deadlock. Stable functions and collections on the other hand are - * guaranteed not to synchronize on {@code this}. + * (directly or indirectly) synchronizing on a {@code StableValue}. Failure to + * do this may lead to deadlock. Stable functions and collections on the + * other hand are guaranteed not to synchronize on {@code this}. * - * @implNote Instance fields explicitly declared as StableValue or one-dimensional arrays - * thereof are eligible for certain JVM optimizations where normal instance - * fields are not. This comes with restrictions on reflective modifications. - * Although most ways of reflective modification of such fields are disabled, - * it is strongly discouraged to circumvent these protection means as - * reflectively modifying such fields may lead to unspecified behavior. + * @implNote Instance fields explicitly declared as {@code StableValue} or one-dimensional + * arrays thereof are eligible for certain JVM optimizations where normal + * instance fields are not. This comes with restrictions on reflective + * modifications. Although most ways of reflective modification of such fields + * are disabled, it is strongly discouraged to circumvent these protection means + * as reflectively modifying such fields may lead to unspecified behavior. * - * @param type of the underlying data + * @param type of the holder value * * @since 25 */ @@ -342,52 +331,52 @@ public sealed interface StableValue // Principal methods /** - * {@return {@code true} if the underlying data was set to the provided - * {@code underlyingData}, {@code false} otherwise} + * {@return {@code true} if the holder value was set to the provided + * {@code value}, {@code false} otherwise} *

    - * When this method returns, the underlying data is always set. + * When this method returns, the holder value is always set. * - * @param underlyingData to set + * @param value to set */ - boolean trySet(T underlyingData); + boolean trySet(T value); /** - * {@return the underlying data if set, otherwise, returns the provided + * {@return the holder value if set, otherwise, returns the provided * {@code other} value} * * - * @param other to return if the underlying data is not set + * @param other to return if the holder value is not set */ T orElse(T other); /** - * {@return the underlying data if set, otherwise, throws {@code NoSuchElementException}} + * {@return the holder value if set, otherwise, throws {@code NoSuchElementException}} * - * @throws NoSuchElementException if no underlying data is set + * @throws NoSuchElementException if no holder value is set */ T orElseThrow(); /** - * {@return {@code true} if the underlying data is set, {@code false} otherwise} + * {@return {@code true} if the holder value is set, {@code false} otherwise} */ boolean isSet(); /** - * {@return the underlying data; if not set, first attempts to compute the - * underlying data using the provided {@code supplier}} + * {@return the holder value; if unset, first attempts to compute the holder value + * using the provided {@code supplier}} *

    * The provided {@code supplier} is guaranteed to be invoked at most once if it * completes without throwing an exception. *

    * If the supplier throws an (unchecked) exception, the exception is rethrown, and no - * underlying data is set. The most common usage is to construct a new object serving + * holder value is set. The most common usage is to construct a new object serving * as a lazily computed value or memoized result, as in: * *

     {@code
          * Value witness = stable.computeIfUnset(Value::new);
          * }
    *

    - * When this method returns successfully, the underlying data is always set. + * When this method returns successfully, the holder value is always set. * * @implSpec The implementation logic is equivalent to the following steps for this * {@code stable}: @@ -406,44 +395,43 @@ public sealed interface StableValue * will only be invoked once even if invoked from several threads unless the * {@code supplier} throws an exception. * - * @param supplier to be used for computing the underlying data + * @param supplier to be used for computing the holder value */ T computeIfUnset(Supplier supplier); // Convenience methods /** - * Sets the underlying data to the provided {@code underlyingData}, or, + * Sets the holder value to the provided {@code value}, or, * if already set, throws {@link IllegalStateException}. *

    - * When this method returns (or throws an Exception), the underlying data is - * always set. + * When this method returns (or throws an Exception), the holder value is always set. * - * @param underlyingData to set - * @throws IllegalStateException if the underlying data is already set + * @param value to set + * @throws IllegalStateException if the holder value was already set */ - void setOrThrow(T underlyingData); + void setOrThrow(T value); // Factories /** - * {@return a new stable value with no underlying data} + * {@return a new stable value with no holder value} *

    * The returned {@linkplain StableValue stable value} is a thin, atomic, thread-safe, - * set-at-most-once, stable value with no underlying data eligible for certain JVM - * optimizations once the underlying data is set. + * set-at-most-once, stable value with no holder value eligible for certain JVM + * optimizations once the holder value is set. * - * @param type of the underlying data + * @param type of the holder value */ static StableValue of() { return StableValueFactories.of(); } /** - * {@return a new stable supplier with no underlying data} + * {@return a new stable supplier with no holder value} *

    * The returned stable {@linkplain Supplier supplier} is a thread-safe, caching, - * lazily computed supplier that records the value of the provided {@code original} + * and lazily computed supplier that records the value of the provided {@code original} * supplier upon being first accessed via the returned supplier's * {@linkplain Supplier#get() get()} method. *

    @@ -454,7 +442,7 @@ static StableValue of() { * thrown by the computing thread. *

    * If the provided {@code original} supplier throws an exception, it is relayed - * to the initial caller and no value is recorded. + * to the initial caller and no holder value is recorded. * * @param original supplier used to compute a cached value * @param the type of results supplied by the returned supplier @@ -465,11 +453,11 @@ static Supplier ofSupplier(Supplier original) { } /** - * {@return a new stable int function with no underlying data} + * {@return a new stable int function with no holder values} *

    * The returned stable {@link IntFunction int function} is a thread-safe, caching, - * lazily computed int function that, for each allowed input, records the values of - * the provided {@code original} int function upon being first accessed via the + * and lazily computed int function that, for each allowed input, records the values + * of the provided {@code original} int function upon being first accessed via the * returned int function's {@linkplain IntFunction#apply(int) apply()} method. *

    * The provided {@code original} int function is guaranteed to be successfully invoked @@ -480,7 +468,7 @@ static Supplier ofSupplier(Supplier original) { * the computing thread. *

    * If the provided {@code original} int function throws an exception, it is relayed - * to the initial caller and no value is recorded. + * to the initial caller and no holder value is recorded. * * @param size the size of the allowed inputs in {@code [0, size)} * @param original IntFunction used to compute cached values @@ -496,10 +484,10 @@ static IntFunction ofIntFunction(int size, } /** - * {@return a new stable function with no underlying data} + * {@return a new stable function with no holder values} *

    * The returned stable {@link Function function} is a stable, thread-safe, - * caching, lazily computed function that, for each allowed input in the given + * caching, and lazily computed function that, for each allowed input in the given * set of {@code inputs}, records the values of the provided {@code original} function * upon being first accessed via the returned function's * {@linkplain Function#apply(Object) apply()} method. @@ -511,7 +499,7 @@ static IntFunction ofIntFunction(int size, * computed or an exception is thrown by the computing thread. *

    * If the provided {@code original} function throws an exception, it is relayed to - * the initial caller and no value is recorded. + * the initial caller and no holder value is recorded. * * @param inputs the set of allowed input values * @param original Function used to compute cached values @@ -526,12 +514,12 @@ static Function ofFunction(Set inputs, } /** - * {@return a new stable list of the provided {@code size} with no underlying data} + * {@return a new stable list of the provided {@code size} with no holder values} *

    * The returned {@linkplain List list} is a stable, thread-safe, caching, - * lazily computed, shallowly immutable list where the individual elements of the list - * are lazily computed via the provided {@code mapper} whenever an element is first - * accessed (directly or indirectly), for example via the returned list's + * lazily computed, and shallowly immutable list where the individual elements of the + * list are lazily computed via the provided {@code mapper} whenever an element is + * first accessed (directly or indirectly), for example via the returned list's * {@linkplain List#get(int) get()} method. *

    * The provided {@code mapper} int function is guaranteed to be successfully invoked @@ -540,10 +528,10 @@ static Function ofFunction(Set inputs, * is computed or an exception is thrown by the computing thread. *

    * If the provided {@code mapper} throws an exception, it is relayed to the initial - * caller and no element is recorded. + * caller and no holder value for the element is recorded. *

    * The returned List is not {@link Serializable} as this would require the provided - * {@code mapper} to be {@link Serializable} as well which would create security + * {@code mapper} to be {@link Serializable} as well, which would create security * concerns. * * @param size the size of the returned list @@ -551,7 +539,8 @@ static Function ofFunction(Set inputs, * (may return {@code null}) * @param the type of elements in the returned list */ - static List ofList(int size, IntFunction mapper) { + static List ofList(int size, + IntFunction mapper) { if (size < 0) { throw new IllegalArgumentException(); } @@ -560,19 +549,19 @@ static List ofList(int size, IntFunction mapper) { } /** - * {@return a new stable map with the provided {@code keys} with no underlying data} + * {@return a new stable map with the provided {@code keys} with no holder values} *

    * The returned {@linkplain Map map} is a stable, thread-safe, caching, - * lazily computed, shallowly immutable map where the individual values of the map + * lazily computed, and shallowly immutable map where the individual values of the map * are lazily computed via the provided {@code mapper} whenever an element is first * accessed (directly or indirectly), for example via the returned map's * {@linkplain Map#get(Object) get()} method. *

    * If the provided {@code mapper} throws an exception, it is relayed to the initial - * caller and no value is recorded. + * caller and no holder value associated with the provided key is recorded. *

    * The returned Map is not {@link Serializable} as this would require the provided - * {@code mapper} to be {@link Serializable} as well which would create security + * {@code mapper} to be {@link Serializable} as well, which would create security * concerns. * * @param keys the keys in the returned map @@ -581,7 +570,8 @@ static List ofList(int size, IntFunction mapper) { * @param the type of keys maintained by the returned map * @param the type of mapped values in the returned map */ - static Map ofMap(Set keys, Function mapper) { + static Map ofMap(Set keys, + Function mapper) { Objects.requireNonNull(keys); Objects.requireNonNull(mapper); return StableValueFactories.ofMap(keys, mapper); 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 f3aa0680953f1..a1471b390ec12 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 @@ -48,7 +48,7 @@ public final class StableValueImpl implements StableValue { // Unsafe offsets for direct field access private static final long UNDERLYING_DATA_OFFSET = - UNSAFE.objectFieldOffset(StableValueImpl.class, "underlyingData"); + UNSAFE.objectFieldOffset(StableValueImpl.class, "value"); // Generally, fields annotated with `@Stable` are accessed by the JVM using special // memory semantics rules (see `parse.hpp` and `parse(1|2|3).cpp`). @@ -62,29 +62,29 @@ public final class StableValueImpl implements StableValue { // | other | Set(other) | // @Stable - private volatile Object underlyingData; + private volatile Object value; // Only allow creation via the factory `StableValueImpl::newInstance` private StableValueImpl() {} @ForceInline @Override - public boolean trySet(T underlyingData) { - if (this.underlyingData != null) { + public boolean trySet(T value) { + if (this.value != null) { return false; } // Mutual exclusion is required here as `computeIfUnset` might also // attempt to modify the `wrappedValue` synchronized (this) { - return wrapAndCas(underlyingData); + return wrapAndCas(value); } } @ForceInline @Override - public void setOrThrow(T underlyingData) { - if (!trySet(underlyingData)) { - throw new IllegalStateException("Cannot set the underlying data to " + underlyingData + + public void setOrThrow(T value) { + if (!trySet(value)) { + throw new IllegalStateException("Cannot set the underlying data to " + value + " because the underlying data is already set: " + this); } } @@ -92,7 +92,7 @@ public void setOrThrow(T underlyingData) { @ForceInline @Override public T orElseThrow() { - final Object t = underlyingData; + final Object t = value; if (t == null) { throw new NoSuchElementException("No underlying data set"); } @@ -102,26 +102,26 @@ public T orElseThrow() { @ForceInline @Override public T orElse(T other) { - final Object t = underlyingData; + final Object t = value; return (t == null) ? other : unwrap(t); } @ForceInline @Override public boolean isSet() { - return underlyingData != null; + return value != null; } @ForceInline @Override public T computeIfUnset(Supplier supplier) { - final Object t = underlyingData; + final Object t = value; return (t == null) ? computeIfUnsetSlowPath(supplier) : unwrap(t); } @DontInline private synchronized T computeIfUnsetSlowPath(Supplier supplier) { - final Object t = underlyingData; + final Object t = value; if (t == null) { final T newValue = supplier.get(); // The mutex is reentrant so we need to check if the value was actually set. @@ -134,7 +134,7 @@ private synchronized T computeIfUnsetSlowPath(Supplier supplier) { @Override public String toString() { - final Object t = underlyingData; + final Object t = value; return t == this ? "(this StableValue)" : "StableValue" + renderWrapped(t); @@ -144,7 +144,7 @@ public String toString() { @ForceInline public Object wrappedValue() { - return underlyingData; + return value; } static String renderWrapped(Object t) { diff --git a/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java b/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java index a4de715bf3054..ad8b1baa97618 100644 --- a/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java +++ b/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java @@ -110,7 +110,7 @@ final class ArrayHolder { StableValue stableValue = StableValue.of(); Class clazz = stableValue.getClass(); System.out.println("clazz = " + clazz); - assertThrows(NoSuchFieldException.class, () -> clazz.getField("underlyingData")); + assertThrows(NoSuchFieldException.class, () -> clazz.getField("value")); } @Test @@ -159,7 +159,7 @@ void updateStableValueUnderlyingData() { stableValue.trySet(42); jdk.internal.misc.Unsafe unsafe = Unsafe.getUnsafe(); - long offset = unsafe.objectFieldOffset(stableValue.getClass(), "underlyingData"); + long offset = unsafe.objectFieldOffset(stableValue.getClass(), "value"); assertTrue(offset > 0); // Unfortunately, it is possible to update the underlying data via jdk.internal.misc.Unsafe From 1a6514c33306002e9b79ccf52b5fc3656f0f3fac Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 12 Nov 2024 14:22:42 +0100 Subject: [PATCH 147/327] Rework docs --- .../share/classes/java/lang/StableValue.java | 160 +++++++----------- 1 file changed, 65 insertions(+), 95 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index f45e33bd87ed2..192d4e3369619 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -40,109 +40,75 @@ import java.util.function.Supplier; /** - * A stable value is an object that represents deferred immutable data. Stable values are - * treated as constants by the JVM, enabling the same performance optimizations that are - * possible by marking a field {@code final}. Yet, stable values offer greater flexibility - * as to the timing of initialization compared to {@code final} fields. + * A stable value is an object that represents deferred immutable data. *

    * In the Java programming language, a field could either be immutable (i.e. declared - * {@code final}) or mutable (i.e. not declared {@code final}). Stable values fill - * the gap between immutable ({@code final}) fields and mutable (non-{@code final}): + * {@code final}) or mutable (i.e. not declared {@code final}). Immutable fields confer + * many performance optimizations but must be set exactly once by the creating thread + * in the constructor or in the static initializer. Mutable fields cannot be optimized in + * the same way but can be set an arbitrary number of times, at any time, by any thread in + * any place in the code. + * {@code StableValue} provides a means to obtain the same performance optimizations as + * for an immutable field while providing almost the same degree of initialization freedom + * as for a mutable field, where the only difference is that a stable value can be set + * at most once. In effect, a stable value represents deferred immutable data. *

    - *
    - *

    - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
    Storage kind comparison
    Storage kind#UpdatesUpdate locationConstant foldingConcurrent updated
    Mutable (non-{@code final})[0, ∞)AnywhereNoYes
    Stable (StableValue)[0, 1]AnywhereYes, after updateYes, by winner
    Immutable ({@code final})1Constructor or static initializerYesNo
    - *
    + * The {@code StableValue} API provides several factory methods that provide stable + * immutable holder objects that have unset values. By executing methods on a + * stable value object, holder values can be set. Once set, the holder + * value is eligible for constant-folding optimizations provided the following + * requirements are fulfilled: + *
      + *
    • There is a trusted constant root in the form of a {@code static final} field.
    • + *
    • There is a path from the constant root to a set holder value via + * one or more trusted edges where each trusted edge can be either: + *
        + *
      • A component in a {@linkplain Record}.
      • + *
      • A {@code final} field of declared type {@linkplain StableValue} in any class.
      • + *
      • An element from a {@code final} field of declared type {@linkplain StableValue StableValue[]} in any class.
      • + *
      • A {@code final} field in a hidden class.
      • + *
      + *
    • + *
    + * It is expected that the types of trusted edges in the JDK will increase + * over time and eventually, all {@code final} fields will be trusted. *

    - * As such, stable values offers many of the advantages from both the other types and - * hence effectively provides a way of achieving deferred immutability. - *

    - * An instance of {@code StableValue} is an immutable holder for a value of - * {@code type T}. Initially, an instance of {@code StableValue} is unset, - * which means it holds no value. It can be set by passing a value to - * {@linkplain StableValue#trySet(Object) trySet()}, - * {@linkplain StableValue#setOrThrow(Object) setOrThrow()}, or - * {@linkplain StableValue#computeIfUnset(Supplier) computeIfUnset()}. Once set, the - * value held by a {@code StableValue} can never change and can be retrieved by calling - * {@linkplain StableValue#orElseThrow() orElseThrow()}, - * {@linkplain StableValue#orElse(Object) orElse()}, or - * {@linkplain StableValue#computeIfUnset(Supplier) computeIfUnset()}. The Java - * Virtual Machine treats a {@code StableValue} that is set as a constant, equal to - * the value that it holds. - *

    - * A stable value can be used imperatively -- by interacting directly with the - * holder value -- or functionally whereby a supplier is provided that will be - * used to calculate the holder value, should it be unset. - *

    - * Imperative use - *

    - * Imperative use often entails more low-level usage. Here is an example of how the - * holder value can be set imperatively using a pre-computed value (here {@code 42}) - * using the method {@linkplain StableValue#trySet(Object) trySet()}. - * The method will return {@code true} if the holder value was indeed set, or - * it will return {@code false} if the holder value was already set: + * Consider the following example with a stable value "{@code VAL}" which + * here is an immutable holder of a value of type {@linkplain Integer} and that is + * initially created as unset, which means it holds no value. Later, the + * holder value is set to {@code 42} unless it was not previously set. + * The holder value of "{@code VAL}" (if set) can be retrieved via the method + * {@linkplain StableValue#orElseThrow()}. * - * {@snippet lang=java : + * {@snippet lang = java: * // Creates a new stable value with no holder value - * StableValue stableValue = StableValue.of(); - * // ... logic that may or may not set the holder value of stableValue - * if (stableValue.trySet(42)) { + * // @link substring="of" target="#of" : + * static final StableValue VAL = StableValue.of(); + * + // ... logic that may or may not set the holder value of `VAL` + * + * // @link substring="trySet(42)" target="#trySet(Object)" + * if (VAL.trySet(42)) { * System.out.println("The holder value was set to 42."); * } else { - * System.out.println("The holder value was already set to " + stableValue.orElseThrow()); + * // @link substring="orElseThrow" target="#orElseThrow" + * System.out.println("The holder value was already set to " + VAL.orElseThrow()); * } - * } + *} + *

    * Note that the holder value can only be set at most once, even when several threads are * racing to set the holder value. Only one thread is selected as the winner. *

    - * Functional use - *

    - * Functional use of a stable value entails providing a - * {@linkplain Supplier {@code Supplier}} to the instance method - * {@linkplain StableValue#computeIfUnset(Supplier) computeIfUnset()} whereby the - * provided supplier is automatically invoked if no holder value is set, as shown in - * this example: - * + * In this other example, a stable "{@code logger}" field is declared and is accessed + * via a {@code getLogger} method in which the holder value is lazily computed via a + * provided lambda expression: * {@snippet lang = java: * class Component { * + * // @link substring="of" target="#of" : * private final StableValue logger = StableValue.of(); * - * Logger getLogger() { + * private Logger getLogger() { * return logger.computeIfUnset( () -> Logger.getLogger("org.app.Component") ); * } * @@ -154,15 +120,15 @@ * } *} * The {@code getLogger()} method calls {@code logger.computeIfUnset()} on the - * stable value to retrieve its holder value. If the stable value is unset, then + * stable value to retrieve its holder value. If the stable value is unset, then * {@code computeIfUnset()} evaluates and sets the holder value; the holder value is then * returned to the client. In other words, {@code computeIfUnset()} guarantees that a - * stable value's holder value is set before it is used. + * stable value's holder value is set before it is used. *

    - * Even though the stable value, once set, is immutable, its holder value is not - * required to be set upfront. Rather, it can be set on demand. Furthermore, - * {@code computeIfUnset()} guarantees that the lambda expression provided is evaluated - * only once, even when {@code logger.computeIfUnset()} is invoked concurrently. + * Even though the stable value, once set, is immutable, its holder value is not + * required to be set upfront. Rather, it can be set on demand. + * Furthermore, {@code computeIfUnset()} guarantees that the lambda expression provided is + * evaluated only once, even when {@code logger.computeIfUnset()} is invoked concurrently. * This property is crucial as evaluation of the lambda expression may have side effects, * e.g., the call above to {@code Logger.getLogger()} may result in storage resources * being prepared. @@ -187,6 +153,7 @@ * class Component { * * private final Supplier logger = + * // @link substring="ofSupplier" target="#ofSupplier(Supplier)" : * StableValue.ofSupplier( () -> Logger.getLogger("org.app.Component") ); * * void process() { @@ -196,8 +163,7 @@ * } *} * This also allows the stable supplier to be accessed directly, without going through - * an accessor method like {@code getLogger()} in the - * {@linkplain StableValue##functional-use functional example} above. + * an accessor method like {@code getLogger()} in the previous example. *

    * A stable int function stores values in an array of stable values where * the elements' holder value are computed the first time a particular input value @@ -212,6 +178,7 @@ * class SqrtUtil { * * private static final IntFunction SQRT = + * // @link substring="ofIntFunction" target="#ofIntFunction(int,IntFunction)" : * StableValue.ofIntFunction(10, Math::sqrt); * * double sqrt9() { @@ -226,13 +193,14 @@ * is provided. * When the stable function is first created -- * via the {@linkplain StableValue#ofFunction(Set, Function) StableValue.ofFunction()} - * factory -- the input set is specified together with an original {@linkplain Function} + * factory -- the input Set is specified together with an original {@linkplain Function} * which is invoked at most once per input value. In effect, the stable function will act * like a cache for the original {@code Function}: * {@snippet lang = java: * class SqrtUtil { * * private static final Function SQRT = + * // @link substring="ofFunction" target="#ofFunction(Set,Function)" : * StableValue.ofFunction(Set.of(1, 2, 4, 8, 16, 32), Math::sqrt); * * double sqrt16() { @@ -259,6 +227,7 @@ * static final int POOL_SIZE = 16; * * static final List ORDER_CONTROLLERS = + * // @link substring="ofList" target="#ofList(int,IntFunction)" : * StableValue.ofList(POOL_SIZE, OrderController::new); * * public static OrderController orderController() { @@ -279,6 +248,7 @@ * class SqrtUtil { * * private static final Map SQRT = + * // @link substring="ofMap" target="#ofMap(Set,Function)" : * StableValue.ofMap(Set.of(1, 2, 4, 8, 16, 32), Math::sqrt); * * double sqrt16() { From 5a9c5ce558eebfec2a9e23d46c97d23920d37481 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 12 Nov 2024 14:54:30 +0100 Subject: [PATCH 148/327] Add missing character --- src/java.base/share/classes/java/lang/StableValue.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 192d4e3369619..0052bf34a3662 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -85,7 +85,7 @@ * // @link substring="of" target="#of" : * static final StableValue VAL = StableValue.of(); * - // ... logic that may or may not set the holder value of `VAL` + * // ... logic that may or may not set the holder value of `VAL` * * // @link substring="trySet(42)" target="#trySet(Object)" * if (VAL.trySet(42)) { From 35192ce5a6589ca973600587a1283a815a45189f Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 12 Nov 2024 16:06:25 +0100 Subject: [PATCH 149/327] Move constant folding section --- .../share/classes/java/lang/StableValue.java | 49 ++++++++++--------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 0052bf34a3662..37ef1284c8afc 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -48,36 +48,21 @@ * in the constructor or in the static initializer. Mutable fields cannot be optimized in * the same way but can be set an arbitrary number of times, at any time, by any thread in * any place in the code. - * {@code StableValue} provides a means to obtain the same performance optimizations as - * for an immutable field while providing almost the same degree of initialization freedom - * as for a mutable field, where the only difference is that a stable value can be set - * at most once. In effect, a stable value represents deferred immutable data. + * {@code StableValue} provides a means to eventually obtain the same performance + * optimizations as for an immutable field while providing almost the same degree of + * initialization freedom as for a mutable field, where the only difference is that a + * stable value can be set at most once. In effect, a stable value represents + * deferred immutable data. *

    * The {@code StableValue} API provides several factory methods that provide stable * immutable holder objects that have unset values. By executing methods on a - * stable value object, holder values can be set. Once set, the holder - * value is eligible for constant-folding optimizations provided the following - * requirements are fulfilled: - *

      - *
    • There is a trusted constant root in the form of a {@code static final} field.
    • - *
    • There is a path from the constant root to a set holder value via - * one or more trusted edges where each trusted edge can be either: - *
        - *
      • A component in a {@linkplain Record}.
      • - *
      • A {@code final} field of declared type {@linkplain StableValue} in any class.
      • - *
      • An element from a {@code final} field of declared type {@linkplain StableValue StableValue[]} in any class.
      • - *
      • A {@code final} field in a hidden class.
      • - *
      - *
    • - *
    - * It is expected that the types of trusted edges in the JDK will increase - * over time and eventually, all {@code final} fields will be trusted. + * stable value object, holder values can be set. *

    * Consider the following example with a stable value "{@code VAL}" which * here is an immutable holder of a value of type {@linkplain Integer} and that is * initially created as unset, which means it holds no value. Later, the - * holder value is set to {@code 42} unless it was not previously set. - * The holder value of "{@code VAL}" (if set) can be retrieved via the method + * holder value is set to {@code 42} (unless it was not previously set). + * Once set, the holder value of "{@code VAL}" can be retrieved via the method * {@linkplain StableValue#orElseThrow()}. * * {@snippet lang = java: @@ -257,6 +242,24 @@ * * } *} + *

    Constant Folding

    + * Once a stable value is set, the holder value is eligible for + * constant-folding optimizations provided the following requirements are + * fulfilled: + *
      + *
    • There is a trusted constant root in the form of a {@code static final} field.
    • + *
    • There is a path from the constant root to said set holder value via + * one or more trusted edges where each trusted edge can be either: + *
        + *
      • A component in a {@linkplain Record}.
      • + *
      • A {@code final} field of declared type {@linkplain StableValue} in any class,
      • + *
      • An element from a {@code final} field of declared type {@linkplain StableValue StableValue[]} in any class.
      • + *
      • A {@code final} field in a hidden class.
      • + *
      + *
    • + *
    + * It is expected that the types of trusted edges in the JDK will increase + * over time and that eventually, all {@code final} fields will be trusted. * *

    Memory Consistency Properties

    * All stores made to a holder value before being set are guaranteed to be seen by From e463a63b0482cd7360c23cfb297da99173aa7186 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 12 Nov 2024 17:38:45 +0100 Subject: [PATCH 150/327] Reduce docs --- .../share/classes/java/lang/StableValue.java | 152 ++++++++---------- 1 file changed, 63 insertions(+), 89 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 37ef1284c8afc..be471fb811738 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -42,68 +42,69 @@ /** * A stable value is an object that represents deferred immutable data. *

    - * In the Java programming language, a field could either be immutable (i.e. declared - * {@code final}) or mutable (i.e. not declared {@code final}). Immutable fields confer - * many performance optimizations but must be set exactly once by the creating thread - * in the constructor or in the static initializer. Mutable fields cannot be optimized in - * the same way but can be set an arbitrary number of times, at any time, by any thread in - * any place in the code. - * {@code StableValue} provides a means to eventually obtain the same performance - * optimizations as for an immutable field while providing almost the same degree of - * initialization freedom as for a mutable field, where the only difference is that a - * stable value can be set at most once. In effect, a stable value represents - * deferred immutable data. + * A {@linkplain StableValue} is created using the factory method + * {@linkplain StableValue#of()}. When created, a stable value is unset, which + * means it holds no value. It can be set by passing a value to + * {@linkplain #trySet(Object) trySet()}, {@linkplain #setOrThrow(Object) setOrThrow()}, + * or {@linkplain #computeIfUnset(Supplier) computeIfUnset()}. Once set, the value held + * by a {@code StableValue} can never change and can be retrieved by calling + * {@linkplain #orElseThrow() orElseThrow()}, {@linkplain #orElse(Object)}, or + * {@linkplain #computeIfUnset(Supplier) computeIfUnset()}. *

    - * The {@code StableValue} API provides several factory methods that provide stable - * immutable holder objects that have unset values. By executing methods on a - * stable value object, holder values can be set. + * A stable value that is set is treated as a constant by the JVM, enabling + * the same performance optimizations that are possible by marking a field {@code final}. + * Yet, stable values offer greater flexibility as to the timing of initialization *

    - * Consider the following example with a stable value "{@code VAL}" which - * here is an immutable holder of a value of type {@linkplain Integer} and that is - * initially created as unset, which means it holds no value. Later, the - * holder value is set to {@code 42} (unless it was not previously set). - * Once set, the holder value of "{@code VAL}" can be retrieved via the method - * {@linkplain StableValue#orElseThrow()}. - * + * Consider the following example with a stable value "{@code logger}" which here is an + * immutable holder of a value of type {@code Logger} and that is initially created as + * unset, which means it holds no value. Later in the example, the holder value + * is set: * {@snippet lang = java: - * // Creates a new stable value with no holder value - * // @link substring="of" target="#of" : - * static final StableValue VAL = StableValue.of(); + * class Component { * - * // ... logic that may or may not set the holder value of `VAL` + * // Creates a new stable value with no holder value + * // @link substring="of" target="#of" : + * private final StableValue logger = StableValue.of(); * - * // @link substring="trySet(42)" target="#trySet(Object)" - * if (VAL.trySet(42)) { - * System.out.println("The holder value was set to 42."); - * } else { - * // @link substring="orElseThrow" target="#orElseThrow" - * System.out.println("The holder value was already set to " + VAL.orElseThrow()); - * } + * Logger getLogger() { + * if (!logger.isSet()) { + * logger.trySet(Logger.create(Component.class)); + * } + * return logger.orThrow(); + * } + * + * void process() { + * logger.get().info("Process started"); + * // ... + * } + * } *} *

    * Note that the holder value can only be set at most once, even when several threads are * racing to set the holder value. Only one thread is selected as the winner. *

    - * In this other example, a stable "{@code logger}" field is declared and is accessed - * via a {@code getLogger} method in which the holder value is lazily computed via a - * provided lambda expression: - * {@snippet lang = java: - * class Component { + * While this more low-level approach works, it does not guarantee that only one Logger + * is ever created. This can be fixed by using the {@linkplain #computeIfUnset(Supplier)} + * method where the holder is lazily computed using a provided lambda expression: * - * // @link substring="of" target="#of" : - * private final StableValue logger = StableValue.of(); + * {@snippet lang = java: + * class Component { * - * private Logger getLogger() { - * return logger.computeIfUnset( () -> Logger.getLogger("org.app.Component") ); - * } + * // Creates a new stable value with no holder value + * // @link substring="of" target="#of" : + * private final StableValue logger = StableValue.of(); * - * void process() { - * getLogger().info("Process started"); - * // ... - * } + * Logger getLogger() { + * return logger.computeIfUnset(() -> Logger.create(Component.class)); + * } * + * void process() { + * logger.get().info("Process started"); + * // ... * } + * } *} + *

    * The {@code getLogger()} method calls {@code logger.computeIfUnset()} on the * stable value to retrieve its holder value. If the stable value is unset, then * {@code computeIfUnset()} evaluates and sets the holder value; the holder value is then @@ -139,7 +140,7 @@ * * private final Supplier logger = * // @link substring="ofSupplier" target="#ofSupplier(Supplier)" : - * StableValue.ofSupplier( () -> Logger.getLogger("org.app.Component") ); + * StableValue.ofSupplier( () -> Logger.getLogger(Component.class) ); * * void process() { * logger.get().info("Process started"); @@ -206,23 +207,23 @@ * A stable list is similar to a stable int function but provides a full * implementation of an immutable {@linkplain List}. This is useful when interacting with * collection-based methods. Here is an example of how a stable list can be used to hold - * a pool of order controllers: + * a pool of {@code Component} objects: * {@snippet lang = java: * class Application { * static final int POOL_SIZE = 16; * - * static final List ORDER_CONTROLLERS = + * static final List COMPONENTS = * // @link substring="ofList" target="#ofList(int,IntFunction)" : - * StableValue.ofList(POOL_SIZE, OrderController::new); + * StableValue.ofList(POOL_SIZE, Component::new); * - * public static OrderController orderController() { + * public static Component component() { * long index = Thread.currentThread().threadId() % POOL_SIZE; - * return ORDER_CONTROLLERS.get((int) index); + * return COMPONENTS.get((int) index); * } * } - * } + *} *

    - * Note: In the example above, there is a constructor in the {@code OrderController} + * Note: In the example above, there is a constructor in the {@code Component} * class that takes an {@code int} parameter. *

    * A stable map is similar to a stable function but provides a full @@ -242,39 +243,12 @@ * * } *} - *

    Constant Folding

    - * Once a stable value is set, the holder value is eligible for - * constant-folding optimizations provided the following requirements are - * fulfilled: - *
      - *
    • There is a trusted constant root in the form of a {@code static final} field.
    • - *
    • There is a path from the constant root to said set holder value via - * one or more trusted edges where each trusted edge can be either: - *
        - *
      • A component in a {@linkplain Record}.
      • - *
      • A {@code final} field of declared type {@linkplain StableValue} in any class,
      • - *
      • An element from a {@code final} field of declared type {@linkplain StableValue StableValue[]} in any class.
      • - *
      • A {@code final} field in a hidden class.
      • - *
      - *
    • - *
    - * It is expected that the types of trusted edges in the JDK will increase - * over time and that eventually, all {@code final} fields will be trusted. - * - *

    Memory Consistency Properties

    - * All stores made to a holder value before being set are guaranteed to be seen by - * any thread that obtains the holder value via a stable value. - *

    - * More formally, the action of attempting to interact (i.e. via load or store operations) - * with a StableValue's holder value (e.g. via {@link StableValue#trySet} or - * {@link StableValue#orElseThrow()}) forms a - * happens-before - * relation between any other action of attempting to interact with the - * StableValue's holder value. The same happens-before guarantees extend to - * stable functions and stable collections; any action of attempting to interact via a - * valid input value {@code I} happens-before any subsequent action of attempting - * to interact via a valid input value {@code J} if, and only if, {@code I} and {@code J} - * {@linkplain Object#equals(Object) equals()}. + *

    Thread Safety

    + * Updates to an object before it is set as a holder value is guaranteed to be seen by + * all other threads discovering the holder value via a stable value. A holder value is + * guaranteed to only be settable at most once. If competing threads are racing to set + * a holder value, only the first is accepted and the other threads are blocked until the + * holder value is set. * *

    Miscellaneous

    * Except for a StableValue's holder value itself, all method parameters must be @@ -335,8 +309,8 @@ public sealed interface StableValue boolean isSet(); /** - * {@return the holder value; if unset, first attempts to compute the holder value - * using the provided {@code supplier}} + * {@return the holder value; if unset, first attempts to compute and set the + * holder value using the provided {@code supplier}} *

    * The provided {@code supplier} is guaranteed to be invoked at most once if it * completes without throwing an exception. From 3c01c26721f40087c415feaebb409901ec5dbc1c Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 13 Nov 2024 09:34:54 +0100 Subject: [PATCH 151/327] Rename and add factory methods --- .../share/classes/java/lang/StableValue.java | 58 ++++++++++++------- .../lang/stable/StableValueFactories.java | 8 ++- test/jdk/java/lang/StableValue/JepTest.java | 14 ++--- .../lang/StableValue/StableValueTest.java | 41 ++++++++----- .../StableValuesSafePublicationTest.java | 2 +- .../StableValue/TrustedFieldTypeTest.java | 20 +++---- .../lang/stable/CachingSupplierBenchmark.java | 8 +-- .../CustomCachingBiFunctionBenchmark.java | 14 ++--- .../CustomCachingPredicateBenchmark.java | 2 +- .../lang/stable/StableValueBenchmark.java | 20 +++---- 10 files changed, 110 insertions(+), 77 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index be471fb811738..44e697bd48974 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -40,14 +40,15 @@ import java.util.function.Supplier; /** - * A stable value is an object that represents deferred immutable data. + * A stable value is a holder of deferred immutable data. *

    - * A {@linkplain StableValue} is created using the factory method - * {@linkplain StableValue#of()}. When created, a stable value is unset, which - * means it holds no value. It can be set by passing a value to - * {@linkplain #trySet(Object) trySet()}, {@linkplain #setOrThrow(Object) setOrThrow()}, - * or {@linkplain #computeIfUnset(Supplier) computeIfUnset()}. Once set, the value held - * by a {@code StableValue} can never change and can be retrieved by calling + * A {@linkplain StableValue {@code StableValue}} is created using the factory method + * {@linkplain StableValue#empty()}. When created, the stable value is unset, which + * means it holds no value. It's holder value of type {@code T} can be set by + * passing a value to {@linkplain #trySet(Object) trySet()}, + * {@linkplain #setOrThrow(Object) setOrThrow()}, or + * {@linkplain #computeIfUnset(Supplier) computeIfUnset()}. Once set, the value held + * by a {@code StableValue} can never change and can be retrieved by calling * {@linkplain #orElseThrow() orElseThrow()}, {@linkplain #orElse(Object)}, or * {@linkplain #computeIfUnset(Supplier) computeIfUnset()}. *

    @@ -55,16 +56,17 @@ * the same performance optimizations that are possible by marking a field {@code final}. * Yet, stable values offer greater flexibility as to the timing of initialization *

    - * Consider the following example with a stable value "{@code logger}" which here is an - * immutable holder of a value of type {@code Logger} and that is initially created as - * unset, which means it holds no value. Later in the example, the holder value - * is set: + * Consider the following example where a stable value field "{@code logger}" is an + * immutable holder of a value of type {@code Logger} and that is initially created + * as unset, which means it holds no value. Later in the example, the + * state of the "{@code logger}" field is checked and if it is still unset, + * a holder value is set: * {@snippet lang = java: * class Component { * * // Creates a new stable value with no holder value - * // @link substring="of" target="#of" : - * private final StableValue logger = StableValue.of(); + * // @link substring="empty" target="#empty" : + * private final StableValue logger = StableValue.empty(); * * Logger getLogger() { * if (!logger.isSet()) { @@ -83,16 +85,17 @@ * Note that the holder value can only be set at most once, even when several threads are * racing to set the holder value. Only one thread is selected as the winner. *

    - * While this more low-level approach works, it does not guarantee that only one Logger - * is ever created. This can be fixed by using the {@linkplain #computeIfUnset(Supplier)} - * method where the holder is lazily computed using a provided lambda expression: + * While this more low-level approach works, it does not guarantee that only one + * {@code Logger} instance is ever created. This problem can be fixed easily by using the + * {@linkplain #computeIfUnset(Supplier)} method instead, where the holder is lazily + * computed using a provided lambda expression: * * {@snippet lang = java: * class Component { * * // Creates a new stable value with no holder value - * // @link substring="of" target="#of" : - * private final StableValue logger = StableValue.of(); + * // @link substring="empty" target="#empty" : + * private final StableValue logger = StableValue.empty(); * * Logger getLogger() { * return logger.computeIfUnset(() -> Logger.create(Component.class)); @@ -362,7 +365,7 @@ public sealed interface StableValue // Factories /** - * {@return a new stable value with no holder value} + * {@return a new empty stable value with no holder value} *

    * The returned {@linkplain StableValue stable value} is a thin, atomic, thread-safe, * set-at-most-once, stable value with no holder value eligible for certain JVM @@ -370,8 +373,21 @@ public sealed interface StableValue * * @param type of the holder value */ - static StableValue of() { - return StableValueFactories.of(); + static StableValue empty() { + return StableValueFactories.empty(); + } + + /** + * {@return a new set stable value with the provided {@code value} as holder value} + *

    + * The returned {@linkplain StableValue stable value} is a thin, atomic, thread-safe, + * stable value with a set holder value eligible for certain JVM optimizations. + * + * @param value holder value to set + * @param type of the holder value + */ + static StableValue of(T value) { + return StableValueFactories.of(value); } /** diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableValueFactories.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableValueFactories.java index 3c18c461cbb9d..94827edae2b67 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableValueFactories.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableValueFactories.java @@ -17,10 +17,16 @@ private StableValueFactories() {} // Factories - public static StableValueImpl of() { + public static StableValueImpl empty() { return StableValueImpl.newInstance(); } + public static StableValueImpl of(T value) { + final StableValueImpl stableValue = empty(); + stableValue.trySet(value); + return stableValue; + } + public static Supplier ofSupplier(Supplier original) { return StableSupplier.of(original); } diff --git a/test/jdk/java/lang/StableValue/JepTest.java b/test/jdk/java/lang/StableValue/JepTest.java index f1540688e5789..71a5666aad490 100644 --- a/test/jdk/java/lang/StableValue/JepTest.java +++ b/test/jdk/java/lang/StableValue/JepTest.java @@ -132,7 +132,7 @@ public Logger logger(int i) { class Foo { // 1. Declare a Stable field - private static final StableValue LOGGER = StableValue.of(); + private static final StableValue LOGGER = StableValue.empty(); static Logger logger() { @@ -241,7 +241,7 @@ record CachingPredicate(Map> delegate, public CachingPredicate(Set inputs, Predicate original) { this(inputs.stream() - .collect(Collectors.toMap(Function.identity(), _ -> StableValue.of())), + .collect(Collectors.toMap(Function.identity(), _ -> StableValue.empty())), original::test ); } @@ -292,7 +292,7 @@ public R apply(T t, U u) { static CachingBiFunction of(Set> inputs, BiFunction original) { Map, StableValue> map = inputs.stream() - .collect(Collectors.toMap(Function.identity(), _ -> StableValue.of())); + .collect(Collectors.toMap(Function.identity(), _ -> StableValue.empty())); return new CachingBiFunction<>(map, pair -> original.apply(pair.left(), pair.right())); } @@ -315,7 +315,7 @@ public R apply(T t, U u) { static BiFunction of(Set> inputs, BiFunction original) { Map, StableValue> map = inputs.stream() - .collect(Collectors.toMap(Function.identity(), _ -> StableValue.of())); + .collect(Collectors.toMap(Function.identity(), _ -> StableValue.empty())); return new CachingBiFunction2<>(map, original); } @@ -324,7 +324,7 @@ static BiFunction of(Set> inputs, BiFunction LOGGER = StableValue.of(); + private final StableValue LOGGER = StableValue.empty(); public Logger getLogger() { return LOGGER.computeIfUnset(() -> Logger.getLogger("com.company.Foo")); @@ -335,7 +335,7 @@ record CachingIntPredicate(List> outputs, IntPredicate resultFunction) implements IntPredicate { CachingIntPredicate(int size, IntPredicate resultFunction) { - this(Stream.generate(StableValue::of).limit(size).toList(), resultFunction); + this(Stream.generate(StableValue::empty).limit(size).toList(), resultFunction); } @Override @@ -351,7 +351,7 @@ class FixedStableList extends AbstractList { private final StableValue[] elements; FixedStableList(int size) { - this.elements = Stream.generate(StableValue::of) + this.elements = Stream.generate(StableValue::empty) .limit(size) .toArray(StableValue[]::new); } diff --git a/test/jdk/java/lang/StableValue/StableValueTest.java b/test/jdk/java/lang/StableValue/StableValueTest.java index 08d996adbf2ad..0d600bd9d8932 100644 --- a/test/jdk/java/lang/StableValue/StableValueTest.java +++ b/test/jdk/java/lang/StableValue/StableValueTest.java @@ -52,8 +52,19 @@ void trySet() { trySet(null); } + @Test + void preSet() { + StableValue stable = StableValue.of(VALUE); + assertTrue(stable.isSet()); + assertEquals(VALUE, stable.orElseThrow()); + assertEquals(VALUE, stable.orElse(VALUE2)); + assertEquals(VALUE, stable.computeIfUnset(() -> VALUE2)); + assertFalse(stable.trySet(VALUE2)); + assertThrows(IllegalStateException.class, () -> stable.setOrThrow(VALUE2)); + } + void trySet(Integer initial) { - StableValue stable = StableValue.of(); + StableValue stable = StableValue.empty(); assertTrue(stable.trySet(initial)); assertFalse(stable.trySet(null)); assertFalse(stable.trySet(VALUE)); @@ -63,7 +74,7 @@ void trySet(Integer initial) { @Test void orElse() { - StableValue stable = StableValue.of(); + StableValue stable = StableValue.empty(); assertEquals(VALUE, stable.orElse(VALUE)); stable.trySet(VALUE); assertEquals(VALUE, stable.orElse(VALUE2)); @@ -71,7 +82,7 @@ void orElse() { @Test void orElseThrow() { - StableValue stable = StableValue.of(); + StableValue stable = StableValue.empty(); var e = assertThrows(NoSuchElementException.class, stable::orElseThrow); assertEquals("No underlying data set", e.getMessage()); stable.trySet(VALUE); @@ -85,7 +96,7 @@ void isSet() { } void isSet(Integer initial) { - StableValue stable = StableValue.of(); + StableValue stable = StableValue.empty(); assertFalse(stable.isSet()); stable.trySet(initial); assertTrue(stable.isSet()); @@ -94,7 +105,7 @@ void isSet(Integer initial) { @Test void testComputeIfUnsetSupplier() { StableTestUtil.CountingSupplier cs = new StableTestUtil.CountingSupplier<>(() -> VALUE); - StableValue stable = StableValue.of(); + StableValue stable = StableValue.empty(); assertEquals(VALUE, stable.computeIfUnset(cs)); assertEquals(1, cs.cnt()); assertEquals(VALUE, stable.computeIfUnset(cs)); @@ -103,22 +114,22 @@ void testComputeIfUnsetSupplier() { @Test void testHashCode() { - StableValue stableValue = StableValue.of(); + StableValue stableValue = StableValue.empty(); // Should be Object::hashCode assertEquals(System.identityHashCode(stableValue), stableValue.hashCode()); } @Test void testEquals() { - StableValue s0 = StableValue.of(); - StableValue s1 = StableValue.of(); + StableValue s0 = StableValue.empty(); + StableValue s1 = StableValue.empty(); assertNotEquals(s0, s1); // Identity based s0.setOrThrow(42); s1.setOrThrow(42); assertNotEquals(s0, s1); assertNotEquals(s0, "a"); - StableValue null0 = StableValue.of(); - StableValue null1 = StableValue.of(); + StableValue null0 = StableValue.empty(); + StableValue null1 = StableValue.empty(); null0.setOrThrow(null); null1.setOrThrow(null); assertNotEquals(null0, null1); @@ -126,27 +137,27 @@ void testEquals() { @Test void toStringUnset() { - StableValue stable = StableValue.of(); + StableValue stable = StableValue.empty(); assertEquals("StableValue.unset", stable.toString()); } @Test void toStringNull() { - StableValue stable = StableValue.of(); + StableValue stable = StableValue.empty(); assertTrue(stable.trySet(null)); assertEquals("StableValue[null]", stable.toString()); } @Test void toStringNonNull() { - StableValue stable = StableValue.of(); + StableValue stable = StableValue.empty(); assertTrue(stable.trySet(VALUE)); assertEquals("StableValue[" + VALUE + "]", stable.toString()); } @Test void toStringCircular() { - StableValue> stable = StableValue.of(); + StableValue> stable = StableValue.empty(); stable.trySet(stable); String toString = stable.toString(); assertEquals(toString, "(this StableValue)"); @@ -187,7 +198,7 @@ void raceMixed() { void race(BiPredicate, Integer> winnerPredicate) { int noThreads = 10; CountDownLatch starter = new CountDownLatch(1); - StableValue stable = StableValue.of(); + StableValue stable = StableValue.empty(); BitSet winner = new BitSet(noThreads); List threads = IntStream.range(0, noThreads).mapToObj(i -> new Thread(() -> { try { diff --git a/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java b/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java index 3531793ab9bfa..54c82b65865fa 100644 --- a/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java +++ b/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java @@ -52,7 +52,7 @@ static StableValue[] stables() { @SuppressWarnings("unchecked") StableValue[] stables = (StableValue[]) new StableValue[SIZE]; for (int i = 0; i < SIZE; i++) { - stables[i] = StableValue.of(); + stables[i] = StableValue.empty(); } return stables; } diff --git a/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java b/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java index ad8b1baa97618..8bb0d951fe9d2 100644 --- a/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java +++ b/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java @@ -44,10 +44,10 @@ final class TrustedFieldTypeTest { @Test void reflection() throws NoSuchFieldException, IllegalAccessException { final class Holder { - private final StableValue value = StableValue.of(); + private final StableValue value = StableValue.empty(); } final class HolderNonFinal { - private StableValue value = StableValue.of(); + private StableValue value = StableValue.empty(); } final class ArrayHolder { private final StableValue[] array = (StableValue[]) new StableValue[]{}; @@ -60,7 +60,7 @@ final class ArrayHolder { Object read = valueField.get(holder); // We should NOT be able to write to the StableValue field assertThrows(IllegalAccessException.class, () -> - valueField.set(holder, StableValue.of()) + valueField.set(holder, StableValue.empty()) ); Field valueNonFinal = HolderNonFinal.class.getDeclaredField("value"); @@ -68,7 +68,7 @@ final class ArrayHolder { HolderNonFinal holderNonFinal = new HolderNonFinal(); // As the field is not final, both read and write should be ok (not trusted) Object readNonFinal = valueNonFinal.get(holderNonFinal); - valueNonFinal.set(holderNonFinal, StableValue.of()); + valueNonFinal.set(holderNonFinal, StableValue.empty()); Field arrayField = ArrayHolder.class.getDeclaredField("array"); arrayField.setAccessible(true); @@ -89,7 +89,7 @@ void sunMiscUnsafe() throws NoSuchFieldException, IllegalAccessException { sun.misc.Unsafe unsafe = (sun.misc.Unsafe)unsafeField.get(null); final class Holder { - private final StableValue value = StableValue.of(); + private final StableValue value = StableValue.empty(); } final class ArrayHolder { private final StableValue[] array = (StableValue[]) new StableValue[]{}; @@ -107,7 +107,7 @@ final class ArrayHolder { ); // Test direct access - StableValue stableValue = StableValue.of(); + StableValue stableValue = StableValue.empty(); Class clazz = stableValue.getClass(); System.out.println("clazz = " + clazz); assertThrows(NoSuchFieldException.class, () -> clazz.getField("value")); @@ -117,7 +117,7 @@ final class ArrayHolder { void varHandle() throws NoSuchFieldException, IllegalAccessException { MethodHandles.Lookup lookup = MethodHandles.lookup(); - StableValue originalValue = StableValue.of(); + StableValue originalValue = StableValue.empty(); @SuppressWarnings("unchecked") StableValue[] originalArrayValue = new StableValue[10]; @@ -133,11 +133,11 @@ final class ArrayHolder { Holder holder = new Holder(); assertThrows(UnsupportedOperationException.class, () -> - valueVarHandle.set(holder, StableValue.of()) + valueVarHandle.set(holder, StableValue.empty()) ); assertThrows(UnsupportedOperationException.class, () -> - valueVarHandle.compareAndSet(holder, originalValue, StableValue.of()) + valueVarHandle.compareAndSet(holder, originalValue, StableValue.empty()) ); VarHandle arrayVarHandle = lookup.findVarHandle(ArrayHolder.class, "array", StableValue[].class); @@ -155,7 +155,7 @@ final class ArrayHolder { @Test void updateStableValueUnderlyingData() { - StableValue stableValue = StableValue.of(); + StableValue stableValue = StableValue.empty(); stableValue.trySet(42); jdk.internal.misc.Unsafe unsafe = Unsafe.getUnsafe(); diff --git a/test/micro/org/openjdk/bench/java/lang/stable/CachingSupplierBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/CachingSupplierBenchmark.java index 166a78771b6e2..30cd45dd07827 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/CachingSupplierBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/CachingSupplierBenchmark.java @@ -56,13 +56,13 @@ public class CachingSupplierBenchmark { private static final int VALUE = 42; private static final int VALUE2 = 23; - private static final StableValue STABLE = init(StableValue.of(), VALUE); - private static final StableValue STABLE2 = init(StableValue.of(), VALUE2); + private static final StableValue STABLE = init(StableValue.empty(), VALUE); + private static final StableValue STABLE2 = init(StableValue.empty(), VALUE2); private static final Supplier SUPPLIER = StableValue.ofSupplier(() -> VALUE); private static final Supplier SUPPLIER2 = StableValue.ofSupplier(() -> VALUE); - private final StableValue stable = init(StableValue.of(), VALUE); - private final StableValue stable2 = init(StableValue.of(), VALUE2); + private final StableValue stable = init(StableValue.empty(), VALUE); + private final StableValue stable2 = init(StableValue.empty(), VALUE2); private final Supplier supplier = StableValue.ofSupplier(() -> VALUE); private final Supplier supplier2 = StableValue.ofSupplier(() -> VALUE2); diff --git a/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingBiFunctionBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingBiFunctionBenchmark.java index 970c7cbfe9127..d94c8ce0feea3 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingBiFunctionBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingBiFunctionBenchmark.java @@ -90,8 +90,8 @@ public class CustomCachingBiFunctionBenchmark { private static final BiFunction function = cachingBiFunction(SET, ORIGINAL);; private static final BiFunction function2 = cachingBiFunction(SET, ORIGINAL);; - private static final StableValue STABLE_VALUE = StableValue.of(); - private static final StableValue STABLE_VALUE2 = StableValue.of(); + private static final StableValue STABLE_VALUE = StableValue.empty(); + private static final StableValue STABLE_VALUE2 = StableValue.empty(); static { STABLE_VALUE.trySet(ORIGINAL.apply(VALUE, VALUE2)); @@ -153,7 +153,7 @@ record CachingBiFunction( public CachingBiFunction(Set> inputs, BiFunction original) { this(Map.copyOf(inputs.stream() - .collect(Collectors.toMap(Function.identity(), _ -> StableValue.of()))), + .collect(Collectors.toMap(Function.identity(), _ -> StableValue.empty()))), original ); } @@ -196,8 +196,8 @@ static Map>> delegate(Set>> map = inputs.stream() .collect(Collectors.groupingBy(Pair::left, Collectors.groupingBy(Pair::right, - Collectors.mapping((Function, ? extends StableValue>) _ -> StableValue.of(), - Collectors.reducing(StableValue.of(), _ -> StableValue.of(), (StableValue a, StableValue b) -> a))))); + Collectors.mapping((Function, ? extends StableValue>) _ -> StableValue.empty(), + Collectors.reducing(StableValue.empty(), _ -> StableValue.empty(), (StableValue a, StableValue b) -> a))))); @SuppressWarnings("unchecked") Map>> copy = Map.ofEntries(map.entrySet().stream() @@ -248,8 +248,8 @@ static Map> delegate(Set>> map = inputs.stream() .collect(Collectors.groupingBy(Pair::left, Collectors.groupingBy(Pair::right, - Collectors.mapping((Function, ? extends StableValue>) _ -> StableValue.of(), - Collectors.reducing(StableValue.of(), _ -> StableValue.of(), (StableValue a, StableValue b) -> b))))); + Collectors.mapping((Function, ? extends StableValue>) _ -> StableValue.empty(), + Collectors.reducing(StableValue.empty(), _ -> StableValue.empty(), (StableValue a, StableValue b) -> b))))); @SuppressWarnings("unchecked") Map> copy = Map.ofEntries(map.entrySet().stream() diff --git a/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingPredicateBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingPredicateBenchmark.java index e09ef94cea7b0..465e480e25ef2 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingPredicateBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingPredicateBenchmark.java @@ -95,7 +95,7 @@ record CachingPredicate(Map> delegate, public CachingPredicate(Set inputs, Predicate original) { this(inputs.stream() - .collect(Collectors.toUnmodifiableMap(Function.identity(), _ -> StableValue.of())), + .collect(Collectors.toUnmodifiableMap(Function.identity(), _ -> StableValue.empty())), original ); } diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java index d8eec559e4e2c..75c6e83636d42 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java @@ -47,19 +47,19 @@ public class StableValueBenchmark { private static final int VALUE = 42; private static final int VALUE2 = 23; - private static final StableValue STABLE = init(StableValue.of(), VALUE); - private static final StableValue STABLE2 = init(StableValue.of(), VALUE2); - private static final StableValue DCL = init(StableValue.of(), VALUE); - private static final StableValue DCL2 = init(StableValue.of(), VALUE2); + private static final StableValue STABLE = init(StableValue.empty(), VALUE); + private static final StableValue STABLE2 = init(StableValue.empty(), VALUE2); + private static final StableValue DCL = init(StableValue.empty(), VALUE); + private static final StableValue DCL2 = init(StableValue.empty(), VALUE2); private static final AtomicReference ATOMIC = new AtomicReference<>(VALUE); private static final AtomicReference ATOMIC2 = new AtomicReference<>(VALUE2); private static final Holder HOLDER = new Holder(VALUE); private static final Holder HOLDER2 = new Holder(VALUE2); - private final StableValue stable = init(StableValue.of(), VALUE); - private final StableValue stable2 = init(StableValue.of(), VALUE2); - private final StableValue stableNull = StableValue.of(); - private final StableValue stableNull2 = StableValue.of(); + private final StableValue stable = init(StableValue.empty(), VALUE); + private final StableValue stable2 = init(StableValue.empty(), VALUE2); + private final StableValue stableNull = StableValue.empty(); + private final StableValue stableNull2 = StableValue.empty(); private final Supplier dcl = new Dcl<>(() -> VALUE); private final Supplier dcl2 = new Dcl<>(() -> VALUE2); private final AtomicReference atomic = new AtomicReference<>(VALUE); @@ -78,7 +78,7 @@ public void setup() { final int v = i; Dcl dclX = new Dcl<>(() -> v); sum += dclX.get(); - StableValue stableX = StableValue.of(); + StableValue stableX = StableValue.empty(); stableX.trySet(i); sum += stableX.orElseThrow(); } @@ -141,7 +141,7 @@ private static StableValue init(StableValue m, Integer value) // because StableValue fields have a special meaning. private static final class Holder { - private final StableValue delegate = StableValue.of(); + private final StableValue delegate = StableValue.empty(); Holder(int value) { delegate.setOrThrow(value); From 4c31beb73ea12f7e1b0c1d46f76e26dd72088524 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 13 Nov 2024 09:49:47 +0100 Subject: [PATCH 152/327] Update docs slightly --- src/java.base/share/classes/java/lang/StableValue.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 44e697bd48974..bc6a209ab354c 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -43,13 +43,13 @@ * A stable value is a holder of deferred immutable data. *

    * A {@linkplain StableValue {@code StableValue}} is created using the factory method - * {@linkplain StableValue#empty()}. When created, the stable value is unset, which - * means it holds no value. It's holder value of type {@code T} can be set by - * passing a value to {@linkplain #trySet(Object) trySet()}, + * {@linkplain StableValue#empty()}. When created, the stable value is unset, + * which means it holds no value. It's holder value of type {@code T} can be set + * by passing a value via {@linkplain #trySet(Object) trySet()}, * {@linkplain #setOrThrow(Object) setOrThrow()}, or * {@linkplain #computeIfUnset(Supplier) computeIfUnset()}. Once set, the value held * by a {@code StableValue} can never change and can be retrieved by calling - * {@linkplain #orElseThrow() orElseThrow()}, {@linkplain #orElse(Object)}, or + * {@linkplain #orElseThrow() orElseThrow()}, {@linkplain #orElse(Object) orElse()}, or * {@linkplain #computeIfUnset(Supplier) computeIfUnset()}. *

    * A stable value that is set is treated as a constant by the JVM, enabling From db754f150a14f66045b6ed4d061d02c990cbd00e Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 13 Nov 2024 14:01:32 +0100 Subject: [PATCH 153/327] Change docs after comments --- .../share/classes/java/lang/StableValue.java | 68 +++++++++---------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index bc6a209ab354c..43f3d0291baa7 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -30,6 +30,7 @@ import jdk.internal.lang.stable.StableValueFactories; import java.io.Serializable; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; @@ -61,6 +62,7 @@ * as unset, which means it holds no value. Later in the example, the * state of the "{@code logger}" field is checked and if it is still unset, * a holder value is set: + * * {@snippet lang = java: * class Component { * @@ -82,13 +84,12 @@ * } *} *

    - * Note that the holder value can only be set at most once, even when several threads are - * racing to set the holder value. Only one thread is selected as the winner. + * Note that the holder value can only be set at most once. *

    - * While this more low-level approach works, it does not guarantee that only one - * {@code Logger} instance is ever created. This problem can be fixed easily by using the - * {@linkplain #computeIfUnset(Supplier)} method instead, where the holder is lazily - * computed using a provided lambda expression: + * To guarantee that only one instance of {@code Logger} instance is ever created, the + * {@linkplain #computeIfUnset(Supplier) computeIfUnset()} method can be used instead as + * shown in this improved example, where the holder is atomically and lazily computed via + * a lambda expression: * * {@snippet lang = java: * class Component { @@ -98,7 +99,7 @@ * private final StableValue logger = StableValue.empty(); * * Logger getLogger() { - * return logger.computeIfUnset(() -> Logger.create(Component.class)); + * return logger.computeIfUnset( () -> Logger.create(Component.class) ); * } * * void process() { @@ -127,17 +128,12 @@ * passed as a method parameter. * *

    Stable Functions

    - * Stable functions are thread-safe, caching, and lazily computed functions that, for each - * allowed input (if any), record the values of some original function upon being first - * accessed via the stable function's sole abstract method. - *

    - * All the stable functions guarantee the provided original function is invoked - * successfully at most once per valid input even in a multithreaded environment. + * Stable functions are backed by one or more {@code StableValue} objects that cache + * results computed as a result of initial invocations. *

    - * A stable supplier allows a more convenient construct compared to a - * {@linkplain StableValue} in the sense that a field declaration can also specify how - * the holder value of a stable value is to be set, but without actually setting its - * holder value upfront: + * A stable supplier is backed by a single {@code StableValue} and allows clients + * to specify how the holder value of the stable value is to be set, but without actually + * setting its holder value upfront: * {@snippet lang = java: * class Component { * @@ -154,7 +150,7 @@ * This also allows the stable supplier to be accessed directly, without going through * an accessor method like {@code getLogger()} in the previous example. *

    - * A stable int function stores values in an array of stable values where + * A stable int function is backed by an array of stable values where * the elements' holder value are computed the first time a particular input value * is provided. * When the stable int function is first created -- @@ -177,14 +173,14 @@ * } *} *

    - * A stable function stores values in an array of stable values where + * A stable function is backed by an array of stable values where * the elements' holder value are computed the first time a particular input value * is provided. * When the stable function is first created -- * via the {@linkplain StableValue#ofFunction(Set, Function) StableValue.ofFunction()} - * factory -- the input Set is specified together with an original {@linkplain Function} - * which is invoked at most once per input value. In effect, the stable function will act - * like a cache for the original {@code Function}: + * factory -- the input {@linkplain Set} is specified together with an original + * {@linkplain Function} which is invoked at most once per input value. In effect, the + * stable function will act like a cache for the original {@code Function}: * {@snippet lang = java: * class SqrtUtil { * @@ -200,12 +196,9 @@ *} * *

    Stable Collections

    - * Stable collections are thread-safe, caching, lazily computed, shallowly immutable - * collections that, for each allowed input, record the values of some original mapper - * upon being first accessed (directly or indirectly) via the collection's get method. - *

    - * All the stable collections guarantee the provided original mapper is invoked - * successfully at most once per valid input even in a multithreaded environment. + * Stable collections are similar to {@linkplain ##stable-functions stable functions} but + * provides additional support for the collection specific methods such as + * {@linkplain Collection#size()}. *

    * A stable list is similar to a stable int function but provides a full * implementation of an immutable {@linkplain List}. This is useful when interacting with @@ -247,11 +240,18 @@ * } *} *

    Thread Safety

    - * Updates to an object before it is set as a holder value is guaranteed to be seen by - * all other threads discovering the holder value via a stable value. A holder value is - * guaranteed to only be settable at most once. If competing threads are racing to set - * a holder value, only the first is accepted and the other threads are blocked until the - * holder value is set. + * All stable value holders, functions, and collections are thread safe. + *

    + * Updates to an object before it is set as a holder value is guaranteed to be seen by all + * other threads discovering the holder value via a stable value. + * + * A holder value is guaranteed to only be settable at most once. If competing threads are + * racing to set a holder value, only the first is accepted and the other threads are + * blocked until the holder value is set. + * + * All the stable functions and collections guarantee the provided original function + * is invoked successfully at most once per valid input even in a multithreaded + * environment. * *

    Miscellaneous

    * Except for a StableValue's holder value itself, all method parameters must be @@ -425,7 +425,7 @@ static Supplier ofSupplier(Supplier original) { *

    * The provided {@code original} int function is guaranteed to be successfully invoked * at most once per allowed input, even in a multi-threaded environment. Competing - * threads invoking the returned inr function's + * threads invoking the returned int function's * {@linkplain IntFunction#apply(int) apply()} method when a value is already under * computation will block until a value is computed or an exception is thrown by * the computing thread. From 6aa9775a744c044ee518482b2a08ab7ef4e20368 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 13 Nov 2024 15:40:05 +0100 Subject: [PATCH 154/327] Make factory method link contain class name --- src/java.base/share/classes/java/lang/StableValue.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 43f3d0291baa7..2f296869ad349 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -44,11 +44,11 @@ * A stable value is a holder of deferred immutable data. *

    * A {@linkplain StableValue {@code StableValue}} is created using the factory method - * {@linkplain StableValue#empty()}. When created, the stable value is unset, - * which means it holds no value. It's holder value of type {@code T} can be set - * by passing a value via {@linkplain #trySet(Object) trySet()}, - * {@linkplain #setOrThrow(Object) setOrThrow()}, or - * {@linkplain #computeIfUnset(Supplier) computeIfUnset()}. Once set, the value held + * {@linkplain StableValue#empty() {@code StableValue.empty()}}. When created, the + * stable value is unset, which means it holds no value. It's holder value of + * type {@code T} can be set by passing a value via + * {@linkplain #trySet(Object) trySet()}, {@linkplain #setOrThrow(Object) setOrThrow()}, + * or {@linkplain #computeIfUnset(Supplier) computeIfUnset()}. Once set, the value held * by a {@code StableValue} can never change and can be retrieved by calling * {@linkplain #orElseThrow() orElseThrow()}, {@linkplain #orElse(Object) orElse()}, or * {@linkplain #computeIfUnset(Supplier) computeIfUnset()}. From 905af9790c1b83dcb3c85613b92127d81e587889 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Thu, 14 Nov 2024 10:59:26 +0100 Subject: [PATCH 155/327] Improve JavaDocs --- .../share/classes/java/lang/StableValue.java | 95 +++++++++---------- 1 file changed, 46 insertions(+), 49 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 2f296869ad349..eaf8e148e5083 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -122,18 +122,15 @@ * This property is crucial as evaluation of the lambda expression may have side effects, * e.g., the call above to {@code Logger.getLogger()} may result in storage resources * being prepared. - *

    - * A {@linkplain StableValue} is mainly intended to be a member of a holding class (as - * shown in the examples above) and is usually neither exposed directly via accessors nor - * passed as a method parameter. * *

    Stable Functions

    - * Stable functions are backed by one or more {@code StableValue} objects that cache - * results computed as a result of initial invocations. - *

    - * A stable supplier is backed by a single {@code StableValue} and allows clients - * to specify how the holder value of the stable value is to be set, but without actually - * setting its holder value upfront: + * Stable values provide the foundation for higher-level functional abstractions. A + * stable supplier is a supplier that computes a value and then caches it into + * a backing stable value storage for later use. A stable supplier is created -- via the + * {@linkplain StableValue#ofSupplier(Supplier) StableValue.ofSupplier()} factory -- + * by providing an original {@linkplain Supplier} which is invoked when the + * stable supplier is first accessed: + * * {@snippet lang = java: * class Component { * @@ -150,15 +147,14 @@ * This also allows the stable supplier to be accessed directly, without going through * an accessor method like {@code getLogger()} in the previous example. *

    - * A stable int function is backed by an array of stable values where - * the elements' holder value are computed the first time a particular input value - * is provided. - * When the stable int function is first created -- - * via the {@linkplain StableValue#ofIntFunction(int, IntFunction) - * StableValue.ofIntFunction()} factory -- the input range (i.e. {@code [0, size)}) is - * specified together with an original {@linkplain IntFunction} which is invoked - * at most once per input value. In effect, the stable int function will act like a cache - * for the original {@code IntFunction}: + * A stable int function is a function that takes an {@code int} parameter and + * uses it to compute a result that is then cached into the backing stable value storage + * for that parameter value. When the stable int function is first created -- via the + * {@linkplain StableValue#ofIntFunction(int, IntFunction) StableValue.ofIntFunction()} + * factory -- the input range (i.e. [0, size)) is specified together with an original + * {@linkplain IntFunction} which is invoked at most once per input value. In effect, + * the stable int function will act like a cache for the original {@linkplain IntFunction}: + * * {@snippet lang = java: * class SqrtUtil { * @@ -173,14 +169,14 @@ * } *} *

    - * A stable function is backed by an array of stable values where - * the elements' holder value are computed the first time a particular input value - * is provided. - * When the stable function is first created -- - * via the {@linkplain StableValue#ofFunction(Set, Function) StableValue.ofFunction()} - * factory -- the input {@linkplain Set} is specified together with an original - * {@linkplain Function} which is invoked at most once per input value. In effect, the - * stable function will act like a cache for the original {@code Function}: + * A stable function is a function that takes a parameter (of type {@code T}) and + * uses it to compute a result that is then cached into the backing stable value storage + * for that parameter value. When the stable function is first created -- via the + * {@linkplain StableValue#ofFunction(Set, Function) StableValue.ofFunction()} factory -- + * the input {@linkplain Set} is specified together with an original {@linkplain Function} + * which is invoked at most once per input value. In effect, the stable function will act + * like a cache for the original {@linkplain Function}: + * * {@snippet lang = java: * class SqrtUtil { * @@ -196,14 +192,11 @@ *} * *

    Stable Collections

    - * Stable collections are similar to {@linkplain ##stable-functions stable functions} but - * provides additional support for the collection specific methods such as - * {@linkplain Collection#size()}. - *

    - * A stable list is similar to a stable int function but provides a full - * implementation of an immutable {@linkplain List}. This is useful when interacting with - * collection-based methods. Here is an example of how a stable list can be used to hold - * a pool of {@code Component} objects: + * Stable values can also be used as backing storage for immutable collections. + * A stable list is an immutable list of fixed size, backed by an array of + * stable values. The stable list elements are computed when they are first accessed, + * using a provided {@linkplain IntFunction}: + * * {@snippet lang = java: * class Application { * static final int POOL_SIZE = 16; @@ -222,10 +215,10 @@ * Note: In the example above, there is a constructor in the {@code Component} * class that takes an {@code int} parameter. *

    - * A stable map is similar to a stable function but provides a full - * implementation of an immutable {@linkplain Map}. This is useful when interacting with - * collection-based methods. Here is how a stable map can be used as a cache for - * square roots for certain input values given at creation: + * Similarly, a stable map is an immutable map whose keys are known at + * construction. The stable map values are computed when they are first accessed, + * using a provided {@linkplain Function}: + * * {@snippet lang = java: * class SqrtUtil { * @@ -240,18 +233,19 @@ * } *} *

    Thread Safety

    - * All stable value holders, functions, and collections are thread safe. - *

    - * Updates to an object before it is set as a holder value is guaranteed to be seen by all - * other threads discovering the holder value via a stable value. - * * A holder value is guaranteed to only be settable at most once. If competing threads are * racing to set a holder value, only the first is accepted and the other threads are * blocked until the holder value is set. - * - * All the stable functions and collections guarantee the provided original function - * is invoked successfully at most once per valid input even in a multithreaded - * environment. + *

    + * Updates to an object + * happens-before + * the object is observed via a stable value. + *

    + * The method {@linkplain StableValue#computeIfUnset(Supplier) computeIfUnset()} + * guarantees that the provided {@linkplain Supplier} is invoked successfully at most + * once even under race. Since stable functions and stable collections are built on top + * of {@linkplain StableValue#computeIfUnset(Supplier) computeIfUnset()} they too are + * thread safe and guarantee at-most-once-per-input invocation. * *

    Miscellaneous

    * Except for a StableValue's holder value itself, all method parameters must be @@ -263,7 +257,10 @@ * do this may lead to deadlock. Stable functions and collections on the * other hand are guaranteed not to synchronize on {@code this}. * - * @implNote Instance fields explicitly declared as {@code StableValue} or one-dimensional + * @implNote A {@linkplain StableValue} is mainly intended to be a non-public field in + * a class and is usually neither exposed directly via accessors nor passed as + * a method parameter. + * Instance fields explicitly declared as {@code StableValue} or one-dimensional * arrays thereof are eligible for certain JVM optimizations where normal * instance fields are not. This comes with restrictions on reflective * modifications. Although most ways of reflective modification of such fields From 09ae146821c8612c2813a36780efb770d1d0f89b Mon Sep 17 00:00:00 2001 From: Maurizio Cimadamore <54672762+mcimadamore@users.noreply.github.com> Date: Thu, 14 Nov 2024 12:47:47 +0000 Subject: [PATCH 156/327] Update StableValue.java --- .../share/classes/java/lang/StableValue.java | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index eaf8e148e5083..c0d68eb54915f 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -45,17 +45,19 @@ *

    * A {@linkplain StableValue {@code StableValue}} is created using the factory method * {@linkplain StableValue#empty() {@code StableValue.empty()}}. When created, the - * stable value is unset, which means it holds no value. It's holder value of - * type {@code T} can be set by passing a value via + * stable value is unset, which means it holds no value. Its holder value, of + * type {@code T}, can be set by calling * {@linkplain #trySet(Object) trySet()}, {@linkplain #setOrThrow(Object) setOrThrow()}, - * or {@linkplain #computeIfUnset(Supplier) computeIfUnset()}. Once set, the value held - * by a {@code StableValue} can never change and can be retrieved by calling + * or {@linkplain #computeIfUnset(Supplier) computeIfUnset()}. Once set, the holder value + * can never change and can be retrieved by calling * {@linkplain #orElseThrow() orElseThrow()}, {@linkplain #orElse(Object) orElse()}, or * {@linkplain #computeIfUnset(Supplier) computeIfUnset()}. *

    - * A stable value that is set is treated as a constant by the JVM, enabling - * the same performance optimizations that are possible by marking a field {@code final}. - * Yet, stable values offer greater flexibility as to the timing of initialization + * A stable value that is set is treated as a constant by the JVM, enabling the + * same performance optimizations that are available for {@code final} fields. + * As such, stable values can be used to replace {@code final} fields in cases where + * at-most-once update semantics is crucial, but where the eager initialization semantics + * associated with {@code final} fields is too restrictive. *

    * Consider the following example where a stable value field "{@code logger}" is an * immutable holder of a value of type {@code Logger} and that is initially created @@ -86,10 +88,9 @@ *

    * Note that the holder value can only be set at most once. *

    - * To guarantee that only one instance of {@code Logger} instance is ever created, the - * {@linkplain #computeIfUnset(Supplier) computeIfUnset()} method can be used instead as - * shown in this improved example, where the holder is atomically and lazily computed via - * a lambda expression: + * To guarantee that, even under races, only one instance of {@code Logger} is ever created, the + * {@linkplain #computeIfUnset(Supplier) computeIfUnset()} method can be used instead, + * where the holder is atomically and lazily computed via a lambda expression: * * {@snippet lang = java: * class Component { @@ -115,8 +116,6 @@ * returned to the client. In other words, {@code computeIfUnset()} guarantees that a * stable value's holder value is set before it is used. *

    - * Even though the stable value, once set, is immutable, its holder value is not - * required to be set upfront. Rather, it can be set on demand. * Furthermore, {@code computeIfUnset()} guarantees that the lambda expression provided is * evaluated only once, even when {@code logger.computeIfUnset()} is invoked concurrently. * This property is crucial as evaluation of the lambda expression may have side effects, @@ -126,8 +125,8 @@ *

    Stable Functions

    * Stable values provide the foundation for higher-level functional abstractions. A * stable supplier is a supplier that computes a value and then caches it into - * a backing stable value storage for later use. A stable supplier is created -- via the - * {@linkplain StableValue#ofSupplier(Supplier) StableValue.ofSupplier()} factory -- + * a backing stable value storage for later use. A stable supplier is created via the + * {@linkplain StableValue#ofSupplier(Supplier) StableValue.ofSupplier()} factory, * by providing an original {@linkplain Supplier} which is invoked when the * stable supplier is first accessed: * @@ -144,14 +143,15 @@ * } * } *} - * This also allows the stable supplier to be accessed directly, without going through - * an accessor method like {@code getLogger()} in the previous example. + * A stable supplier encapsulates access to its backing stable value storage. This means that + * code inside {@code Component} can obtain the logger object directly from the stable supplier, + * without having to go through an accessor method like {@code getLogger()}. *

    * A stable int function is a function that takes an {@code int} parameter and * uses it to compute a result that is then cached into the backing stable value storage - * for that parameter value. When the stable int function is first created -- via the + * for that parameter value. A stable int function is created via the * {@linkplain StableValue#ofIntFunction(int, IntFunction) StableValue.ofIntFunction()} - * factory -- the input range (i.e. [0, size)) is specified together with an original + * factory. Upon creation, the input range (i.e. [0, size)) is specified together with an original * {@linkplain IntFunction} which is invoked at most once per input value. In effect, * the stable int function will act like a cache for the original {@linkplain IntFunction}: * @@ -171,9 +171,9 @@ *

    * A stable function is a function that takes a parameter (of type {@code T}) and * uses it to compute a result that is then cached into the backing stable value storage - * for that parameter value. When the stable function is first created -- via the - * {@linkplain StableValue#ofFunction(Set, Function) StableValue.ofFunction()} factory -- - * the input {@linkplain Set} is specified together with an original {@linkplain Function} + * for that parameter value. A stable function is created via the + * {@linkplain StableValue#ofFunction(Set, Function) StableValue.ofFunction()} factory. + * Upon creation, the input {@linkplain Set} is specified together with an original {@linkplain Function} * which is invoked at most once per input value. In effect, the stable function will act * like a cache for the original {@linkplain Function}: * @@ -193,7 +193,7 @@ * *

    Stable Collections

    * Stable values can also be used as backing storage for immutable collections. - * A stable list is an immutable list of fixed size, backed by an array of + * A stable list is an immutable list, backed by an array of * stable values. The stable list elements are computed when they are first accessed, * using a provided {@linkplain IntFunction}: * @@ -233,9 +233,9 @@ * } *} *

    Thread Safety

    - * A holder value is guaranteed to only be settable at most once. If competing threads are - * racing to set a holder value, only the first is accepted and the other threads are - * blocked until the holder value is set. + * A holder value is guaranteed to be set at most once. If competing threads are + * racing to set a stable value, only one update succeeds, while other updates are + * blocked until the stable value becomes set. *

    * Updates to an object * happens-before From a4e926da27be10027fd4b20323e35f5fea043f52 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Thu, 14 Nov 2024 14:25:01 +0100 Subject: [PATCH 157/327] Update lazy list example --- .../share/classes/java/lang/StableValue.java | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index eaf8e148e5083..0e279634f55db 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -198,18 +198,17 @@ * using a provided {@linkplain IntFunction}: * * {@snippet lang = java: - * class Application { - * static final int POOL_SIZE = 16; + * class SqrtUtil { * - * static final List COMPONENTS = - * // @link substring="ofList" target="#ofList(int,IntFunction)" : - * StableValue.ofList(POOL_SIZE, Component::new); + * private static final List SQRT = + * // @link substring="ofList" target="#ofList(int,IntFunction)" : + * StableValue.ofList(10, Math::sqrt); * - * public static Component component() { - * long index = Thread.currentThread().threadId() % POOL_SIZE; - * return COMPONENTS.get((int) index); - * } - * } + * double sqrt9() { + * return SQRT.apply(9); // Constant folds to 3.0 + * } + * + * } *} *

    * Note: In the example above, there is a constructor in the {@code Component} From da0c8d1060e899bb8e48768f63b3450c8a6ba4ab Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Thu, 14 Nov 2024 14:37:48 +0100 Subject: [PATCH 158/327] Update happens-before text --- src/java.base/share/classes/java/lang/StableValue.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 21bd63b8f8130..b8a3f34fbed91 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -236,9 +236,9 @@ * racing to set a stable value, only one update succeeds, while other updates are * blocked until the stable value becomes set. *

    - * Updates to an object + * The one-and-only write operation on a stable value (e.g. {@linkplain #trySet(Object)}) * happens-before - * the object is observed via a stable value. + * any subsequent read operation (e.g. {@linkplain #orElseThrow()}). *

    * The method {@linkplain StableValue#computeIfUnset(Supplier) computeIfUnset()} * guarantees that the provided {@linkplain Supplier} is invoked successfully at most From 122168664f298559e2af94ed0b840403221eb4e4 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Fri, 15 Nov 2024 09:00:57 +0100 Subject: [PATCH 159/327] Update minor doc issue --- src/java.base/share/classes/java/lang/StableValue.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index b8a3f34fbed91..f38efe4fd7036 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -236,7 +236,7 @@ * racing to set a stable value, only one update succeeds, while other updates are * blocked until the stable value becomes set. *

    - * The one-and-only write operation on a stable value (e.g. {@linkplain #trySet(Object)}) + * The at-most-once write operation on a stable value (e.g. {@linkplain #trySet(Object)}) * happens-before * any subsequent read operation (e.g. {@linkplain #orElseThrow()}). *

    From e71a4a12cfaaf7db734514998d08ee4ee56dcc7a Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 18 Nov 2024 10:34:02 +0100 Subject: [PATCH 160/327] Update docs and rename benchmarks --- .../share/classes/java/lang/StableValue.java | 116 +++++++++--------- .../lang/stable/StableValueFactories.java | 4 +- test/jdk/java/lang/StableValue/JepTest.java | 14 +-- .../lang/StableValue/StableValueTest.java | 30 ++--- .../StableValuesSafePublicationTest.java | 2 +- .../StableValue/TrustedFieldTypeTest.java | 20 +-- ...a => CustomStableBiFunctionBenchmark.java} | 20 +-- ...ctions.java => CustomStableFunctions.java} | 4 +- ...va => CustomStablePredicateBenchmark.java} | 6 +- ...mark.java => StableFunctionBenchmark.java} | 2 +- ...k.java => StableIntFunctionBenchmark.java} | 2 +- ...mark.java => StableSupplierBenchmark.java} | 10 +- .../lang/stable/StableValueBenchmark.java | 20 +-- 13 files changed, 124 insertions(+), 126 deletions(-) rename test/micro/org/openjdk/bench/java/lang/stable/{CustomCachingBiFunctionBenchmark.java => CustomStableBiFunctionBenchmark.java} (96%) rename test/micro/org/openjdk/bench/java/lang/stable/{CustomCachingFunctions.java => CustomStableFunctions.java} (98%) rename test/micro/org/openjdk/bench/java/lang/stable/{CustomCachingPredicateBenchmark.java => CustomStablePredicateBenchmark.java} (95%) rename test/micro/org/openjdk/bench/java/lang/stable/{CachingFunctionBenchmark.java => StableFunctionBenchmark.java} (98%) rename test/micro/org/openjdk/bench/java/lang/stable/{CachingIntFunctionBenchmark.java => StableIntFunctionBenchmark.java} (98%) rename test/micro/org/openjdk/bench/java/lang/stable/{CachingSupplierBenchmark.java => StableSupplierBenchmark.java} (96%) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index f38efe4fd7036..d0ccf87521bb7 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -35,6 +35,7 @@ import java.util.Map; import java.util.NoSuchElementException; import java.util.Objects; +import java.util.RandomAccess; import java.util.Set; import java.util.function.Function; import java.util.function.IntFunction; @@ -44,7 +45,7 @@ * A stable value is a holder of deferred immutable data. *

    * A {@linkplain StableValue {@code StableValue}} is created using the factory method - * {@linkplain StableValue#empty() {@code StableValue.empty()}}. When created, the + * {@linkplain StableValue#unset() {@code StableValue.unset()}}. When created, the * stable value is unset, which means it holds no value. Its holder value, of * type {@code T}, can be set by calling * {@linkplain #trySet(Object) trySet()}, {@linkplain #setOrThrow(Object) setOrThrow()}, @@ -60,7 +61,7 @@ * associated with {@code final} fields is too restrictive. *

    * Consider the following example where a stable value field "{@code logger}" is an - * immutable holder of a value of type {@code Logger} and that is initially created + * immutable holder of a value of type {@code Logger} and that is initially created * as unset, which means it holds no value. Later in the example, the * state of the "{@code logger}" field is checked and if it is still unset, * a holder value is set: @@ -68,9 +69,9 @@ * {@snippet lang = java: * class Component { * - * // Creates a new stable value with no holder value - * // @link substring="empty" target="#empty" : - * private final StableValue logger = StableValue.empty(); + * // Creates a new unset stable value with no holder value + * // @link substring="unset" target="#unset" : + * private final StableValue logger = StableValue.unset(); * * Logger getLogger() { * if (!logger.isSet()) { @@ -95,9 +96,9 @@ * {@snippet lang = java: * class Component { * - * // Creates a new stable value with no holder value - * // @link substring="empty" target="#empty" : - * private final StableValue logger = StableValue.empty(); + * // Creates a new unset stable value with no holder value + * // @link substring="unset" target="#unset" : + * private final StableValue logger = StableValue.unset(); * * Logger getLogger() { * return logger.computeIfUnset( () -> Logger.create(Component.class) ); @@ -192,10 +193,10 @@ *} * *

    Stable Collections

    - * Stable values can also be used as backing storage for immutable collections. - * A stable list is an immutable list, backed by an array of - * stable values. The stable list elements are computed when they are first accessed, - * using a provided {@linkplain IntFunction}: + * Stable values can also be used as backing storage for + * {@linkplain Collection##unmodifiable unmodifiable collections}. A stable list + * is an unmodifiable list, backed by an array of stable values. The stable list elements + * are computed when they are first accessed, using the provided {@linkplain IntFunction}: * * {@snippet lang = java: * class SqrtUtil { @@ -214,9 +215,9 @@ * Note: In the example above, there is a constructor in the {@code Component} * class that takes an {@code int} parameter. *

    - * Similarly, a stable map is an immutable map whose keys are known at + * Similarly, a stable map is an unmodifiable map whose keys are known at * construction. The stable map values are computed when they are first accessed, - * using a provided {@linkplain Function}: + * using the provided {@linkplain Function}: * * {@snippet lang = java: * class SqrtUtil { @@ -236,7 +237,7 @@ * racing to set a stable value, only one update succeeds, while other updates are * blocked until the stable value becomes set. *

    - * The at-most-once write operation on a stable value (e.g. {@linkplain #trySet(Object)}) + * The at-most-once write operation on a stable value (e.g. {@linkplain #trySet(Object) trySet()}) * happens-before * any subsequent read operation (e.g. {@linkplain #orElseThrow()}). *

    @@ -249,6 +250,10 @@ *

    Miscellaneous

    * Except for a StableValue's holder value itself, all method parameters must be * non-null or a {@link NullPointerException} will be thrown. + *

    + * Stable functions and collections are not {@link Serializable} as this would require + * {@linkplain #ofList(int, IntFunction) mappers} to be {@link Serializable} as well, + * which would introduce security vulnerabilities. * * @implSpec Implementing classes of {@linkplain StableValue} are free to synchronize on * {@code this} and consequently, care should be taken whenever @@ -349,9 +354,9 @@ public sealed interface StableValue /** * Sets the holder value to the provided {@code value}, or, - * if already set, throws {@link IllegalStateException}. + * if already set, throws {@code IllegalStateException}. *

    - * When this method returns (or throws an Exception), the holder value is always set. + * When this method returns (or throws an exception), the holder value is always set. * * @param value to set * @throws IllegalStateException if the holder value was already set @@ -361,23 +366,18 @@ public sealed interface StableValue // Factories /** - * {@return a new empty stable value with no holder value} + * {@return a new unset stable value} *

    - * The returned {@linkplain StableValue stable value} is a thin, atomic, thread-safe, - * set-at-most-once, stable value with no holder value eligible for certain JVM - * optimizations once the holder value is set. + * An unset stable value has no holder value. * * @param type of the holder value */ - static StableValue empty() { - return StableValueFactories.empty(); + static StableValue unset() { + return StableValueFactories.unset(); } /** - * {@return a new set stable value with the provided {@code value} as holder value} - *

    - * The returned {@linkplain StableValue stable value} is a thin, atomic, thread-safe, - * stable value with a set holder value eligible for certain JVM optimizations. + * {@return a new set stable value holding the provided {@code value}} * * @param value holder value to set * @param type of the holder value @@ -387,9 +387,9 @@ static StableValue of(T value) { } /** - * {@return a new stable supplier with no holder value} + * {@return a new unset stable supplier} *

    - * The returned stable {@linkplain Supplier supplier} is a thread-safe, caching, + * The returned stable {@linkplain Supplier supplier} is a caching, * and lazily computed supplier that records the value of the provided {@code original} * supplier upon being first accessed via the returned supplier's * {@linkplain Supplier#get() get()} method. @@ -412,10 +412,10 @@ static Supplier ofSupplier(Supplier original) { } /** - * {@return a new stable int function with no holder values} + * {@return a new unset stable int function} *

    - * The returned stable {@link IntFunction int function} is a thread-safe, caching, - * and lazily computed int function that, for each allowed input, records the values + * The returned stable {@link IntFunction int function} is a caching and lazily + * computed int function that, for each allowed input, records the values * of the provided {@code original} int function upon being first accessed via the * returned int function's {@linkplain IntFunction#apply(int) apply()} method. *

    @@ -443,13 +443,13 @@ static IntFunction ofIntFunction(int size, } /** - * {@return a new stable function with no holder values} + * {@return a new unset stable function} *

    - * The returned stable {@link Function function} is a stable, thread-safe, - * caching, and lazily computed function that, for each allowed input in the given - * set of {@code inputs}, records the values of the provided {@code original} function - * upon being first accessed via the returned function's - * {@linkplain Function#apply(Object) apply()} method. + * The returned stable {@link Function function} is a stable caching and lazily + * computed function that, for each allowed input in the given set of {@code inputs}, + * records the values of the provided {@code original} function upon being first + * accessed via the returned function's {@linkplain Function#apply(Object) apply()} + * method. *

    * The provided {@code original} function is guaranteed to be successfully invoked * at most once per allowed input, even in a multi-threaded environment. Competing @@ -473,13 +473,12 @@ static Function ofFunction(Set inputs, } /** - * {@return a new stable list of the provided {@code size} with no holder values} + * {@return a new unset stable list with the provided {@code size}} *

    - * The returned {@linkplain List list} is a stable, thread-safe, caching, - * lazily computed, and shallowly immutable list where the individual elements of the - * list are lazily computed via the provided {@code mapper} whenever an element is - * first accessed (directly or indirectly), for example via the returned list's - * {@linkplain List#get(int) get()} method. + * The returned list is an {@linkplain Collection##unmodifiable unmodifiable} list + * whose size is known at construction. The stable list elements are computed via the + * provided {@code mapper} when they are first accessed + * (e.g. via {@linkplain List#get(int) List::get}). *

    * The provided {@code mapper} int function is guaranteed to be successfully invoked * at most once per list index, even in a multi-threaded environment. Competing @@ -487,11 +486,10 @@ static Function ofFunction(Set inputs, * is computed or an exception is thrown by the computing thread. *

    * If the provided {@code mapper} throws an exception, it is relayed to the initial - * caller and no holder value for the element is recorded. + * caller and no value for the element is recorded. *

    - * The returned List is not {@link Serializable} as this would require the provided - * {@code mapper} to be {@link Serializable} as well, which would create security - * concerns. + * The returned list and its {@link List#subList(int, int) subList} views implement + * the {@link RandomAccess} interface. * * @param size the size of the returned list * @param mapper to invoke whenever an element is first accessed @@ -508,20 +506,20 @@ static List ofList(int size, } /** - * {@return a new stable map with the provided {@code keys} with no holder values} + * {@return a new unset stable map with the provided {@code keys}} *

    - * The returned {@linkplain Map map} is a stable, thread-safe, caching, - * lazily computed, and shallowly immutable map where the individual values of the map - * are lazily computed via the provided {@code mapper} whenever an element is first - * accessed (directly or indirectly), for example via the returned map's - * {@linkplain Map#get(Object) get()} method. + * The returned map is an {@linkplain Collection##unmodifiable unmodifiable} map whose + * keys are known at construction. The stable map values are computed via the provided + * {@code mapper} when they are first accessed + * (e.g. via {@linkplain Map#get(Object) Map::get}). *

    - * If the provided {@code mapper} throws an exception, it is relayed to the initial - * caller and no holder value associated with the provided key is recorded. + * The provided {@code mapper} function is guaranteed to be successfully invoked + * at most once per key, even in a multi-threaded environment. Competing + * threads accessing a value already under computation will block until an element + * is computed or an exception is thrown by the computing thread. *

    - * The returned Map is not {@link Serializable} as this would require the provided - * {@code mapper} to be {@link Serializable} as well, which would create security - * concerns. + * If the provided {@code mapper} throws an exception, it is relayed to the initial + * caller and no value associated with the provided key is recorded. * * @param keys the keys in the returned map * @param mapper to invoke whenever an associated value is first accessed diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableValueFactories.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableValueFactories.java index 94827edae2b67..298341a67ae42 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableValueFactories.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableValueFactories.java @@ -17,12 +17,12 @@ private StableValueFactories() {} // Factories - public static StableValueImpl empty() { + public static StableValueImpl unset() { return StableValueImpl.newInstance(); } public static StableValueImpl of(T value) { - final StableValueImpl stableValue = empty(); + final StableValueImpl stableValue = unset(); stableValue.trySet(value); return stableValue; } diff --git a/test/jdk/java/lang/StableValue/JepTest.java b/test/jdk/java/lang/StableValue/JepTest.java index 71a5666aad490..4b91ae673d30d 100644 --- a/test/jdk/java/lang/StableValue/JepTest.java +++ b/test/jdk/java/lang/StableValue/JepTest.java @@ -132,7 +132,7 @@ public Logger logger(int i) { class Foo { // 1. Declare a Stable field - private static final StableValue LOGGER = StableValue.empty(); + private static final StableValue LOGGER = StableValue.unset(); static Logger logger() { @@ -241,7 +241,7 @@ record CachingPredicate(Map> delegate, public CachingPredicate(Set inputs, Predicate original) { this(inputs.stream() - .collect(Collectors.toMap(Function.identity(), _ -> StableValue.empty())), + .collect(Collectors.toMap(Function.identity(), _ -> StableValue.unset())), original::test ); } @@ -292,7 +292,7 @@ public R apply(T t, U u) { static CachingBiFunction of(Set> inputs, BiFunction original) { Map, StableValue> map = inputs.stream() - .collect(Collectors.toMap(Function.identity(), _ -> StableValue.empty())); + .collect(Collectors.toMap(Function.identity(), _ -> StableValue.unset())); return new CachingBiFunction<>(map, pair -> original.apply(pair.left(), pair.right())); } @@ -315,7 +315,7 @@ public R apply(T t, U u) { static BiFunction of(Set> inputs, BiFunction original) { Map, StableValue> map = inputs.stream() - .collect(Collectors.toMap(Function.identity(), _ -> StableValue.empty())); + .collect(Collectors.toMap(Function.identity(), _ -> StableValue.unset())); return new CachingBiFunction2<>(map, original); } @@ -324,7 +324,7 @@ static BiFunction of(Set> inputs, BiFunction LOGGER = StableValue.empty(); + private final StableValue LOGGER = StableValue.unset(); public Logger getLogger() { return LOGGER.computeIfUnset(() -> Logger.getLogger("com.company.Foo")); @@ -335,7 +335,7 @@ record CachingIntPredicate(List> outputs, IntPredicate resultFunction) implements IntPredicate { CachingIntPredicate(int size, IntPredicate resultFunction) { - this(Stream.generate(StableValue::empty).limit(size).toList(), resultFunction); + this(Stream.generate(StableValue::unset).limit(size).toList(), resultFunction); } @Override @@ -351,7 +351,7 @@ class FixedStableList extends AbstractList { private final StableValue[] elements; FixedStableList(int size) { - this.elements = Stream.generate(StableValue::empty) + this.elements = Stream.generate(StableValue::unset) .limit(size) .toArray(StableValue[]::new); } diff --git a/test/jdk/java/lang/StableValue/StableValueTest.java b/test/jdk/java/lang/StableValue/StableValueTest.java index 0d600bd9d8932..78a5119f366c5 100644 --- a/test/jdk/java/lang/StableValue/StableValueTest.java +++ b/test/jdk/java/lang/StableValue/StableValueTest.java @@ -64,7 +64,7 @@ void preSet() { } void trySet(Integer initial) { - StableValue stable = StableValue.empty(); + StableValue stable = StableValue.unset(); assertTrue(stable.trySet(initial)); assertFalse(stable.trySet(null)); assertFalse(stable.trySet(VALUE)); @@ -74,7 +74,7 @@ void trySet(Integer initial) { @Test void orElse() { - StableValue stable = StableValue.empty(); + StableValue stable = StableValue.unset(); assertEquals(VALUE, stable.orElse(VALUE)); stable.trySet(VALUE); assertEquals(VALUE, stable.orElse(VALUE2)); @@ -82,7 +82,7 @@ void orElse() { @Test void orElseThrow() { - StableValue stable = StableValue.empty(); + StableValue stable = StableValue.unset(); var e = assertThrows(NoSuchElementException.class, stable::orElseThrow); assertEquals("No underlying data set", e.getMessage()); stable.trySet(VALUE); @@ -96,7 +96,7 @@ void isSet() { } void isSet(Integer initial) { - StableValue stable = StableValue.empty(); + StableValue stable = StableValue.unset(); assertFalse(stable.isSet()); stable.trySet(initial); assertTrue(stable.isSet()); @@ -105,7 +105,7 @@ void isSet(Integer initial) { @Test void testComputeIfUnsetSupplier() { StableTestUtil.CountingSupplier cs = new StableTestUtil.CountingSupplier<>(() -> VALUE); - StableValue stable = StableValue.empty(); + StableValue stable = StableValue.unset(); assertEquals(VALUE, stable.computeIfUnset(cs)); assertEquals(1, cs.cnt()); assertEquals(VALUE, stable.computeIfUnset(cs)); @@ -114,22 +114,22 @@ void testComputeIfUnsetSupplier() { @Test void testHashCode() { - StableValue stableValue = StableValue.empty(); + StableValue stableValue = StableValue.unset(); // Should be Object::hashCode assertEquals(System.identityHashCode(stableValue), stableValue.hashCode()); } @Test void testEquals() { - StableValue s0 = StableValue.empty(); - StableValue s1 = StableValue.empty(); + StableValue s0 = StableValue.unset(); + StableValue s1 = StableValue.unset(); assertNotEquals(s0, s1); // Identity based s0.setOrThrow(42); s1.setOrThrow(42); assertNotEquals(s0, s1); assertNotEquals(s0, "a"); - StableValue null0 = StableValue.empty(); - StableValue null1 = StableValue.empty(); + StableValue null0 = StableValue.unset(); + StableValue null1 = StableValue.unset(); null0.setOrThrow(null); null1.setOrThrow(null); assertNotEquals(null0, null1); @@ -137,27 +137,27 @@ void testEquals() { @Test void toStringUnset() { - StableValue stable = StableValue.empty(); + StableValue stable = StableValue.unset(); assertEquals("StableValue.unset", stable.toString()); } @Test void toStringNull() { - StableValue stable = StableValue.empty(); + StableValue stable = StableValue.unset(); assertTrue(stable.trySet(null)); assertEquals("StableValue[null]", stable.toString()); } @Test void toStringNonNull() { - StableValue stable = StableValue.empty(); + StableValue stable = StableValue.unset(); assertTrue(stable.trySet(VALUE)); assertEquals("StableValue[" + VALUE + "]", stable.toString()); } @Test void toStringCircular() { - StableValue> stable = StableValue.empty(); + StableValue> stable = StableValue.unset(); stable.trySet(stable); String toString = stable.toString(); assertEquals(toString, "(this StableValue)"); @@ -198,7 +198,7 @@ void raceMixed() { void race(BiPredicate, Integer> winnerPredicate) { int noThreads = 10; CountDownLatch starter = new CountDownLatch(1); - StableValue stable = StableValue.empty(); + StableValue stable = StableValue.unset(); BitSet winner = new BitSet(noThreads); List threads = IntStream.range(0, noThreads).mapToObj(i -> new Thread(() -> { try { diff --git a/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java b/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java index 54c82b65865fa..7d7e5893a3e47 100644 --- a/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java +++ b/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java @@ -52,7 +52,7 @@ static StableValue[] stables() { @SuppressWarnings("unchecked") StableValue[] stables = (StableValue[]) new StableValue[SIZE]; for (int i = 0; i < SIZE; i++) { - stables[i] = StableValue.empty(); + stables[i] = StableValue.unset(); } return stables; } diff --git a/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java b/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java index 8bb0d951fe9d2..fb5a7381f6c54 100644 --- a/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java +++ b/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java @@ -44,10 +44,10 @@ final class TrustedFieldTypeTest { @Test void reflection() throws NoSuchFieldException, IllegalAccessException { final class Holder { - private final StableValue value = StableValue.empty(); + private final StableValue value = StableValue.unset(); } final class HolderNonFinal { - private StableValue value = StableValue.empty(); + private StableValue value = StableValue.unset(); } final class ArrayHolder { private final StableValue[] array = (StableValue[]) new StableValue[]{}; @@ -60,7 +60,7 @@ final class ArrayHolder { Object read = valueField.get(holder); // We should NOT be able to write to the StableValue field assertThrows(IllegalAccessException.class, () -> - valueField.set(holder, StableValue.empty()) + valueField.set(holder, StableValue.unset()) ); Field valueNonFinal = HolderNonFinal.class.getDeclaredField("value"); @@ -68,7 +68,7 @@ final class ArrayHolder { HolderNonFinal holderNonFinal = new HolderNonFinal(); // As the field is not final, both read and write should be ok (not trusted) Object readNonFinal = valueNonFinal.get(holderNonFinal); - valueNonFinal.set(holderNonFinal, StableValue.empty()); + valueNonFinal.set(holderNonFinal, StableValue.unset()); Field arrayField = ArrayHolder.class.getDeclaredField("array"); arrayField.setAccessible(true); @@ -89,7 +89,7 @@ void sunMiscUnsafe() throws NoSuchFieldException, IllegalAccessException { sun.misc.Unsafe unsafe = (sun.misc.Unsafe)unsafeField.get(null); final class Holder { - private final StableValue value = StableValue.empty(); + private final StableValue value = StableValue.unset(); } final class ArrayHolder { private final StableValue[] array = (StableValue[]) new StableValue[]{}; @@ -107,7 +107,7 @@ final class ArrayHolder { ); // Test direct access - StableValue stableValue = StableValue.empty(); + StableValue stableValue = StableValue.unset(); Class clazz = stableValue.getClass(); System.out.println("clazz = " + clazz); assertThrows(NoSuchFieldException.class, () -> clazz.getField("value")); @@ -117,7 +117,7 @@ final class ArrayHolder { void varHandle() throws NoSuchFieldException, IllegalAccessException { MethodHandles.Lookup lookup = MethodHandles.lookup(); - StableValue originalValue = StableValue.empty(); + StableValue originalValue = StableValue.unset(); @SuppressWarnings("unchecked") StableValue[] originalArrayValue = new StableValue[10]; @@ -133,11 +133,11 @@ final class ArrayHolder { Holder holder = new Holder(); assertThrows(UnsupportedOperationException.class, () -> - valueVarHandle.set(holder, StableValue.empty()) + valueVarHandle.set(holder, StableValue.unset()) ); assertThrows(UnsupportedOperationException.class, () -> - valueVarHandle.compareAndSet(holder, originalValue, StableValue.empty()) + valueVarHandle.compareAndSet(holder, originalValue, StableValue.unset()) ); VarHandle arrayVarHandle = lookup.findVarHandle(ArrayHolder.class, "array", StableValue[].class); @@ -155,7 +155,7 @@ final class ArrayHolder { @Test void updateStableValueUnderlyingData() { - StableValue stableValue = StableValue.empty(); + StableValue stableValue = StableValue.unset(); stableValue.trySet(42); jdk.internal.misc.Unsafe unsafe = Unsafe.getUnsafe(); diff --git a/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingBiFunctionBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/CustomStableBiFunctionBenchmark.java similarity index 96% rename from test/micro/org/openjdk/bench/java/lang/stable/CustomCachingBiFunctionBenchmark.java rename to test/micro/org/openjdk/bench/java/lang/stable/CustomStableBiFunctionBenchmark.java index d94c8ce0feea3..3869487063fc9 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingBiFunctionBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/CustomStableBiFunctionBenchmark.java @@ -44,9 +44,9 @@ import java.util.function.Function; import java.util.stream.Collectors; -import org.openjdk.bench.java.lang.stable.CustomCachingFunctions.Pair; +import org.openjdk.bench.java.lang.stable.CustomStableFunctions.Pair; -import static org.openjdk.bench.java.lang.stable.CustomCachingFunctions.cachingBiFunction; +import static org.openjdk.bench.java.lang.stable.CustomStableFunctions.cachingBiFunction; /** * Benchmark measuring custom stable value types @@ -65,7 +65,7 @@ }) @Threads(Threads.MAX) // Benchmark under contention @OperationsPerInvocation(2) -public class CustomCachingBiFunctionBenchmark { +public class CustomStableBiFunctionBenchmark { private static final Set> SET = Set.of( new Pair<>(1, 4), @@ -90,8 +90,8 @@ public class CustomCachingBiFunctionBenchmark { private static final BiFunction function = cachingBiFunction(SET, ORIGINAL);; private static final BiFunction function2 = cachingBiFunction(SET, ORIGINAL);; - private static final StableValue STABLE_VALUE = StableValue.empty(); - private static final StableValue STABLE_VALUE2 = StableValue.empty(); + private static final StableValue STABLE_VALUE = StableValue.unset(); + private static final StableValue STABLE_VALUE2 = StableValue.unset(); static { STABLE_VALUE.trySet(ORIGINAL.apply(VALUE, VALUE2)); @@ -153,7 +153,7 @@ record CachingBiFunction( public CachingBiFunction(Set> inputs, BiFunction original) { this(Map.copyOf(inputs.stream() - .collect(Collectors.toMap(Function.identity(), _ -> StableValue.empty()))), + .collect(Collectors.toMap(Function.identity(), _ -> StableValue.unset()))), original ); } @@ -196,8 +196,8 @@ static Map>> delegate(Set>> map = inputs.stream() .collect(Collectors.groupingBy(Pair::left, Collectors.groupingBy(Pair::right, - Collectors.mapping((Function, ? extends StableValue>) _ -> StableValue.empty(), - Collectors.reducing(StableValue.empty(), _ -> StableValue.empty(), (StableValue a, StableValue b) -> a))))); + Collectors.mapping((Function, ? extends StableValue>) _ -> StableValue.unset(), + Collectors.reducing(StableValue.unset(), _ -> StableValue.unset(), (StableValue a, StableValue b) -> a))))); @SuppressWarnings("unchecked") Map>> copy = Map.ofEntries(map.entrySet().stream() @@ -248,8 +248,8 @@ static Map> delegate(Set>> map = inputs.stream() .collect(Collectors.groupingBy(Pair::left, Collectors.groupingBy(Pair::right, - Collectors.mapping((Function, ? extends StableValue>) _ -> StableValue.empty(), - Collectors.reducing(StableValue.empty(), _ -> StableValue.empty(), (StableValue a, StableValue b) -> b))))); + Collectors.mapping((Function, ? extends StableValue>) _ -> StableValue.unset(), + Collectors.reducing(StableValue.unset(), _ -> StableValue.unset(), (StableValue a, StableValue b) -> b))))); @SuppressWarnings("unchecked") Map> copy = Map.ofEntries(map.entrySet().stream() diff --git a/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingFunctions.java b/test/micro/org/openjdk/bench/java/lang/stable/CustomStableFunctions.java similarity index 98% rename from test/micro/org/openjdk/bench/java/lang/stable/CustomCachingFunctions.java rename to test/micro/org/openjdk/bench/java/lang/stable/CustomStableFunctions.java index 7af5ae130d291..32fc22ad28deb 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingFunctions.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/CustomStableFunctions.java @@ -34,9 +34,9 @@ import java.util.function.UnaryOperator; import java.util.stream.Collectors; -final class CustomCachingFunctions { +final class CustomStableFunctions { - private CustomCachingFunctions() {} + private CustomStableFunctions() {} record Pair(L left, R right){} diff --git a/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingPredicateBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/CustomStablePredicateBenchmark.java similarity index 95% rename from test/micro/org/openjdk/bench/java/lang/stable/CustomCachingPredicateBenchmark.java rename to test/micro/org/openjdk/bench/java/lang/stable/CustomStablePredicateBenchmark.java index 465e480e25ef2..99810d76c48c9 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/CustomCachingPredicateBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/CustomStablePredicateBenchmark.java @@ -43,7 +43,7 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; -import static org.openjdk.bench.java.lang.stable.CustomCachingFunctions.cachingPredicate; +import static org.openjdk.bench.java.lang.stable.CustomStableFunctions.cachingPredicate; /** * Benchmark measuring custom stable value types @@ -60,7 +60,7 @@ }) @Threads(Threads.MAX) // Benchmark under contention //@OperationsPerInvocation(2) -public class CustomCachingPredicateBenchmark { +public class CustomStablePredicateBenchmark { private static final Set SET = IntStream.range(0, 64).boxed().collect(Collectors.toSet()); private static final Predicate EVEN = i -> { @@ -95,7 +95,7 @@ record CachingPredicate(Map> delegate, public CachingPredicate(Set inputs, Predicate original) { this(inputs.stream() - .collect(Collectors.toUnmodifiableMap(Function.identity(), _ -> StableValue.empty())), + .collect(Collectors.toUnmodifiableMap(Function.identity(), _ -> StableValue.unset())), original ); } diff --git a/test/micro/org/openjdk/bench/java/lang/stable/CachingFunctionBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableFunctionBenchmark.java similarity index 98% rename from test/micro/org/openjdk/bench/java/lang/stable/CachingFunctionBenchmark.java rename to test/micro/org/openjdk/bench/java/lang/stable/StableFunctionBenchmark.java index 24c88df3a2a04..029e1f23f71f1 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/CachingFunctionBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableFunctionBenchmark.java @@ -55,7 +55,7 @@ }) @Threads(Threads.MAX) // Benchmark under contention @OperationsPerInvocation(100) -public class CachingFunctionBenchmark { +public class StableFunctionBenchmark { private static final int SIZE = 100; private static final Set SET = IntStream.range(0, SIZE).boxed().collect(Collectors.toSet()); diff --git a/test/micro/org/openjdk/bench/java/lang/stable/CachingIntFunctionBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableIntFunctionBenchmark.java similarity index 98% rename from test/micro/org/openjdk/bench/java/lang/stable/CachingIntFunctionBenchmark.java rename to test/micro/org/openjdk/bench/java/lang/stable/StableIntFunctionBenchmark.java index f8ad68bfaff73..0c9f225c1fece 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/CachingIntFunctionBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableIntFunctionBenchmark.java @@ -52,7 +52,7 @@ }) @Threads(Threads.MAX) // Benchmark under contention @OperationsPerInvocation(100) -public class CachingIntFunctionBenchmark { +public class StableIntFunctionBenchmark { private static final int SIZE = 100; private static final IntFunction IDENTITY = i -> i; diff --git a/test/micro/org/openjdk/bench/java/lang/stable/CachingSupplierBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableSupplierBenchmark.java similarity index 96% rename from test/micro/org/openjdk/bench/java/lang/stable/CachingSupplierBenchmark.java rename to test/micro/org/openjdk/bench/java/lang/stable/StableSupplierBenchmark.java index 30cd45dd07827..36fe309b3cb8e 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/CachingSupplierBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableSupplierBenchmark.java @@ -51,18 +51,18 @@ }) @Threads(Threads.MAX) // Benchmark under contention @OperationsPerInvocation(2) -public class CachingSupplierBenchmark { +public class StableSupplierBenchmark { private static final int VALUE = 42; private static final int VALUE2 = 23; - private static final StableValue STABLE = init(StableValue.empty(), VALUE); - private static final StableValue STABLE2 = init(StableValue.empty(), VALUE2); + private static final StableValue STABLE = init(StableValue.unset(), VALUE); + private static final StableValue STABLE2 = init(StableValue.unset(), VALUE2); private static final Supplier SUPPLIER = StableValue.ofSupplier(() -> VALUE); private static final Supplier SUPPLIER2 = StableValue.ofSupplier(() -> VALUE); - private final StableValue stable = init(StableValue.empty(), VALUE); - private final StableValue stable2 = init(StableValue.empty(), VALUE2); + private final StableValue stable = init(StableValue.unset(), VALUE); + private final StableValue stable2 = init(StableValue.unset(), VALUE2); private final Supplier supplier = StableValue.ofSupplier(() -> VALUE); private final Supplier supplier2 = StableValue.ofSupplier(() -> VALUE2); diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java index 75c6e83636d42..463f407ce3e40 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java @@ -47,19 +47,19 @@ public class StableValueBenchmark { private static final int VALUE = 42; private static final int VALUE2 = 23; - private static final StableValue STABLE = init(StableValue.empty(), VALUE); - private static final StableValue STABLE2 = init(StableValue.empty(), VALUE2); - private static final StableValue DCL = init(StableValue.empty(), VALUE); - private static final StableValue DCL2 = init(StableValue.empty(), VALUE2); + private static final StableValue STABLE = init(StableValue.unset(), VALUE); + private static final StableValue STABLE2 = init(StableValue.unset(), VALUE2); + private static final StableValue DCL = init(StableValue.unset(), VALUE); + private static final StableValue DCL2 = init(StableValue.unset(), VALUE2); private static final AtomicReference ATOMIC = new AtomicReference<>(VALUE); private static final AtomicReference ATOMIC2 = new AtomicReference<>(VALUE2); private static final Holder HOLDER = new Holder(VALUE); private static final Holder HOLDER2 = new Holder(VALUE2); - private final StableValue stable = init(StableValue.empty(), VALUE); - private final StableValue stable2 = init(StableValue.empty(), VALUE2); - private final StableValue stableNull = StableValue.empty(); - private final StableValue stableNull2 = StableValue.empty(); + private final StableValue stable = init(StableValue.unset(), VALUE); + private final StableValue stable2 = init(StableValue.unset(), VALUE2); + private final StableValue stableNull = StableValue.unset(); + private final StableValue stableNull2 = StableValue.unset(); private final Supplier dcl = new Dcl<>(() -> VALUE); private final Supplier dcl2 = new Dcl<>(() -> VALUE2); private final AtomicReference atomic = new AtomicReference<>(VALUE); @@ -78,7 +78,7 @@ public void setup() { final int v = i; Dcl dclX = new Dcl<>(() -> v); sum += dclX.get(); - StableValue stableX = StableValue.empty(); + StableValue stableX = StableValue.unset(); stableX.trySet(i); sum += stableX.orElseThrow(); } @@ -141,7 +141,7 @@ private static StableValue init(StableValue m, Integer value) // because StableValue fields have a special meaning. private static final class Holder { - private final StableValue delegate = StableValue.empty(); + private final StableValue delegate = StableValue.unset(); Holder(int value) { delegate.setOrThrow(value); From e1194cee4ae6b47219152f232a6b77cd3179287c Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 18 Nov 2024 13:09:11 +0100 Subject: [PATCH 161/327] Update docs after comments --- .../share/classes/java/lang/StableValue.java | 50 +++++++++---------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index d0ccf87521bb7..fd81bcc47e81d 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -57,8 +57,8 @@ * A stable value that is set is treated as a constant by the JVM, enabling the * same performance optimizations that are available for {@code final} fields. * As such, stable values can be used to replace {@code final} fields in cases where - * at-most-once update semantics is crucial, but where the eager initialization semantics - * associated with {@code final} fields is too restrictive. + * at-most-once update semantics is crucial, but where the eager initialization + * semantics associated with {@code final} fields is too restrictive. *

    * Consider the following example where a stable value field "{@code logger}" is an * immutable holder of a value of type {@code Logger} and that is initially created @@ -89,9 +89,9 @@ *

    * Note that the holder value can only be set at most once. *

    - * To guarantee that, even under races, only one instance of {@code Logger} is ever created, the - * {@linkplain #computeIfUnset(Supplier) computeIfUnset()} method can be used instead, - * where the holder is atomically and lazily computed via a lambda expression: + * To guarantee that, even under races, only one instance of {@code Logger} is ever + * created, the {@linkplain #computeIfUnset(Supplier) computeIfUnset()} method can be used + * instead, where the holder is atomically and lazily computed via a lambda expression: * * {@snippet lang = java: * class Component { @@ -144,17 +144,17 @@ * } * } *} - * A stable supplier encapsulates access to its backing stable value storage. This means that - * code inside {@code Component} can obtain the logger object directly from the stable supplier, - * without having to go through an accessor method like {@code getLogger()}. + * A stable supplier encapsulates access to its backing stable value storage. This means + * that code inside {@code Component} can obtain the logger object directly from the + * stable supplier, without having to go through an accessor method like {@code getLogger()}. *

    * A stable int function is a function that takes an {@code int} parameter and * uses it to compute a result that is then cached into the backing stable value storage * for that parameter value. A stable int function is created via the * {@linkplain StableValue#ofIntFunction(int, IntFunction) StableValue.ofIntFunction()} - * factory. Upon creation, the input range (i.e. [0, size)) is specified together with an original - * {@linkplain IntFunction} which is invoked at most once per input value. In effect, - * the stable int function will act like a cache for the original {@linkplain IntFunction}: + * factory. Upon creation, the input range (i.e. [0, size)) is specified together with + * an original {@linkplain IntFunction} which is invoked at most once per input value. In + * effect, the stable int function will act like a cache for the original {@linkplain IntFunction}: * * {@snippet lang = java: * class SqrtUtil { @@ -389,10 +389,9 @@ static StableValue of(T value) { /** * {@return a new unset stable supplier} *

    - * The returned stable {@linkplain Supplier supplier} is a caching, - * and lazily computed supplier that records the value of the provided {@code original} - * supplier upon being first accessed via the returned supplier's - * {@linkplain Supplier#get() get()} method. + * The returned {@linkplain Supplier supplier} is a caching supplier that records + * the value of the provided {@code original} supplier upon being first accessed via + * the returned supplier's {@linkplain Supplier#get() get()} method. *

    * The provided {@code original} supplier is guaranteed to be successfully invoked * at most once even in a multi-threaded environment. Competing threads invoking the @@ -414,10 +413,10 @@ static Supplier ofSupplier(Supplier original) { /** * {@return a new unset stable int function} *

    - * The returned stable {@link IntFunction int function} is a caching and lazily - * computed int function that, for each allowed input, records the values - * of the provided {@code original} int function upon being first accessed via the - * returned int function's {@linkplain IntFunction#apply(int) apply()} method. + * The returned {@link IntFunction int function} is a caching int function that, + * for each allowed input, records the values of the provided {@code original} + * int function upon being first accessed via the returned int function's + * {@linkplain IntFunction#apply(int) apply()} method. *

    * The provided {@code original} int function is guaranteed to be successfully invoked * at most once per allowed input, even in a multi-threaded environment. Competing @@ -445,11 +444,10 @@ static IntFunction ofIntFunction(int size, /** * {@return a new unset stable function} *

    - * The returned stable {@link Function function} is a stable caching and lazily - * computed function that, for each allowed input in the given set of {@code inputs}, - * records the values of the provided {@code original} function upon being first - * accessed via the returned function's {@linkplain Function#apply(Object) apply()} - * method. + * The returned {@link Function function} is a caching function that, for each allowed + * input in the given set of {@code inputs}, records the values of the provided + * {@code original} function upon being first accessed via the returned function's + * {@linkplain Function#apply(Object) apply()} method. *

    * The provided {@code original} function is guaranteed to be successfully invoked * at most once per allowed input, even in a multi-threaded environment. Competing @@ -476,7 +474,7 @@ static Function ofFunction(Set inputs, * {@return a new unset stable list with the provided {@code size}} *

    * The returned list is an {@linkplain Collection##unmodifiable unmodifiable} list - * whose size is known at construction. The stable list elements are computed via the + * whose size is known at construction. The list's elements are computed via the * provided {@code mapper} when they are first accessed * (e.g. via {@linkplain List#get(int) List::get}). *

    @@ -509,7 +507,7 @@ static List ofList(int size, * {@return a new unset stable map with the provided {@code keys}} *

    * The returned map is an {@linkplain Collection##unmodifiable unmodifiable} map whose - * keys are known at construction. The stable map values are computed via the provided + * keys are known at construction. The map's values are computed via the provided * {@code mapper} when they are first accessed * (e.g. via {@linkplain Map#get(Object) Map::get}). *

    From 7eb2f998f4b5ec926ea24f21d729c9f433b9d3b5 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 4 Dec 2024 14:21:05 +0100 Subject: [PATCH 162/327] Update after CSR comments --- .../share/classes/java/lang/StableValue.java | 36 ++++++++++++++----- .../lang/StableValue/StableValueTest.java | 3 +- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index fd81bcc47e81d..0673a56ea4938 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -161,10 +161,10 @@ * * private static final IntFunction SQRT = * // @link substring="ofIntFunction" target="#ofIntFunction(int,IntFunction)" : - * StableValue.ofIntFunction(10, Math::sqrt); + * StableValue.ofIntFunction(10, StrictMath::sqrt); * * double sqrt9() { - * return SQRT.apply(9); // Constant folds to 3.0 + * return SQRT.apply(9); // Eventually constant folds to 3.0 at runtime * } * * } @@ -183,10 +183,10 @@ * * private static final Function SQRT = * // @link substring="ofFunction" target="#ofFunction(Set,Function)" : - * StableValue.ofFunction(Set.of(1, 2, 4, 8, 16, 32), Math::sqrt); + * StableValue.ofFunction(Set.of(1, 2, 4, 8, 16, 32), StrictMath::sqrt); * * double sqrt16() { - * return SQRT.apply(16); // Constant folds to 4.0 + * return SQRT.apply(16); // Eventually constant folds to 4.0 at runtime * } * * } @@ -203,10 +203,10 @@ * * private static final List SQRT = * // @link substring="ofList" target="#ofList(int,IntFunction)" : - * StableValue.ofList(10, Math::sqrt); + * StableValue.ofList(10, StrictMath::sqrt); * * double sqrt9() { - * return SQRT.apply(9); // Constant folds to 3.0 + * return SQRT.apply(9); // Eventually constant folds to 3.0 at runtime * } * * } @@ -224,10 +224,10 @@ * * private static final Map SQRT = * // @link substring="ofMap" target="#ofMap(Set,Function)" : - * StableValue.ofMap(Set.of(1, 2, 4, 8, 16, 32), Math::sqrt); + * StableValue.ofMap(Set.of(1, 2, 4, 8, 16, 32), StrictMath::sqrt); * * double sqrt16() { - * return SQRT.apply(16); // Constant folds to 4.0 + * return SQRT.apply(16); // Eventually constant folds to 4.0 at runtime * } * * } @@ -254,6 +254,13 @@ * Stable functions and collections are not {@link Serializable} as this would require * {@linkplain #ofList(int, IntFunction) mappers} to be {@link Serializable} as well, * which would introduce security vulnerabilities. + *

    + * As objects can be set via stable values but never removed, this can be a source + * of unintended memory leaks. Clients are advised that live stable values will hold set + * values perpetually. + *

    + * A {@linkplain StableValue} may hold a reference to itself. Stable functions and + * collections may hold self-references. * * @implSpec Implementing classes of {@linkplain StableValue} are free to synchronize on * {@code this} and consequently, care should be taken whenever @@ -363,6 +370,19 @@ public sealed interface StableValue */ void setOrThrow(T value); + /** + * {@return {@code true} if {@code this == obj}, {@code false} otherwise} + * + * @param obj to check for equality + */ + boolean equals(Object obj); + + /** + * {@return the {@linkplain System#identityHashCode(Object) identity hash code} of + * {@code this} object} + */ + int hashCode(); + // Factories /** diff --git a/test/jdk/java/lang/StableValue/StableValueTest.java b/test/jdk/java/lang/StableValue/StableValueTest.java index 78a5119f366c5..ceaa4d7a22819 100644 --- a/test/jdk/java/lang/StableValue/StableValueTest.java +++ b/test/jdk/java/lang/StableValue/StableValueTest.java @@ -160,7 +160,7 @@ void toStringCircular() { StableValue> stable = StableValue.unset(); stable.trySet(stable); String toString = stable.toString(); - assertEquals(toString, "(this StableValue)"); + assertEquals("(this StableValue)", toString); assertDoesNotThrow(stable::hashCode); assertDoesNotThrow((() -> stable.equals(stable))); } @@ -185,7 +185,6 @@ void raceSetOrThrow() { race(SET_OR_THROW); } - @Test void raceMixed() { race((s, i) -> switch (i % 2) { From 6a5e2a0cee4fa27b5633572a7fe574ceeca84c20 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Thu, 5 Dec 2024 15:36:14 +0100 Subject: [PATCH 163/327] Add section on composition --- .../share/classes/java/lang/StableValue.java | 66 ++++++++++++++++++- test/jdk/java/lang/StableValue/JepTest.java | 42 ++++++++++++ 2 files changed, 105 insertions(+), 3 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 0673a56ea4938..6deee263dac54 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -232,6 +232,69 @@ * * } *} + * + *

    Composition

    + * A stable value can depend on other stable values, thereby creating a dependency tree + * that can be lazily computed but where the individual tree branches still provide + * as-final performance. In the following example, a single `Foo` and a `Bar` instance + * (that is dependent on the`Foo` instance) are lazily created, both of which are held + * by stable values: + * {@snippet lang = java: + * class Dependency { + * + * public static class Foo { + * // ... + * } + * + * public static class Bar { + * public Bar(Foo foo) { + * // ... + * } + * } + * + * private static final Supplier FOO = StableValue.ofSupplier(Foo::new); + * private static final Supplier BAR = StableValue.ofSupplier(() -> new Bar(FOO.get())); + * + * public static Foo foo() { + * return FOO.get(); + * } + * + * public static Bar bar() { + * return BAR.get(); + * } + * + * } + *} + * Calling {@code bar()} will create the {@code Bar} singleton if needed and will also + * first create the {@code Bar} (which it depends on) if needed. + *

    + * A {@linkplain StableValue} may hold a reference to itself. Stable functions and + * collections may hold self-references. + *

    + * Here is another example where a more complex dependency tree is created in which + * integers in the Fibonacci delta series are lazily computed: + * {@snippet lang = java: + * class Fibonacci { + * + * private static final int MAX_SIZE_INT = 46; + * + * private static final IntFunction FIB = + * StableValue.ofIntFunction(MAX_SIZE_INT, Fibonacci::fib); + * + * public static int fib(int n) { + * return n < 2 + * ? n + * : FIB.apply(n - 1) + FIB.apply(n - 2); + * } + * + * } + * } + * Both {@code FIB} and {@code Fibonacci::fib} recurses into each other. Because the + * stable int function {@code FIB} internalizes intermediate results, the initial + * computational complexity is reduced from exponential to linear compared to a + * traditional non-internalizing recursive fibonacci method. Once computed, the VM + * can constant-fold expressions like {@code Fibonacci.fib(10)}. + * *

    Thread Safety

    * A holder value is guaranteed to be set at most once. If competing threads are * racing to set a stable value, only one update succeeds, while other updates are @@ -258,9 +321,6 @@ * As objects can be set via stable values but never removed, this can be a source * of unintended memory leaks. Clients are advised that live stable values will hold set * values perpetually. - *

    - * A {@linkplain StableValue} may hold a reference to itself. Stable functions and - * collections may hold self-references. * * @implSpec Implementing classes of {@linkplain StableValue} are free to synchronize on * {@code this} and consequently, care should be taken whenever diff --git a/test/jdk/java/lang/StableValue/JepTest.java b/test/jdk/java/lang/StableValue/JepTest.java index 4b91ae673d30d..8896517b6fb0f 100644 --- a/test/jdk/java/lang/StableValue/JepTest.java +++ b/test/jdk/java/lang/StableValue/JepTest.java @@ -377,5 +377,47 @@ public int size() { } } + static + class Dependency { + + public static class Foo { + // ... + } + + public static class Bar { + public Bar(Foo foo) { + // ... + } + } + + private static final Supplier FOO = StableValue.ofSupplier(Foo::new); + private static final Supplier BAR = StableValue.ofSupplier(() -> new Bar(FOO.get())); + + public static Foo foo() { + return FOO.get(); + } + + public static Bar bar() { + return BAR.get(); + } + + } + + static + class Fibonacci { + + private static final int MAX_SIZE_INT = 46; + + private static final IntFunction FIB = + StableValue.ofIntFunction(MAX_SIZE_INT, Fibonacci::fib); + + public static int fib(int n) { + return n < 2 + ? n + : FIB.apply(n - 1) + FIB.apply(n - 2); + } + + } + } From f02aa986978a012c0d6295a3d947f948a25becac Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Thu, 5 Dec 2024 16:02:47 +0100 Subject: [PATCH 164/327] Fix typo --- src/java.base/share/classes/java/lang/StableValue.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 6deee263dac54..4e8c64277159f 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -266,7 +266,7 @@ * } *} * Calling {@code bar()} will create the {@code Bar} singleton if needed and will also - * first create the {@code Bar} (which it depends on) if needed. + * first create the {@code Foo} (which it depends on) if needed. *

    * A {@linkplain StableValue} may hold a reference to itself. Stable functions and * collections may hold self-references. From 447a589eb4cd071256eb47938cbb9c3a3a8211ba Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Thu, 5 Dec 2024 16:25:58 +0100 Subject: [PATCH 165/327] Improve docs --- .../share/classes/java/lang/StableValue.java | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 4e8c64277159f..e9a94c796b234 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -233,12 +233,12 @@ * } *} * - *

    Composition

    + *

    Composing stable values

    * A stable value can depend on other stable values, thereby creating a dependency tree * that can be lazily computed but where the individual tree branches still provide - * as-final performance. In the following example, a single `Foo` and a `Bar` instance - * (that is dependent on the`Foo` instance) are lazily created, both of which are held - * by stable values: + * as-final performance. In the following example, a single {@code Foo} and a {@code Bar} + * instance (that is dependent on the {@code Foo} instance) are lazily created, both of + * which are held by stable values: * {@snippet lang = java: * class Dependency { * @@ -294,6 +294,11 @@ * computational complexity is reduced from exponential to linear compared to a * traditional non-internalizing recursive fibonacci method. Once computed, the VM * can constant-fold expressions like {@code Fibonacci.fib(10)}. + *

    + * The fibonacci example above is a dependency tree with no circular dependencies. + * In a more general dependency graph, a stable value will throw a + * {@linkplain StackOverflowError} if there is a circular dependency and elements in + * said circle are referenced. * *

    Thread Safety

    * A holder value is guaranteed to be set at most once. If competing threads are @@ -319,8 +324,10 @@ * which would introduce security vulnerabilities. *

    * As objects can be set via stable values but never removed, this can be a source - * of unintended memory leaks. Clients are advised that live stable values will hold set - * values perpetually. + * of unintended memory leaks. A stable value's set values are + * {@linkplain java.lang.ref##reachability strongly reachable}. Clients are advised that + * {@linkplain java.lang.ref##reachability reachable} stable values will hold set values + * perpetually. * * @implSpec Implementing classes of {@linkplain StableValue} are free to synchronize on * {@code this} and consequently, care should be taken whenever From 58d97582623e6447cd9144bba438a41c889b0157 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Fri, 6 Dec 2024 08:03:50 +0100 Subject: [PATCH 166/327] Improve text on circulatities --- .../share/classes/java/lang/StableValue.java | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index e9a94c796b234..08c203d87c13e 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -268,9 +268,6 @@ * Calling {@code bar()} will create the {@code Bar} singleton if needed and will also * first create the {@code Foo} (which it depends on) if needed. *

    - * A {@linkplain StableValue} may hold a reference to itself. Stable functions and - * collections may hold self-references. - *

    * Here is another example where a more complex dependency tree is created in which * integers in the Fibonacci delta series are lazily computed: * {@snippet lang = java: @@ -295,10 +292,10 @@ * traditional non-internalizing recursive fibonacci method. Once computed, the VM * can constant-fold expressions like {@code Fibonacci.fib(10)}. *

    - * The fibonacci example above is a dependency tree with no circular dependencies. - * In a more general dependency graph, a stable value will throw a - * {@linkplain StackOverflowError} if there is a circular dependency and elements in - * said circle are referenced. + * The fibonacci example above is a dependency graph with no circular dependencies. If + * there are circular dependencies in a dependency graph, a stable value will + * eventually throw a {@linkplain StackOverflowError} upon referencing elements in + * a circularity. * *

    Thread Safety

    * A holder value is guaranteed to be set at most once. If competing threads are From 62d33e3959a320681be5872a1a6684bc54235271 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Fri, 6 Dec 2024 10:46:27 +0100 Subject: [PATCH 167/327] Add internal stable heterogenious container --- .../stable/StableHeterogeneousContainer.java | 105 ++++++++++++ .../lang/stable/StableValueFactories.java | 4 + .../StableHeterogeneousContainerTest.java | 160 ++++++++++++++++++ 3 files changed, 269 insertions(+) create mode 100644 src/java.base/share/classes/jdk/internal/lang/stable/StableHeterogeneousContainer.java create mode 100644 test/jdk/java/lang/StableValue/StableHeterogeneousContainerTest.java diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableHeterogeneousContainer.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableHeterogeneousContainer.java new file mode 100644 index 0000000000000..3ecaaf6f504ce --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableHeterogeneousContainer.java @@ -0,0 +1,105 @@ +package jdk.internal.lang.stable; + +import jdk.internal.vm.annotation.Stable; + +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Supplier; + +/** + * A stable heterogeneous container that can associate types to implementing instances. + *

    + * Attempting to associate a type with an instance that is {@code null} will result in + * a {@linkplain NullPointerException}. + * + * @implNote Implementations of this interface are thread-safe + + */ +public sealed interface StableHeterogeneousContainer { + + /** + * {@return {@code true} if the provided {@code type} was associated with the + * provided {@code instance}, {@code false} otherwise} + *

    + * When this method returns, the provided {@code type} is always associated with + * an instance. + * + * @param type the class type of the instance to store + * @param instance the instance to store + * @throws IllegalArgumentException if the provided {@code type} is not a type + * specified at creation + */ + boolean tryPut(Class type, T instance); + + T computeIfAbsent(Class type, Function, T> constructor); + + /** + * {@return the instance associated with the provided {@code type}, {@code null} + * otherwise} + * @param type used to retrieve an associated instance + * @param type of the associated instance + * @throws IllegalArgumentException if the provided {@code type} is not a type + * specified at creation + */ + T get(Class type); + + final class Impl implements StableHeterogeneousContainer { + + @Stable + private final Map, StableValueImpl> map; + + public Impl(Set> types) { + this.map = StableValueFactories.ofMap(types); + } + + @Override + public boolean tryPut(Class type, T instance) { + Objects.requireNonNull(type); + Objects.requireNonNull(instance); + return of(type) + .trySet( + type.cast(instance) + ); + } + + @SuppressWarnings("unchecked") + @Override + public T computeIfAbsent(Class type, Function, T> constructor) { + Objects.requireNonNull(type); + Objects.requireNonNull(constructor); + return (T) of(type).computeIfUnset(new Supplier() { + @Override + public Object get() { + return type.cast( + Objects.requireNonNull( + constructor.apply(type) + )); + } + }); + } + + @Override + public T get(Class type) { + return type.cast( + of(type) + .orElse(null) + ); + } + + private StableValue of(Class type) { + final StableValue stableValue = map.get(type); + if (stableValue == null) { + throw new IllegalArgumentException("No such type: " + type); + } + return stableValue; + } + + @Override + public String toString() { + return map.toString(); + } + } + +} diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableValueFactories.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableValueFactories.java index 298341a67ae42..24c430e99bd95 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableValueFactories.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableValueFactories.java @@ -46,6 +46,10 @@ public static Function ofFunction(Set inputs, : StableFunction.of(inputs, original); } + public static StableHeterogeneousContainer ofHeterogeneousContainer(Set> types) { + return new StableHeterogeneousContainer.Impl(types); + } + public static List ofList(int size, IntFunction mapper) { return SharedSecrets.getJavaUtilCollectionAccess().stableList(size, mapper); } diff --git a/test/jdk/java/lang/StableValue/StableHeterogeneousContainerTest.java b/test/jdk/java/lang/StableValue/StableHeterogeneousContainerTest.java new file mode 100644 index 0000000000000..b2514c1736f59 --- /dev/null +++ b/test/jdk/java/lang/StableValue/StableHeterogeneousContainerTest.java @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2024, 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 Stable Heterogeneous Container methods + * @modules java.base/jdk.internal.lang.stable + * @compile --enable-preview -source ${jdk.version} StableHeterogeneousContainerTest.java + * @run junit/othervm --enable-preview StableHeterogeneousContainerTest + */ + +import jdk.internal.lang.stable.StableValueFactories; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.EnumSet; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.TreeSet; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.*; + +final class StableHeterogeneousContainerTest { + + enum Value { + SHORT((short) Short.BYTES), + INTEGER(Integer.BYTES); + + final Object value; + + Value(Object value) { + this.value = value; + } + + Object value() { + return value; + } + + T valueAs(Class type) { + return type.cast(value); + } + + Class clazz() { + return value.getClass(); + } + + } + + @Test + void factoryInvariants() { + assertThrows(NullPointerException.class, () -> StableValueFactories.ofHeterogeneousContainer(null)); + } + + @ParameterizedTest + @MethodSource("nonEmptySets") + void trySet(Set inputs) { + var container = StableValueFactories.ofHeterogeneousContainer(classes(inputs)); + assertTrue(container.tryPut(Integer.class, Value.INTEGER.valueAs(Integer.class))); + assertFalse(container.tryPut(Integer.class, Value.INTEGER.valueAs(Integer.class))); + assertEquals(Value.INTEGER.value(), container.get(Integer.class)); + assertThrows(IllegalArgumentException.class, () -> container.tryPut(Long.class, 8L)); + assertThrows(NullPointerException.class, () -> container.tryPut(Short.class, null)); + } + + @ParameterizedTest + @MethodSource("nonEmptySets") + void computeIfAbsent(Set inputs) { + var container = StableValueFactories.ofHeterogeneousContainer(classes(inputs)); + assertEquals(Value.INTEGER.value(), container.computeIfAbsent(Integer.class, Value.INTEGER::valueAs)); + assertThrows(IllegalArgumentException.class, () -> container.computeIfAbsent(Long.class, _ -> 8L)); + assertThrows(NullPointerException.class, () -> container.computeIfAbsent(Short.class, _ -> null)); + } + + @ParameterizedTest + @MethodSource("nonEmptySets") + void get(Set inputs) { + var container = StableValueFactories.ofHeterogeneousContainer(classes(inputs)); + assertTrue(container.tryPut(Integer.class, Value.INTEGER.valueAs(Integer.class))); + assertEquals(Value.INTEGER.value(), container.get(Integer.class)); + assertNull(container.get(Value.SHORT.clazz())); + } + + @ParameterizedTest + @MethodSource("nonEmptySets") + void toString(Set inputs) { + var container = StableValueFactories.ofHeterogeneousContainer(classes(inputs)); + assertTrue(container.toString().contains("class java.lang.Integer=StableValue.unset")); + container.tryPut(Integer.class, Value.INTEGER.valueAs(Integer.class)); + assertTrue(container.toString().contains("class java.lang.Integer=StableValue[" + Value.INTEGER.value() + "]"), container.toString()); + } + + private static Set> classes(Set values) { + return values.stream() + .map(Value::clazz) + .collect(Collectors.toUnmodifiableSet()); + } + + private static Stream> nonEmptySets() { + return Stream.of( + Set.of(Value.SHORT, Value.INTEGER), + linkedHashSet(Value.SHORT, Value.INTEGER), + treeSet(Value.SHORT, Value.INTEGER), + EnumSet.of(Value.SHORT, Value.INTEGER) + ); + } + + private static Stream> emptySets() { + return Stream.of( + Set.of(), + linkedHashSet(), + treeSet(), + EnumSet.noneOf(Value.class) + ); + } + + private static Stream> allSets() { + return Stream.concat( + nonEmptySets(), + emptySets() + ); + } + + static Set treeSet(Value... values) { + return populate(new TreeSet<>(Comparator.comparingInt(Value::ordinal).reversed()),values); + } + + static Set linkedHashSet(Value... values) { + return populate(new LinkedHashSet<>(), values); + } + + static Set populate(Set set, Value... values) { + set.addAll(Arrays.asList(values)); + return set; + } + +} From 44dc46bba3cc1d6b48dc2f3f9431e2919e515419 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Fri, 6 Dec 2024 16:33:57 +0100 Subject: [PATCH 168/327] Improve StableHeterogeneousContainer --- .../stable/StableHeterogeneousContainer.java | 47 +++++++++++++++++-- .../StableHeterogeneousContainerTest.java | 25 ++++++++-- 2 files changed, 64 insertions(+), 8 deletions(-) diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableHeterogeneousContainer.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableHeterogeneousContainer.java index 3ecaaf6f504ce..9be7b800cc725 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableHeterogeneousContainer.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableHeterogeneousContainer.java @@ -1,8 +1,11 @@ package jdk.internal.lang.stable; +import jdk.internal.vm.annotation.DontInline; +import jdk.internal.vm.annotation.ForceInline; import jdk.internal.vm.annotation.Stable; import java.util.Map; +import java.util.NoSuchElementException; import java.util.Objects; import java.util.Set; import java.util.function.Function; @@ -45,6 +48,18 @@ public sealed interface StableHeterogeneousContainer { */ T get(Class type); + /** + * {@return the instance associated with the provided {@code type}, or throws + * NoSuchElementException} + * @param type used to retrieve an associated instance + * @param type of the associated instance + * @throws IllegalArgumentException if the provided {@code type} is not a type + * specified at creation + * @throws java.util.NoSuchElementException if the provided {@code type} is not + * associated with an instance + */ + T getOrThrow(Class type); + final class Impl implements StableHeterogeneousContainer { @Stable @@ -54,10 +69,11 @@ public Impl(Set> types) { this.map = StableValueFactories.ofMap(types); } + @ForceInline @Override public boolean tryPut(Class type, T instance) { Objects.requireNonNull(type); - Objects.requireNonNull(instance); + Objects.requireNonNull(instance, "The instance was null"); return of(type) .trySet( type.cast(instance) @@ -65,21 +81,35 @@ public boolean tryPut(Class type, T instance) { } @SuppressWarnings("unchecked") + @ForceInline @Override public T computeIfAbsent(Class type, Function, T> constructor) { Objects.requireNonNull(type); Objects.requireNonNull(constructor); - return (T) of(type).computeIfUnset(new Supplier() { + final StableValue stableValue = of(type); + if (stableValue.isSet()) { + return (T) stableValue.orElseThrow(); + } + return computeIfAbsentSlowPath(type, constructor, stableValue); + } + + @SuppressWarnings("unchecked") + @DontInline + public T computeIfAbsentSlowPath(Class type, + Function, T> constructor, + StableValue stableValue) { + return (T) stableValue.computeIfUnset(new Supplier() { @Override public Object get() { return type.cast( Objects.requireNonNull( - constructor.apply(type) + constructor.apply(type), "The constructor returned null" )); } }); } + @ForceInline @Override public T get(Class type) { return type.cast( @@ -88,6 +118,17 @@ public T get(Class type) { ); } + @ForceInline + @Override + public T getOrThrow(Class type) { + final T t = get(type); + if (t == null) { + throw new NoSuchElementException("No instance associated with " + type); + } + return t; + } + + @ForceInline private StableValue of(Class type) { final StableValue stableValue = map.get(type); if (stableValue == null) { diff --git a/test/jdk/java/lang/StableValue/StableHeterogeneousContainerTest.java b/test/jdk/java/lang/StableValue/StableHeterogeneousContainerTest.java index b2514c1736f59..af0a9430416ac 100644 --- a/test/jdk/java/lang/StableValue/StableHeterogeneousContainerTest.java +++ b/test/jdk/java/lang/StableValue/StableHeterogeneousContainerTest.java @@ -37,6 +37,7 @@ import java.util.Comparator; import java.util.EnumSet; import java.util.LinkedHashSet; +import java.util.NoSuchElementException; import java.util.Set; import java.util.TreeSet; import java.util.stream.Collectors; @@ -77,13 +78,15 @@ void factoryInvariants() { @ParameterizedTest @MethodSource("nonEmptySets") - void trySet(Set inputs) { + void tryPut(Set inputs) { var container = StableValueFactories.ofHeterogeneousContainer(classes(inputs)); assertTrue(container.tryPut(Integer.class, Value.INTEGER.valueAs(Integer.class))); assertFalse(container.tryPut(Integer.class, Value.INTEGER.valueAs(Integer.class))); assertEquals(Value.INTEGER.value(), container.get(Integer.class)); - assertThrows(IllegalArgumentException.class, () -> container.tryPut(Long.class, 8L)); - assertThrows(NullPointerException.class, () -> container.tryPut(Short.class, null)); + var iae = assertThrows(IllegalArgumentException.class, () -> container.tryPut(Long.class, 8L)); + assertEquals("No such type: " + Long.class, iae.getMessage()); + var npe = assertThrows(NullPointerException.class, () -> container.tryPut(Short.class, null)); + assertEquals("The instance was null", npe.getMessage()); } @ParameterizedTest @@ -91,8 +94,10 @@ void trySet(Set inputs) { void computeIfAbsent(Set inputs) { var container = StableValueFactories.ofHeterogeneousContainer(classes(inputs)); assertEquals(Value.INTEGER.value(), container.computeIfAbsent(Integer.class, Value.INTEGER::valueAs)); - assertThrows(IllegalArgumentException.class, () -> container.computeIfAbsent(Long.class, _ -> 8L)); - assertThrows(NullPointerException.class, () -> container.computeIfAbsent(Short.class, _ -> null)); + var iae = assertThrows(IllegalArgumentException.class, () -> container.computeIfAbsent(Long.class, _ -> 8L)); + assertEquals("No such type: " + Long.class, iae.getMessage()); + var npe = assertThrows(NullPointerException.class, () -> container.computeIfAbsent(Short.class, _ -> null)); + assertEquals("The constructor returned null", npe.getMessage()); } @ParameterizedTest @@ -104,6 +109,16 @@ void get(Set inputs) { assertNull(container.get(Value.SHORT.clazz())); } + @ParameterizedTest + @MethodSource("nonEmptySets") + void getOrThrow(Set inputs) { + var container = StableValueFactories.ofHeterogeneousContainer(classes(inputs)); + assertTrue(container.tryPut(Integer.class, Value.INTEGER.valueAs(Integer.class))); + assertEquals(Value.INTEGER.value(), container.get(Integer.class)); + var e = assertThrows(NoSuchElementException.class , () -> container.getOrThrow(Value.SHORT.clazz())); + assertEquals("No instance associated with " + Short.class, e.getMessage()); + } + @ParameterizedTest @MethodSource("nonEmptySets") void toString(Set inputs) { From 26a91da3bb12a1240633043401cc27c5a5820bd8 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 10 Dec 2024 12:48:24 +0100 Subject: [PATCH 169/327] Improve docs slightly --- .../share/classes/java/lang/StableValue.java | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 08c203d87c13e..7188ce53a80d1 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -234,9 +234,9 @@ *} * *

    Composing stable values

    - * A stable value can depend on other stable values, thereby creating a dependency tree - * that can be lazily computed but where the individual tree branches still provide - * as-final performance. In the following example, a single {@code Foo} and a {@code Bar} + * A stable value can depend on other stable values, thereby creating a dependency graph + * that can be lazily computed but where access to individual elements still can be + * constant-folded. In the following example, a single {@code Foo} and a {@code Bar} * instance (that is dependent on the {@code Foo} instance) are lazily created, both of * which are held by stable values: * {@snippet lang = java: @@ -268,7 +268,7 @@ * Calling {@code bar()} will create the {@code Bar} singleton if needed and will also * first create the {@code Foo} (which it depends on) if needed. *

    - * Here is another example where a more complex dependency tree is created in which + * Here is another example where a more complex dependency graph is created in which * integers in the Fibonacci delta series are lazily computed: * {@snippet lang = java: * class Fibonacci { @@ -287,15 +287,15 @@ * } * } * Both {@code FIB} and {@code Fibonacci::fib} recurses into each other. Because the - * stable int function {@code FIB} internalizes intermediate results, the initial + * stable int function {@code FIB} caches intermediate results, the initial * computational complexity is reduced from exponential to linear compared to a - * traditional non-internalizing recursive fibonacci method. Once computed, the VM - * can constant-fold expressions like {@code Fibonacci.fib(10)}. + * traditional non-caching recursive fibonacci method. Once computed, the VM can + * constant-fold expressions like {@code Fibonacci.fib(10)}. *

    - * The fibonacci example above is a dependency graph with no circular dependencies. If - * there are circular dependencies in a dependency graph, a stable value will - * eventually throw a {@linkplain StackOverflowError} upon referencing elements in - * a circularity. + * The fibonacci example above is a dependency graph with no circular dependencies (i.e., + * it is a dependency tree). If there are circular dependencies in a dependency graph, + * a stable value will eventually throw a {@linkplain StackOverflowError} upon referencing + * elements in a circularity. * *

    Thread Safety

    * A holder value is guaranteed to be set at most once. If competing threads are From e6a8dcdbf4b91f5e6726b5af1746728a97e9c532 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 10 Dec 2024 13:37:09 +0100 Subject: [PATCH 170/327] Improve SHC --- .../stable/StableHeterogeneousContainer.java | 76 +++++++++++++------ .../StableHeterogeneousContainerTest.java | 6 +- 2 files changed, 55 insertions(+), 27 deletions(-) diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableHeterogeneousContainer.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableHeterogeneousContainer.java index 9be7b800cc725..cd6c5c621c240 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableHeterogeneousContainer.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableHeterogeneousContainer.java @@ -14,52 +14,78 @@ /** * A stable heterogeneous container that can associate types to implementing instances. *

    - * Attempting to associate a type with an instance that is {@code null} will result in - * a {@linkplain NullPointerException}. + * Attempting to associate a type with an instance that is {@code null} or attempting to + * provide a {@code type} that is {@code null} will result in a {@linkplain NullPointerException} + * for all methods in this class. * * @implNote Implementations of this interface are thread-safe - */ public sealed interface StableHeterogeneousContainer { /** - * {@return {@code true} if the provided {@code type} was associated with the - * provided {@code instance}, {@code false} otherwise} + * {@return {@code true} if the provided {@code type} was associated with the provided + * {@code instance}, {@code false} otherwise} *

    - * When this method returns, the provided {@code type} is always associated with - * an instance. + * When this method returns, the provided {@code type} is always associated with an + * instance. * * @param type the class type of the instance to store * @param instance the instance to store + * @throws ClassCastException if the provided {@code instance} is not + * {@code null} and is not assignable to the type T * @throws IllegalArgumentException if the provided {@code type} is not a type - * specified at creation + * specified at creation */ boolean tryPut(Class type, T instance); - T computeIfAbsent(Class type, Function, T> constructor); - /** * {@return the instance associated with the provided {@code type}, {@code null} - * otherwise} + * otherwise} + * * @param type used to retrieve an associated instance - * @param type of the associated instance + * @param type of the associated instance + * @throws ClassCastException if the associated instance is not assignable to + * the type T * @throws IllegalArgumentException if the provided {@code type} is not a type - * specified at creation + * specified at creation */ T get(Class type); /** * {@return the instance associated with the provided {@code type}, or throws - * NoSuchElementException} + * NoSuchElementException} + * * @param type used to retrieve an associated instance - * @param type of the associated instance + * @param type of the associated instance + * @throws ClassCastException if the associated instance is not assignable to + * the type T * @throws IllegalArgumentException if the provided {@code type} is not a type - * specified at creation - * @throws java.util.NoSuchElementException if the provided {@code type} is not - * associated with an instance + * specified at creation + * @throws NoSuchElementException if the provided {@code type} is not associated + * with an instance */ T getOrThrow(Class type); + /** + * {@return the instance associated with the provided {@code type}, if no association + * is made, first attempts to compute and associate an instance with the + * provided {@code type} using the provided {@code mapper}} + *

    + * The provided {@code mapper} is guaranteed to be invoked at most once if it + * completes without throwing an exception. + *

    + * If the mapper throws an (unchecked) exception, the exception is rethrown, and no + * association is made. + *

    + * When this method returns, the provided {@code type} is always associated with an + * instance. + * + * @param type used to retrieve an associated instance + * @param type of the associated instance + * @param mapper to be used for computing the associated instance + */ + T computeIfAbsent(Class type, Function, T> mapper); + final class Impl implements StableHeterogeneousContainer { @Stable @@ -73,7 +99,7 @@ public Impl(Set> types) { @Override public boolean tryPut(Class type, T instance) { Objects.requireNonNull(type); - Objects.requireNonNull(instance, "The instance was null"); + Objects.requireNonNull(instance, "The provided instance for '" + type + "' was null"); return of(type) .trySet( type.cast(instance) @@ -83,14 +109,14 @@ public boolean tryPut(Class type, T instance) { @SuppressWarnings("unchecked") @ForceInline @Override - public T computeIfAbsent(Class type, Function, T> constructor) { + public T computeIfAbsent(Class type, Function, T> mapper) { Objects.requireNonNull(type); - Objects.requireNonNull(constructor); + Objects.requireNonNull(mapper); final StableValue stableValue = of(type); if (stableValue.isSet()) { return (T) stableValue.orElseThrow(); } - return computeIfAbsentSlowPath(type, constructor, stableValue); + return computeIfAbsentSlowPath(type, mapper, stableValue); } @SuppressWarnings("unchecked") @@ -103,7 +129,8 @@ public T computeIfAbsentSlowPath(Class type, public Object get() { return type.cast( Objects.requireNonNull( - constructor.apply(type), "The constructor returned null" + constructor.apply(type), + "The constructor for `" + type + "` returned null" )); } }); @@ -112,6 +139,7 @@ public Object get() { @ForceInline @Override public T get(Class type) { + Objects.requireNonNull(type); return type.cast( of(type) .orElse(null) @@ -123,7 +151,7 @@ public T get(Class type) { public T getOrThrow(Class type) { final T t = get(type); if (t == null) { - throw new NoSuchElementException("No instance associated with " + type); + throw new NoSuchElementException("The type `" + type + "` is know but there is no instance associated with it"); } return t; } diff --git a/test/jdk/java/lang/StableValue/StableHeterogeneousContainerTest.java b/test/jdk/java/lang/StableValue/StableHeterogeneousContainerTest.java index af0a9430416ac..729d6ad43ebb5 100644 --- a/test/jdk/java/lang/StableValue/StableHeterogeneousContainerTest.java +++ b/test/jdk/java/lang/StableValue/StableHeterogeneousContainerTest.java @@ -86,7 +86,7 @@ void tryPut(Set inputs) { var iae = assertThrows(IllegalArgumentException.class, () -> container.tryPut(Long.class, 8L)); assertEquals("No such type: " + Long.class, iae.getMessage()); var npe = assertThrows(NullPointerException.class, () -> container.tryPut(Short.class, null)); - assertEquals("The instance was null", npe.getMessage()); + assertEquals("The provided instance for '" + Short.class + "' was null", npe.getMessage()); } @ParameterizedTest @@ -97,7 +97,7 @@ void computeIfAbsent(Set inputs) { var iae = assertThrows(IllegalArgumentException.class, () -> container.computeIfAbsent(Long.class, _ -> 8L)); assertEquals("No such type: " + Long.class, iae.getMessage()); var npe = assertThrows(NullPointerException.class, () -> container.computeIfAbsent(Short.class, _ -> null)); - assertEquals("The constructor returned null", npe.getMessage()); + assertEquals("The constructor for `" + Short.class + "` returned null", npe.getMessage()); } @ParameterizedTest @@ -116,7 +116,7 @@ void getOrThrow(Set inputs) { assertTrue(container.tryPut(Integer.class, Value.INTEGER.valueAs(Integer.class))); assertEquals(Value.INTEGER.value(), container.get(Integer.class)); var e = assertThrows(NoSuchElementException.class , () -> container.getOrThrow(Value.SHORT.clazz())); - assertEquals("No instance associated with " + Short.class, e.getMessage()); + assertEquals("The type `" + Short.class + "` is know but there is no instance associated with it", e.getMessage()); } @ParameterizedTest From 2c2382f8a6a0439e09c841d7cdb4d1ee5eeab627 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 10 Dec 2024 14:46:24 +0100 Subject: [PATCH 171/327] Improve test --- .../java/lang/StableValue/StableHeterogeneousContainerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/jdk/java/lang/StableValue/StableHeterogeneousContainerTest.java b/test/jdk/java/lang/StableValue/StableHeterogeneousContainerTest.java index 729d6ad43ebb5..fcedd7e70f04d 100644 --- a/test/jdk/java/lang/StableValue/StableHeterogeneousContainerTest.java +++ b/test/jdk/java/lang/StableValue/StableHeterogeneousContainerTest.java @@ -114,7 +114,7 @@ void get(Set inputs) { void getOrThrow(Set inputs) { var container = StableValueFactories.ofHeterogeneousContainer(classes(inputs)); assertTrue(container.tryPut(Integer.class, Value.INTEGER.valueAs(Integer.class))); - assertEquals(Value.INTEGER.value(), container.get(Integer.class)); + assertEquals(Value.INTEGER.value(), container.getOrThrow(Integer.class)); var e = assertThrows(NoSuchElementException.class , () -> container.getOrThrow(Value.SHORT.clazz())); assertEquals("The type `" + Short.class + "` is know but there is no instance associated with it", e.getMessage()); } From d056c123ba52b2fe9aea22373dbc584aeaeac6fc Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 10 Dec 2024 14:55:56 +0100 Subject: [PATCH 172/327] Improve wording in doc --- src/java.base/share/classes/java/lang/StableValue.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 7188ce53a80d1..dbbe696c7b019 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -265,8 +265,9 @@ * * } *} - * Calling {@code bar()} will create the {@code Bar} singleton if needed and will also - * first create the {@code Foo} (which it depends on) if needed. + * Calling {@code bar()} will create the {@code Bar} singleton if it is not already + * created. Upon such a creation, the dependent {@code Foo} will first be created if + * the {@code Foo} does not already exist. *

    * Here is another example where a more complex dependency graph is created in which * integers in the Fibonacci delta series are lazily computed: From dc3817a9dfd208389bef15f691b4abe82cae0219 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 20 Jan 2025 16:46:59 +0100 Subject: [PATCH 173/327] Update the specs from JEP and CSR comments --- .../share/classes/java/lang/StableValue.java | 239 +++++++++--------- .../java/util/ImmutableCollections.java | 12 +- .../lang/stable/StableEnumFunction.java | 4 +- .../internal/lang/stable/StableFunction.java | 4 +- .../stable/StableHeterogeneousContainer.java | 4 +- .../lang/stable/StableIntFunction.java | 4 +- .../internal/lang/stable/StableSupplier.java | 2 +- .../lang/stable/StableValueFactories.java | 23 +- .../internal/lang/stable/StableValueImpl.java | 2 +- .../CustomStableBiFunctionBenchmark.java | 20 +- .../lang/stable/CustomStableFunctions.java | 8 +- .../CustomStablePredicateBenchmark.java | 2 +- .../lang/stable/StableFunctionBenchmark.java | 8 +- .../stable/StableIntFunctionBenchmark.java | 8 +- .../lang/stable/StableSupplierBenchmark.java | 16 +- .../lang/stable/StableValueBenchmark.java | 20 +- 16 files changed, 187 insertions(+), 189 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index dbbe696c7b019..b6357d093663a 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 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 @@ -42,17 +42,16 @@ import java.util.function.Supplier; /** - * A stable value is a holder of deferred immutable data. + * A stable value is an immutable holder of deferred content. *

    * A {@linkplain StableValue {@code StableValue}} is created using the factory method - * {@linkplain StableValue#unset() {@code StableValue.unset()}}. When created, the - * stable value is unset, which means it holds no value. Its holder value, of - * type {@code T}, can be set by calling + * {@linkplain StableValue#ofUnset() {@code StableValue.unset()}}. When created, the + * stable value is unset, which means it holds no content. Its content + * , of type {@code T}, can be set by calling * {@linkplain #trySet(Object) trySet()}, {@linkplain #setOrThrow(Object) setOrThrow()}, - * or {@linkplain #computeIfUnset(Supplier) computeIfUnset()}. Once set, the holder value - * can never change and can be retrieved by calling - * {@linkplain #orElseThrow() orElseThrow()}, {@linkplain #orElse(Object) orElse()}, or - * {@linkplain #computeIfUnset(Supplier) computeIfUnset()}. + * or {@linkplain #orElseSet(Supplier) orElseSet()}. Once set, the content + * can never change and can be retrieved by calling {@linkplain #orElseThrow() orElseThrow()} + * , {@linkplain #orElse(Object) orElse()}, or {@linkplain #orElseSet(Supplier) orElseSet()}. *

    * A stable value that is set is treated as a constant by the JVM, enabling the * same performance optimizations that are available for {@code final} fields. @@ -61,17 +60,17 @@ * semantics associated with {@code final} fields is too restrictive. *

    * Consider the following example where a stable value field "{@code logger}" is an - * immutable holder of a value of type {@code Logger} and that is initially created + * immutable holder of content of type {@code Logger} and that is initially created * as unset, which means it holds no value. Later in the example, the * state of the "{@code logger}" field is checked and if it is still unset, - * a holder value is set: + * the content is set: * * {@snippet lang = java: * class Component { * - * // Creates a new unset stable value with no holder value - * // @link substring="unset" target="#unset" : - * private final StableValue logger = StableValue.unset(); + * // Creates a new unset stable value with no content + * // @link substring="ofUnset" target="#ofUnset" : + * private final StableValue logger = StableValue.ofUnset(); * * Logger getLogger() { * if (!logger.isSet()) { @@ -90,18 +89,18 @@ * Note that the holder value can only be set at most once. *

    * To guarantee that, even under races, only one instance of {@code Logger} is ever - * created, the {@linkplain #computeIfUnset(Supplier) computeIfUnset()} method can be used - * instead, where the holder is atomically and lazily computed via a lambda expression: + * created, the {@linkplain #orElseSet(Supplier) orElseSet()} method can be used + * instead, where the content is atomically and lazily computed via a lambda expression: * * {@snippet lang = java: * class Component { * - * // Creates a new unset stable value with no holder value - * // @link substring="unset" target="#unset" : - * private final StableValue logger = StableValue.unset(); + * // Creates a new unset stable value with no content + * // @link substring="ofUnset" target="#ofUnset" : + * private final StableValue logger = StableValue.ofUnset(); * * Logger getLogger() { - * return logger.computeIfUnset( () -> Logger.create(Component.class) ); + * return logger.orElseSet( () -> Logger.create(Component.class) ); * } * * void process() { @@ -111,14 +110,14 @@ * } *} *

    - * The {@code getLogger()} method calls {@code logger.computeIfUnset()} on the - * stable value to retrieve its holder value. If the stable value is unset, then - * {@code computeIfUnset()} evaluates and sets the holder value; the holder value is then - * returned to the client. In other words, {@code computeIfUnset()} guarantees that a - * stable value's holder value is set before it is used. + * The {@code getLogger()} method calls {@code logger.orElseSet()} on the stable value to + * retrieve its content. If the stable value is unset, then {@code orElseSet()} + * evaluates and sets the content; the content is then returned to the client. In other + * words, {@code orElseSet()} guarantees that a stable value's content is set + * before it is used. *

    - * Furthermore, {@code computeIfUnset()} guarantees that the lambda expression provided is - * evaluated only once, even when {@code logger.computeIfUnset()} is invoked concurrently. + * Furthermore, {@code orElseSet()} guarantees that the lambda expression provided is + * evaluated only once, even when {@code logger.orElseSet()} is invoked concurrently. * This property is crucial as evaluation of the lambda expression may have side effects, * e.g., the call above to {@code Logger.getLogger()} may result in storage resources * being prepared. @@ -127,16 +126,16 @@ * Stable values provide the foundation for higher-level functional abstractions. A * stable supplier is a supplier that computes a value and then caches it into * a backing stable value storage for later use. A stable supplier is created via the - * {@linkplain StableValue#ofSupplier(Supplier) StableValue.ofSupplier()} factory, - * by providing an original {@linkplain Supplier} which is invoked when the - * stable supplier is first accessed: + * {@linkplain StableValue#supplier(Supplier) StableValue.ofSupplier()} factory, by + * providing an original {@linkplain Supplier} which is invoked when the stable supplier + * is first accessed: * * {@snippet lang = java: * class Component { * * private final Supplier logger = - * // @link substring="ofSupplier" target="#ofSupplier(Supplier)" : - * StableValue.ofSupplier( () -> Logger.getLogger(Component.class) ); + * // @link substring="supplier" target="#supplier(Supplier)" : + * StableValue.supplier( () -> Logger.getLogger(Component.class) ); * * void process() { * logger.get().info("Process started"); @@ -151,7 +150,7 @@ * A stable int function is a function that takes an {@code int} parameter and * uses it to compute a result that is then cached into the backing stable value storage * for that parameter value. A stable int function is created via the - * {@linkplain StableValue#ofIntFunction(int, IntFunction) StableValue.ofIntFunction()} + * {@linkplain StableValue#intFunction(int, IntFunction) StableValue.ofIntFunction()} * factory. Upon creation, the input range (i.e. [0, size)) is specified together with * an original {@linkplain IntFunction} which is invoked at most once per input value. In * effect, the stable int function will act like a cache for the original {@linkplain IntFunction}: @@ -160,11 +159,11 @@ * class SqrtUtil { * * private static final IntFunction SQRT = - * // @link substring="ofIntFunction" target="#ofIntFunction(int,IntFunction)" : - * StableValue.ofIntFunction(10, StrictMath::sqrt); + * // @link substring="intFunction" target="#intFunction(int,IntFunction)" : + * StableValue.intFunction(10, StrictMath::sqrt); * * double sqrt9() { - * return SQRT.apply(9); // Eventually constant folds to 3.0 at runtime + * return SQRT.apply(9); // May eventually constant fold to 3.0 at runtime * } * * } @@ -173,20 +172,20 @@ * A stable function is a function that takes a parameter (of type {@code T}) and * uses it to compute a result that is then cached into the backing stable value storage * for that parameter value. A stable function is created via the - * {@linkplain StableValue#ofFunction(Set, Function) StableValue.ofFunction()} factory. - * Upon creation, the input {@linkplain Set} is specified together with an original {@linkplain Function} - * which is invoked at most once per input value. In effect, the stable function will act - * like a cache for the original {@linkplain Function}: + * {@linkplain StableValue#function(Set, Function) StableValue.ofFunction()} factory. + * Upon creation, the input {@linkplain Set} is specified together with an original + * {@linkplain Function} which is invoked at most once per input value. In effect, the + * stable function will act like a cache for the original {@linkplain Function}: * * {@snippet lang = java: * class SqrtUtil { * * private static final Function SQRT = - * // @link substring="ofFunction" target="#ofFunction(Set,Function)" : - * StableValue.ofFunction(Set.of(1, 2, 4, 8, 16, 32), StrictMath::sqrt); + * // @link substring="function" target="#function(Set,Function)" : + * StableValue.function(Set.of(1, 2, 4, 8, 16, 32), StrictMath::sqrt); * * double sqrt16() { - * return SQRT.apply(16); // Eventually constant folds to 4.0 at runtime + * return SQRT.apply(16); // May eventually constant fold to 4.0 at runtime * } * * } @@ -202,11 +201,11 @@ * class SqrtUtil { * * private static final List SQRT = - * // @link substring="ofList" target="#ofList(int,IntFunction)" : - * StableValue.ofList(10, StrictMath::sqrt); + * // @link substring="list" target="#list(int,IntFunction)" : + * StableValue.list(10, StrictMath::sqrt); * * double sqrt9() { - * return SQRT.apply(9); // Eventually constant folds to 3.0 at runtime + * return SQRT.apply(9); // May eventually constant fold to 3.0 at runtime * } * * } @@ -223,11 +222,11 @@ * class SqrtUtil { * * private static final Map SQRT = - * // @link substring="ofMap" target="#ofMap(Set,Function)" : - * StableValue.ofMap(Set.of(1, 2, 4, 8, 16, 32), StrictMath::sqrt); + * // @link substring="map" target="#map(Set,Function)" : + * StableValue.map(Set.of(1, 2, 4, 8, 16, 32), StrictMath::sqrt); * * double sqrt16() { - * return SQRT.apply(16); // Eventually constant folds to 4.0 at runtime + * return SQRT.apply(16); // May eventually constant fold to 4.0 at runtime * } * * } @@ -252,8 +251,8 @@ * } * } * - * private static final Supplier FOO = StableValue.ofSupplier(Foo::new); - * private static final Supplier BAR = StableValue.ofSupplier(() -> new Bar(FOO.get())); + * private static final Supplier FOO = StableValue.supplier(Foo::new); + * private static final Supplier BAR = StableValue.supplier(() -> new Bar(FOO.get())); * * public static Foo foo() { * return FOO.get(); @@ -277,7 +276,7 @@ * private static final int MAX_SIZE_INT = 46; * * private static final IntFunction FIB = - * StableValue.ofIntFunction(MAX_SIZE_INT, Fibonacci::fib); + * StableValue.intFunction(MAX_SIZE_INT, Fibonacci::fib); * * public static int fib(int n) { * return n < 2 @@ -286,7 +285,7 @@ * } * * } - * } + *} * Both {@code FIB} and {@code Fibonacci::fib} recurses into each other. Because the * stable int function {@code FIB} caches intermediate results, the initial * computational complexity is reduced from exponential to linear compared to a @@ -299,26 +298,26 @@ * elements in a circularity. * *

    Thread Safety

    - * A holder value is guaranteed to be set at most once. If competing threads are - * racing to set a stable value, only one update succeeds, while other updates are - * blocked until the stable value becomes set. + * The content of a stable value is guaranteed to be set at most once. If competing + * threads are racing to set a stable value, only one update succeeds, while other updates + * are blocked until the stable value becomes set. *

    * The at-most-once write operation on a stable value (e.g. {@linkplain #trySet(Object) trySet()}) * happens-before * any subsequent read operation (e.g. {@linkplain #orElseThrow()}). *

    - * The method {@linkplain StableValue#computeIfUnset(Supplier) computeIfUnset()} - * guarantees that the provided {@linkplain Supplier} is invoked successfully at most - * once even under race. Since stable functions and stable collections are built on top - * of {@linkplain StableValue#computeIfUnset(Supplier) computeIfUnset()} they too are + * The method {@linkplain StableValue#orElseSet(Supplier) orElseSet()} guarantees that + * the provided {@linkplain Supplier} is invoked successfully at most once even under + * race. Since stable functions and stable collections are built on top of + * {@linkplain StableValue#orElseSet(Supplier) orElseSet()} they too are * thread safe and guarantee at-most-once-per-input invocation. * *

    Miscellaneous

    - * Except for a StableValue's holder value itself, all method parameters must be + * Except for a StableValue's content itself, all method parameters must be * non-null or a {@link NullPointerException} will be thrown. *

    * Stable functions and collections are not {@link Serializable} as this would require - * {@linkplain #ofList(int, IntFunction) mappers} to be {@link Serializable} as well, + * {@linkplain #list(int, IntFunction) mappers} to be {@link Serializable} as well, * which would introduce security vulnerabilities. *

    * As objects can be set via stable values but never removed, this can be a source @@ -343,7 +342,7 @@ * are disabled, it is strongly discouraged to circumvent these protection means * as reflectively modifying such fields may lead to unspecified behavior. * - * @param type of the holder value + * @param type of the content * * @since 25 */ @@ -354,87 +353,87 @@ public sealed interface StableValue // Principal methods /** - * {@return {@code true} if the holder value was set to the provided - * {@code value}, {@code false} otherwise} + * {@return {@code true} if the content was set to the provided {@code value}, + * {@code false} otherwise} *

    - * When this method returns, the holder value is always set. + * When this method returns, the content is always set. * * @param value to set */ boolean trySet(T value); /** - * {@return the holder value if set, otherwise, returns the provided - * {@code other} value} - * + * {@return the content if set, otherwise, returns the provided {@code other} value} * - * @param other to return if the holder value is not set + * @param other to return if the content is not set */ T orElse(T other); /** - * {@return the holder value if set, otherwise, throws {@code NoSuchElementException}} + * {@return the content if set, otherwise, throws {@code NoSuchElementException}} * - * @throws NoSuchElementException if no holder value is set + * @throws NoSuchElementException if no content is set */ T orElseThrow(); /** - * {@return {@code true} if the holder value is set, {@code false} otherwise} + * {@return {@code true} if the content is set, {@code false} otherwise} */ boolean isSet(); /** - * {@return the holder value; if unset, first attempts to compute and set the - * holder value using the provided {@code supplier}} + * {@return the content; if unset, first attempts to compute and set the + * content using the provided {@code supplier}} *

    * The provided {@code supplier} is guaranteed to be invoked at most once if it * completes without throwing an exception. *

    * If the supplier throws an (unchecked) exception, the exception is rethrown, and no - * holder value is set. The most common usage is to construct a new object serving + * content is set. The most common usage is to construct a new object serving * as a lazily computed value or memoized result, as in: * - *

     {@code
    -     * Value witness = stable.computeIfUnset(Value::new);
    -     * }
    + * {@snippet lang=java: + * Value witness = stable.orElseSet(Value::new); + * } *

    - * When this method returns successfully, the holder value is always set. + * When this method returns successfully, the content is always set. * * @implSpec The implementation logic is equivalent to the following steps for this * {@code stable}: * - *

     {@code
    -     * if (stable.isSet()) {
    -     *     return stable.get();
    -     * } else {
    -     *     V newValue = supplier.get();
    -     *     stable.setOrThrow(newValue);
    -     *     return newValue;
    +     * {@snippet lang=java:
    +     *     if (stable.isSet()) {
    +     *         return stable.get();
    +     *      } else {
    +     *         V newValue = supplier.get();
    +     *         stable.setOrThrow(newValue);
    +     *         return newValue;
    +     *     }
          * }
    -     * }
    * Except it is thread-safe and will only return the same witness value * regardless if invoked by several threads. Also, the provided {@code supplier} * will only be invoked once even if invoked from several threads unless the * {@code supplier} throws an exception. * - * @param supplier to be used for computing the holder value + * @param supplier to be used for computing the content, if not previously set */ - T computeIfUnset(Supplier supplier); + T orElseSet(Supplier supplier); // Convenience methods /** - * Sets the holder value to the provided {@code value}, or, - * if already set, throws {@code IllegalStateException}. + * Sets the cintent to the provided {@code value}, or, if already set, + * throws {@code IllegalStateException}. *

    - * When this method returns (or throws an exception), the holder value is always set. + * When this method returns (or throws an exception), the content is always set. * * @param value to set - * @throws IllegalStateException if the holder value was already set + * @throws IllegalStateException if the content was already set */ void setOrThrow(T value); + // Object methods + /** * {@return {@code true} if {@code this == obj}, {@code false} otherwise} * @@ -453,22 +452,22 @@ public sealed interface StableValue /** * {@return a new unset stable value} *

    - * An unset stable value has no holder value. + * An unset stable value has no content. * - * @param type of the holder value + * @param type of the content */ - static StableValue unset() { - return StableValueFactories.unset(); + static StableValue ofUnset() { + return StableValueFactories.ofUnset(); } /** - * {@return a new set stable value holding the provided {@code value}} + * {@return a new set stable value holding the provided {@code content}} * - * @param value holder value to set - * @param type of the holder value + * @param content to set + * @param type of the content */ - static StableValue of(T value) { - return StableValueFactories.of(value); + static StableValue of(T content) { + return StableValueFactories.of(content); } /** @@ -485,14 +484,14 @@ static StableValue of(T value) { * thrown by the computing thread. *

    * If the provided {@code original} supplier throws an exception, it is relayed - * to the initial caller and no holder value is recorded. + * to the initial caller and no content is recorded. * * @param original supplier used to compute a cached value * @param the type of results supplied by the returned supplier */ - static Supplier ofSupplier(Supplier original) { + static Supplier supplier(Supplier original) { Objects.requireNonNull(original); - return StableValueFactories.ofSupplier(original); + return StableValueFactories.supplier(original); } /** @@ -511,19 +510,19 @@ static Supplier ofSupplier(Supplier original) { * the computing thread. *

    * If the provided {@code original} int function throws an exception, it is relayed - * to the initial caller and no holder value is recorded. + * to the initial caller and no content is recorded. * * @param size the size of the allowed inputs in {@code [0, size)} * @param original IntFunction used to compute cached values * @param the type of results delivered by the returned IntFunction */ - static IntFunction ofIntFunction(int size, - IntFunction original) { + static IntFunction intFunction(int size, + IntFunction original) { if (size < 0) { throw new IllegalArgumentException(); } Objects.requireNonNull(original); - return StableValueFactories.ofIntFunction(size, original); + return StableValueFactories.intFunction(size, original); } /** @@ -541,18 +540,18 @@ static IntFunction ofIntFunction(int size, * computed or an exception is thrown by the computing thread. *

    * If the provided {@code original} function throws an exception, it is relayed to - * the initial caller and no holder value is recorded. + * the initial caller and no content is recorded. * * @param inputs the set of allowed input values * @param original Function used to compute cached values * @param the type of the input to the returned Function * @param the type of results delivered by the returned Function */ - static Function ofFunction(Set inputs, - Function original) { + static Function function(Set inputs, + Function original) { Objects.requireNonNull(inputs); Objects.requireNonNull(original); - return StableValueFactories.ofFunction(inputs, original); + return StableValueFactories.function(inputs, original); } /** @@ -579,13 +578,13 @@ static Function ofFunction(Set inputs, * (may return {@code null}) * @param the type of elements in the returned list */ - static List ofList(int size, - IntFunction mapper) { + static List list(int size, + IntFunction mapper) { if (size < 0) { throw new IllegalArgumentException(); } Objects.requireNonNull(mapper); - return StableValueFactories.ofList(size, mapper); + return StableValueFactories.list(size, mapper); } /** @@ -610,11 +609,11 @@ static List ofList(int size, * @param the type of keys maintained by the returned map * @param the type of mapped values in the returned map */ - static Map ofMap(Set keys, - Function mapper) { + static Map map(Set keys, + Function mapper) { Objects.requireNonNull(keys); Objects.requireNonNull(mapper); - return StableValueFactories.ofMap(keys, mapper); + return StableValueFactories.map(keys, mapper); } } diff --git a/src/java.base/share/classes/java/util/ImmutableCollections.java b/src/java.base/share/classes/java/util/ImmutableCollections.java index ba91176d2362a..deec57ef3c5f2 100644 --- a/src/java.base/share/classes/java/util/ImmutableCollections.java +++ b/src/java.base/share/classes/java/util/ImmutableCollections.java @@ -778,7 +778,7 @@ static final class StableList extends AbstractImmutableList { StableList(int size, IntFunction mapper) { this.mapper = mapper; - this.backing = StableValueFactories.ofArray(size); + this.backing = StableValueFactories.array(size); } @Override public boolean isEmpty() { return backing.length == 0;} @@ -790,7 +790,7 @@ static final class StableList extends AbstractImmutableList { public E get(int i) { try { return backing[i] - .computeIfUnset(new Supplier() { + .orElseSet(new Supplier() { @Override public E get() { return mapper.apply(i); }}); } catch (ArrayIndexOutOfBoundsException aioobe) { throw new IndexOutOfBoundsException(i); @@ -1468,7 +1468,7 @@ static final class StableMap StableMap(Set keys, Function mapper) { this.mapper = mapper; - this.delegate = StableValueFactories.ofMap(keys); + this.delegate = StableValueFactories.map(keys); } @Override public boolean containsKey(Object o) { return delegate.containsKey(o); } @@ -1484,7 +1484,7 @@ public V get(Object key) { } @SuppressWarnings("unchecked") final K k = (K) key; - return stable.computeIfUnset(new Supplier() { + return stable.orElseSet(new Supplier() { @Override public V get() { return mapper.apply(k); }}); } @@ -1518,7 +1518,7 @@ final class LazyMapIterator implements Iterator> { public Entry next() { final Map.Entry> inner = delegateIterator.next(); final K k = inner.getKey(); - return new NullableKeyValueHolder<>(k, inner.getValue().computeIfUnset(new Supplier() { + return new NullableKeyValueHolder<>(k, inner.getValue().orElseSet(new Supplier() { @Override public V get() { return mapper.apply(k); }})); } @@ -1529,7 +1529,7 @@ public void forEachRemaining(Consumer> action) { @Override public void accept(Entry> inner) { final K k = inner.getKey(); - action.accept(new NullableKeyValueHolder<>(k, inner.getValue().computeIfUnset(new Supplier() { + action.accept(new NullableKeyValueHolder<>(k, inner.getValue().orElseSet(new Supplier() { @Override public V get() { return mapper.apply(k); }}))); } }; diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java index cfb1b5f336efb..eab53db45ea22 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java @@ -54,7 +54,7 @@ public R apply(E value) { final int index = value.ordinal() - firstOrdinal; try { return delegates[index] - .computeIfUnset(new Supplier() { + .orElseSet(new Supplier() { @Override public R get() { return original.apply(value); }}); } catch (ArrayIndexOutOfBoundsException ioob) { throw new IllegalArgumentException("Input not allowed: " + value, ioob); @@ -108,7 +108,7 @@ static , R> Function of(Set inputs, } final int size = max - min + 1; final Class enumType = (Class)inputs.iterator().next().getClass(); - return (Function) new StableEnumFunction(enumType, min, StableValueFactories.ofArray(size), (Function) original); + return (Function) new StableEnumFunction(enumType, min, StableValueFactories.array(size), (Function) original); } } diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableFunction.java index 89fcc1ea586cb..e96e99a7d25bc 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableFunction.java @@ -55,7 +55,7 @@ public R apply(T value) { if (stable == null) { throw new IllegalArgumentException("Input not allowed: " + value); } - return stable.computeIfUnset(new Supplier() { + return stable.orElseSet(new Supplier() { @Override public R get() { return original.apply(value); }}); } @@ -94,7 +94,7 @@ private String renderMappings() { static StableFunction of(Set inputs, Function original) { - return new StableFunction<>(StableValueFactories.ofMap(inputs), original); + return new StableFunction<>(StableValueFactories.map(inputs), original); } } diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableHeterogeneousContainer.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableHeterogeneousContainer.java index cd6c5c621c240..5fbb78b6447a6 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableHeterogeneousContainer.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableHeterogeneousContainer.java @@ -92,7 +92,7 @@ final class Impl implements StableHeterogeneousContainer { private final Map, StableValueImpl> map; public Impl(Set> types) { - this.map = StableValueFactories.ofMap(types); + this.map = StableValueFactories.map(types); } @ForceInline @@ -124,7 +124,7 @@ public T computeIfAbsent(Class type, Function, T> mapper) { public T computeIfAbsentSlowPath(Class type, Function, T> constructor, StableValue stableValue) { - return (T) stableValue.computeIfUnset(new Supplier() { + return (T) stableValue.orElseSet(new Supplier() { @Override public Object get() { return type.cast( diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableIntFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableIntFunction.java index 45c5401c3fd93..7cb9cdb6359b4 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableIntFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableIntFunction.java @@ -53,7 +53,7 @@ record StableIntFunction(@Stable StableValueImpl[] delegates, public R apply(int index) { try { return delegates[index] - .computeIfUnset(new Supplier() { + .orElseSet(new Supplier() { @Override public R get() { return original.apply(index); }}); } catch (ArrayIndexOutOfBoundsException ioob) { throw new IllegalArgumentException("Input not allowed: " + index, ioob); @@ -95,7 +95,7 @@ private String renderElements() { } static StableIntFunction of(int size, IntFunction original) { - return new StableIntFunction<>(StableValueFactories.ofArray(size), original); + return new StableIntFunction<>(StableValueFactories.array(size), original); } } diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableSupplier.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableSupplier.java index c1d05c459fa20..74d4992da2dcb 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableSupplier.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableSupplier.java @@ -42,7 +42,7 @@ record StableSupplier(StableValueImpl delegate, Supplier orig @ForceInline @Override public T get() { - return delegate.computeIfUnset(original); + return delegate.orElseSet(original); } @Override diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableValueFactories.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableValueFactories.java index 24c430e99bd95..a1b607d1bc6fa 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableValueFactories.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableValueFactories.java @@ -17,27 +17,27 @@ private StableValueFactories() {} // Factories - public static StableValueImpl unset() { + public static StableValueImpl ofUnset() { return StableValueImpl.newInstance(); } public static StableValueImpl of(T value) { - final StableValueImpl stableValue = unset(); + final StableValueImpl stableValue = ofUnset(); stableValue.trySet(value); return stableValue; } - public static Supplier ofSupplier(Supplier original) { + public static Supplier supplier(Supplier original) { return StableSupplier.of(original); } - public static IntFunction ofIntFunction(int size, - IntFunction original) { + public static IntFunction intFunction(int size, + IntFunction original) { return StableIntFunction.of(size, original); } - public static Function ofFunction(Set inputs, - Function original) { + public static Function function(Set inputs, + Function original) { if (inputs.isEmpty()) { return EmptyStableFunction.of(original); } @@ -50,17 +50,17 @@ public static StableHeterogeneousContainer ofHeterogeneousContainer(Set return new StableHeterogeneousContainer.Impl(types); } - public static List ofList(int size, IntFunction mapper) { + public static List list(int size, IntFunction mapper) { return SharedSecrets.getJavaUtilCollectionAccess().stableList(size, mapper); } - public static Map ofMap(Set keys, Function mapper) { + public static Map map(Set keys, Function mapper) { return SharedSecrets.getJavaUtilCollectionAccess().stableMap(keys, mapper); } // Supporting methods - public static StableValueImpl[] ofArray(int size) { + public static StableValueImpl[] array(int size) { if (size < 0) { throw new IllegalArgumentException(); } @@ -72,7 +72,7 @@ public static StableValueImpl[] ofArray(int size) { return stableValues; } - public static Map> ofMap(Set keys) { + public static Map> map(Set keys) { Objects.requireNonNull(keys); @SuppressWarnings("unchecked") final var entries = (Map.Entry>[]) new Map.Entry[keys.size()]; @@ -83,5 +83,4 @@ public static Map> ofMap(Set keys) { return Map.ofEntries(entries); } - } 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 a1471b390ec12..850864740c36e 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 @@ -114,7 +114,7 @@ public boolean isSet() { @ForceInline @Override - public T computeIfUnset(Supplier supplier) { + public T orElseSet(Supplier supplier) { final Object t = value; return (t == null) ? computeIfUnsetSlowPath(supplier) : unwrap(t); } diff --git a/test/micro/org/openjdk/bench/java/lang/stable/CustomStableBiFunctionBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/CustomStableBiFunctionBenchmark.java index 3869487063fc9..0f39195bc2c6c 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/CustomStableBiFunctionBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/CustomStableBiFunctionBenchmark.java @@ -90,8 +90,8 @@ public class CustomStableBiFunctionBenchmark { private static final BiFunction function = cachingBiFunction(SET, ORIGINAL);; private static final BiFunction function2 = cachingBiFunction(SET, ORIGINAL);; - private static final StableValue STABLE_VALUE = StableValue.unset(); - private static final StableValue STABLE_VALUE2 = StableValue.unset(); + private static final StableValue STABLE_VALUE = StableValue.ofUnset(); + private static final StableValue STABLE_VALUE2 = StableValue.ofUnset(); static { STABLE_VALUE.trySet(ORIGINAL.apply(VALUE, VALUE2)); @@ -121,7 +121,7 @@ public int staticStableValue() { // Pair seams to not work that well... static BiFunction cachingBiFunction2(Set> inputs, BiFunction original) { - final Function, R> delegate = StableValue.ofFunction(inputs, p -> original.apply(p.left(), p.right())); + final Function, R> delegate = StableValue.function(inputs, p -> original.apply(p.left(), p.right())); return (T t, U u) -> delegate.apply(new Pair<>(t, u)); } @@ -132,7 +132,7 @@ static BiFunction cachingBiFunction2(Set> inputs, record CachingBiFunction2(Function, R> delegate) implements BiFunction { public CachingBiFunction2(Set> inputs, BiFunction original) { - this(StableValue.ofFunction(inputs, (Pair p) -> original.apply(p.left(), p.right()))); + this(StableValue.function(inputs, (Pair p) -> original.apply(p.left(), p.right()))); } @Override @@ -153,7 +153,7 @@ record CachingBiFunction( public CachingBiFunction(Set> inputs, BiFunction original) { this(Map.copyOf(inputs.stream() - .collect(Collectors.toMap(Function.identity(), _ -> StableValue.unset()))), + .collect(Collectors.toMap(Function.identity(), _ -> StableValue.ofUnset()))), original ); } @@ -196,8 +196,8 @@ static Map>> delegate(Set>> map = inputs.stream() .collect(Collectors.groupingBy(Pair::left, Collectors.groupingBy(Pair::right, - Collectors.mapping((Function, ? extends StableValue>) _ -> StableValue.unset(), - Collectors.reducing(StableValue.unset(), _ -> StableValue.unset(), (StableValue a, StableValue b) -> a))))); + Collectors.mapping((Function, ? extends StableValue>) _ -> StableValue.ofUnset(), + Collectors.reducing(StableValue.ofUnset(), _ -> StableValue.ofUnset(), (StableValue a, StableValue b) -> a))))); @SuppressWarnings("unchecked") Map>> copy = Map.ofEntries(map.entrySet().stream() @@ -248,12 +248,12 @@ static Map> delegate(Set>> map = inputs.stream() .collect(Collectors.groupingBy(Pair::left, Collectors.groupingBy(Pair::right, - Collectors.mapping((Function, ? extends StableValue>) _ -> StableValue.unset(), - Collectors.reducing(StableValue.unset(), _ -> StableValue.unset(), (StableValue a, StableValue b) -> b))))); + Collectors.mapping((Function, ? extends StableValue>) _ -> StableValue.ofUnset(), + Collectors.reducing(StableValue.ofUnset(), _ -> StableValue.ofUnset(), (StableValue a, StableValue b) -> b))))); @SuppressWarnings("unchecked") Map> copy = Map.ofEntries(map.entrySet().stream() - .map(e -> Map.entry(e.getKey(), StableValue.ofFunction( e.getValue().keySet(), (U u) -> original.apply(e.getKey(), u)))) + .map(e -> Map.entry(e.getKey(), StableValue.function( e.getValue().keySet(), (U u) -> original.apply(e.getKey(), u)))) .toArray(Map.Entry[]::new)); return copy; diff --git a/test/micro/org/openjdk/bench/java/lang/stable/CustomStableFunctions.java b/test/micro/org/openjdk/bench/java/lang/stable/CustomStableFunctions.java index 32fc22ad28deb..a80f8cb86e8b2 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/CustomStableFunctions.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/CustomStableFunctions.java @@ -43,7 +43,7 @@ record Pair(L left, R right){} static Predicate cachingPredicate(Set inputs, Predicate original) { - final Function delegate = StableValue.ofFunction(inputs, original::test); + final Function delegate = StableValue.function(inputs, original::test); return delegate::apply; } @@ -56,7 +56,7 @@ static BiFunction cachingBiFunction(Set> inputs, // Collectors.toUnmodifiableMap() is crucial! final Map> map = tToUs.entrySet().stream() - .map(e -> Map.entry(e.getKey(), StableValue.ofFunction(e.getValue(), (U u) -> original.apply(e.getKey(), u)))) + .map(e -> Map.entry(e.getKey(), StableValue.function(e.getValue(), (U u) -> original.apply(e.getKey(), u)))) .collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue)); return (T t, U u) -> { @@ -83,13 +83,13 @@ static BinaryOperator cachingBinaryOperator(Set> inputs, static UnaryOperator cachingUnaryOperator(Set inputs, UnaryOperator original) { - final Function function = StableValue.ofFunction(inputs, original); + final Function function = StableValue.function(inputs, original); return function::apply; } static IntSupplier cachingIntSupplier(IntSupplier original) { - final Supplier delegate = StableValue.ofSupplier(original::getAsInt); + final Supplier delegate = StableValue.supplier(original::getAsInt); return delegate::get; } diff --git a/test/micro/org/openjdk/bench/java/lang/stable/CustomStablePredicateBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/CustomStablePredicateBenchmark.java index 99810d76c48c9..a10287e78eb7c 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/CustomStablePredicateBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/CustomStablePredicateBenchmark.java @@ -95,7 +95,7 @@ record CachingPredicate(Map> delegate, public CachingPredicate(Set inputs, Predicate original) { this(inputs.stream() - .collect(Collectors.toUnmodifiableMap(Function.identity(), _ -> StableValue.unset())), + .collect(Collectors.toUnmodifiableMap(Function.identity(), _ -> StableValue.ofUnset())), original ); } diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableFunctionBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableFunctionBenchmark.java index 029e1f23f71f1..62767d0644181 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/StableFunctionBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableFunctionBenchmark.java @@ -60,11 +60,11 @@ public class StableFunctionBenchmark { private static final int SIZE = 100; private static final Set SET = IntStream.range(0, SIZE).boxed().collect(Collectors.toSet()); - private static final Map STABLE = StableValue.ofMap(SET, Function.identity()); - private static final Function FUNCTION = StableValue.ofFunction(SET, Function.identity()); + private static final Map STABLE = StableValue.map(SET, Function.identity()); + private static final Function FUNCTION = StableValue.function(SET, Function.identity()); - private final Map stable = StableValue.ofMap(SET, Function.identity()); - private final Function function = StableValue.ofFunction(SET, Function.identity()); + private final Map stable = StableValue.map(SET, Function.identity()); + private final Function function = StableValue.function(SET, Function.identity()); @Benchmark public int stable() { diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableIntFunctionBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableIntFunctionBenchmark.java index 0c9f225c1fece..c28363cd94748 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/StableIntFunctionBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableIntFunctionBenchmark.java @@ -57,11 +57,11 @@ public class StableIntFunctionBenchmark { private static final int SIZE = 100; private static final IntFunction IDENTITY = i -> i; - private static final List STABLE = StableValue.ofList(SIZE, IDENTITY); - private static final IntFunction INT_FUNCTION = StableValue.ofIntFunction(SIZE, IDENTITY); + private static final List STABLE = StableValue.list(SIZE, IDENTITY); + private static final IntFunction INT_FUNCTION = StableValue.intFunction(SIZE, IDENTITY); - private final List stable = StableValue.ofList(SIZE, IDENTITY); - private final IntFunction intFunction = StableValue.ofIntFunction(SIZE, IDENTITY); + private final List stable = StableValue.list(SIZE, IDENTITY); + private final IntFunction intFunction = StableValue.intFunction(SIZE, IDENTITY); @Benchmark public int stable() { diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableSupplierBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableSupplierBenchmark.java index 36fe309b3cb8e..ead18e24f0d9f 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/StableSupplierBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableSupplierBenchmark.java @@ -56,15 +56,15 @@ public class StableSupplierBenchmark { private static final int VALUE = 42; private static final int VALUE2 = 23; - private static final StableValue STABLE = init(StableValue.unset(), VALUE); - private static final StableValue STABLE2 = init(StableValue.unset(), VALUE2); - private static final Supplier SUPPLIER = StableValue.ofSupplier(() -> VALUE); - private static final Supplier SUPPLIER2 = StableValue.ofSupplier(() -> VALUE); + private static final StableValue STABLE = init(StableValue.ofUnset(), VALUE); + private static final StableValue STABLE2 = init(StableValue.ofUnset(), VALUE2); + private static final Supplier SUPPLIER = StableValue.supplier(() -> VALUE); + private static final Supplier SUPPLIER2 = StableValue.supplier(() -> VALUE); - private final StableValue stable = init(StableValue.unset(), VALUE); - private final StableValue stable2 = init(StableValue.unset(), VALUE2); - private final Supplier supplier = StableValue.ofSupplier(() -> VALUE); - private final Supplier supplier2 = StableValue.ofSupplier(() -> VALUE2); + private final StableValue stable = init(StableValue.ofUnset(), VALUE); + private final StableValue stable2 = init(StableValue.ofUnset(), VALUE2); + private final Supplier supplier = StableValue.supplier(() -> VALUE); + private final Supplier supplier2 = StableValue.supplier(() -> VALUE2); @Benchmark public int stable() { diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java index 463f407ce3e40..6ac027d8c3db5 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java @@ -47,19 +47,19 @@ public class StableValueBenchmark { private static final int VALUE = 42; private static final int VALUE2 = 23; - private static final StableValue STABLE = init(StableValue.unset(), VALUE); - private static final StableValue STABLE2 = init(StableValue.unset(), VALUE2); - private static final StableValue DCL = init(StableValue.unset(), VALUE); - private static final StableValue DCL2 = init(StableValue.unset(), VALUE2); + private static final StableValue STABLE = init(StableValue.ofUnset(), VALUE); + private static final StableValue STABLE2 = init(StableValue.ofUnset(), VALUE2); + private static final StableValue DCL = init(StableValue.ofUnset(), VALUE); + private static final StableValue DCL2 = init(StableValue.ofUnset(), VALUE2); private static final AtomicReference ATOMIC = new AtomicReference<>(VALUE); private static final AtomicReference ATOMIC2 = new AtomicReference<>(VALUE2); private static final Holder HOLDER = new Holder(VALUE); private static final Holder HOLDER2 = new Holder(VALUE2); - private final StableValue stable = init(StableValue.unset(), VALUE); - private final StableValue stable2 = init(StableValue.unset(), VALUE2); - private final StableValue stableNull = StableValue.unset(); - private final StableValue stableNull2 = StableValue.unset(); + private final StableValue stable = init(StableValue.ofUnset(), VALUE); + private final StableValue stable2 = init(StableValue.ofUnset(), VALUE2); + private final StableValue stableNull = StableValue.ofUnset(); + private final StableValue stableNull2 = StableValue.ofUnset(); private final Supplier dcl = new Dcl<>(() -> VALUE); private final Supplier dcl2 = new Dcl<>(() -> VALUE2); private final AtomicReference atomic = new AtomicReference<>(VALUE); @@ -78,7 +78,7 @@ public void setup() { final int v = i; Dcl dclX = new Dcl<>(() -> v); sum += dclX.get(); - StableValue stableX = StableValue.unset(); + StableValue stableX = StableValue.ofUnset(); stableX.trySet(i); sum += stableX.orElseThrow(); } @@ -141,7 +141,7 @@ private static StableValue init(StableValue m, Integer value) // because StableValue fields have a special meaning. private static final class Holder { - private final StableValue delegate = StableValue.unset(); + private final StableValue delegate = StableValue.ofUnset(); Holder(int value) { delegate.setOrThrow(value); From e59083a717762042c6532e7056e5a06b0740d1f3 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 20 Jan 2025 17:26:42 +0100 Subject: [PATCH 174/327] Update typos in docs --- .../share/classes/java/lang/StableValue.java | 166 +++++++++--------- 1 file changed, 83 insertions(+), 83 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index b6357d093663a..d745073a63e35 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -45,7 +45,7 @@ * A stable value is an immutable holder of deferred content. *

    * A {@linkplain StableValue {@code StableValue}} is created using the factory method - * {@linkplain StableValue#ofUnset() {@code StableValue.unset()}}. When created, the + * {@linkplain StableValue#ofUnset() {@code StableValue.ofUnset()}}. When created, the * stable value is unset, which means it holds no content. Its content * , of type {@code T}, can be set by calling * {@linkplain #trySet(Object) trySet()}, {@linkplain #setOrThrow(Object) setOrThrow()}, @@ -126,22 +126,22 @@ * Stable values provide the foundation for higher-level functional abstractions. A * stable supplier is a supplier that computes a value and then caches it into * a backing stable value storage for later use. A stable supplier is created via the - * {@linkplain StableValue#supplier(Supplier) StableValue.ofSupplier()} factory, by + * {@linkplain StableValue#supplier(Supplier) StableValue.supplier()} factory, by * providing an original {@linkplain Supplier} which is invoked when the stable supplier * is first accessed: * * {@snippet lang = java: - * class Component { + * class Component { * - * private final Supplier logger = - * // @link substring="supplier" target="#supplier(Supplier)" : - * StableValue.supplier( () -> Logger.getLogger(Component.class) ); + * private final Supplier logger = + * // @link substring="supplier" target="#supplier(Supplier)" : + * StableValue.supplier( () -> Logger.getLogger(Component.class) ); * - * void process() { - * logger.get().info("Process started"); - * // ... - * } + * void process() { + * logger.get().info("Process started"); + * // ... * } + * } *} * A stable supplier encapsulates access to its backing stable value storage. This means * that code inside {@code Component} can obtain the logger object directly from the @@ -150,65 +150,65 @@ * A stable int function is a function that takes an {@code int} parameter and * uses it to compute a result that is then cached into the backing stable value storage * for that parameter value. A stable int function is created via the - * {@linkplain StableValue#intFunction(int, IntFunction) StableValue.ofIntFunction()} + * {@linkplain StableValue#intFunction(int, IntFunction) StableValue.intFunction()} * factory. Upon creation, the input range (i.e. [0, size)) is specified together with * an original {@linkplain IntFunction} which is invoked at most once per input value. In * effect, the stable int function will act like a cache for the original {@linkplain IntFunction}: * * {@snippet lang = java: - * class SqrtUtil { + * class SqrtUtil { * - * private static final IntFunction SQRT = - * // @link substring="intFunction" target="#intFunction(int,IntFunction)" : - * StableValue.intFunction(10, StrictMath::sqrt); - * - * double sqrt9() { - * return SQRT.apply(9); // May eventually constant fold to 3.0 at runtime - * } + * private static final IntFunction SQRT = + * // @link substring="intFunction" target="#intFunction(int,IntFunction)" : + * StableValue.intFunction(10, StrictMath::sqrt); * + * double sqrt9() { + * return SQRT.apply(9); // May eventually constant fold to 3.0 at runtime * } + * + * } *} *

    * A stable function is a function that takes a parameter (of type {@code T}) and * uses it to compute a result that is then cached into the backing stable value storage * for that parameter value. A stable function is created via the - * {@linkplain StableValue#function(Set, Function) StableValue.ofFunction()} factory. + * {@linkplain StableValue#function(Set, Function) StableValue.function()} factory. * Upon creation, the input {@linkplain Set} is specified together with an original * {@linkplain Function} which is invoked at most once per input value. In effect, the * stable function will act like a cache for the original {@linkplain Function}: * * {@snippet lang = java: - * class SqrtUtil { + * class SqrtUtil { * - * private static final Function SQRT = - * // @link substring="function" target="#function(Set,Function)" : - * StableValue.function(Set.of(1, 2, 4, 8, 16, 32), StrictMath::sqrt); - * - * double sqrt16() { - * return SQRT.apply(16); // May eventually constant fold to 4.0 at runtime - * } + * private static final Function SQRT = + * // @link substring="function" target="#function(Set,Function)" : + * StableValue.function(Set.of(1, 2, 4, 8, 16, 32), StrictMath::sqrt); * + * double sqrt16() { + * return SQRT.apply(16); // May eventually constant fold to 4.0 at runtime * } + * + * } *} * *

    Stable Collections

    * Stable values can also be used as backing storage for * {@linkplain Collection##unmodifiable unmodifiable collections}. A stable list * is an unmodifiable list, backed by an array of stable values. The stable list elements - * are computed when they are first accessed, using the provided {@linkplain IntFunction}: + * are computed when they are first accessed, using a provided {@linkplain IntFunction}: * * {@snippet lang = java: - * class SqrtUtil { + * class SqrtUtil { * - * private static final List SQRT = - * // @link substring="list" target="#list(int,IntFunction)" : - * StableValue.list(10, StrictMath::sqrt); - * - * double sqrt9() { - * return SQRT.apply(9); // May eventually constant fold to 3.0 at runtime - * } + * private static final List SQRT = + * // @link substring="list" target="#list(int,IntFunction)" : + * StableValue.list(10, StrictMath::sqrt); * + * double sqrt9() { + * return SQRT.apply(9); // May eventually constant fold to 3.0 at runtime * } + * + * } *} *

    * Note: In the example above, there is a constructor in the {@code Component} @@ -216,20 +216,20 @@ *

    * Similarly, a stable map is an unmodifiable map whose keys are known at * construction. The stable map values are computed when they are first accessed, - * using the provided {@linkplain Function}: + * using a provided {@linkplain Function}: * * {@snippet lang = java: - * class SqrtUtil { + * class SqrtUtil { * - * private static final Map SQRT = - * // @link substring="map" target="#map(Set,Function)" : - * StableValue.map(Set.of(1, 2, 4, 8, 16, 32), StrictMath::sqrt); - * - * double sqrt16() { - * return SQRT.apply(16); // May eventually constant fold to 4.0 at runtime - * } + * private static final Map SQRT = + * // @link substring="map" target="#map(Set,Function)" : + * StableValue.map(Set.of(1, 2, 4, 8, 16, 32), StrictMath::sqrt); * + * double sqrt16() { + * return SQRT.apply(16); // May eventually constant fold to 4.0 at runtime * } + * + * } *} * *

    Composing stable values

    @@ -239,30 +239,30 @@ * instance (that is dependent on the {@code Foo} instance) are lazily created, both of * which are held by stable values: * {@snippet lang = java: - * class Dependency { + * class Dependency { * - * public static class Foo { - * // ... - * } + * public static class Foo { + * // ... + * } * - * public static class Bar { - * public Bar(Foo foo) { - * // ... - * } + * public static class Bar { + * public Bar(Foo foo) { + * // ... * } + * } * - * private static final Supplier FOO = StableValue.supplier(Foo::new); - * private static final Supplier BAR = StableValue.supplier(() -> new Bar(FOO.get())); - * - * public static Foo foo() { - * return FOO.get(); - * } + * private static final Supplier FOO = StableValue.supplier(Foo::new); + * private static final Supplier BAR = StableValue.supplier(() -> new Bar(FOO.get())); * - * public static Bar bar() { - * return BAR.get(); - * } + * public static Foo foo() { + * return FOO.get(); + * } * + * public static Bar bar() { + * return BAR.get(); * } + * + * } *} * Calling {@code bar()} will create the {@code Bar} singleton if it is not already * created. Upon such a creation, the dependent {@code Foo} will first be created if @@ -271,20 +271,20 @@ * Here is another example where a more complex dependency graph is created in which * integers in the Fibonacci delta series are lazily computed: * {@snippet lang = java: - * class Fibonacci { - * - * private static final int MAX_SIZE_INT = 46; + * class Fibonacci { * - * private static final IntFunction FIB = - * StableValue.intFunction(MAX_SIZE_INT, Fibonacci::fib); + * private static final int MAX_SIZE_INT = 46; * - * public static int fib(int n) { - * return n < 2 - * ? n - * : FIB.apply(n - 1) + FIB.apply(n - 2); - * } + * private static final IntFunction FIB = + * StableValue.intFunction(MAX_SIZE_INT, Fibonacci::fib); * + * public static int fib(int n) { + * return n < 2 + * ? n + * : FIB.apply(n - 1) + FIB.apply(n - 2); * } + * + * } *} * Both {@code FIB} and {@code Fibonacci::fib} recurses into each other. Because the * stable int function {@code FIB} caches intermediate results, the initial @@ -321,9 +321,9 @@ * which would introduce security vulnerabilities. *

    * As objects can be set via stable values but never removed, this can be a source - * of unintended memory leaks. A stable value's set values are + * of unintended memory leaks. A stable value's content is * {@linkplain java.lang.ref##reachability strongly reachable}. Clients are advised that - * {@linkplain java.lang.ref##reachability reachable} stable values will hold set values + * {@linkplain java.lang.ref##reachability reachable} stable values will hold set content * perpetually. * * @implSpec Implementing classes of {@linkplain StableValue} are free to synchronize on @@ -393,7 +393,7 @@ public sealed interface StableValue * as a lazily computed value or memoized result, as in: * * {@snippet lang=java: - * Value witness = stable.orElseSet(Value::new); + * Value witness = stable.orElseSet(Value::new); * } *

    * When this method returns successfully, the content is always set. @@ -402,13 +402,13 @@ public sealed interface StableValue * {@code stable}: * * {@snippet lang=java: - * if (stable.isSet()) { - * return stable.get(); - * } else { - * V newValue = supplier.get(); - * stable.setOrThrow(newValue); - * return newValue; - * } + * if (stable.isSet()) { + * return stable.get(); + * } else { + * V newValue = supplier.get(); + * stable.setOrThrow(newValue); + * return newValue; + * } * } * Except it is thread-safe and will only return the same witness value * regardless if invoked by several threads. Also, the provided {@code supplier} From bc3a1a6a0ef0d86efb38b808e99992b6aec80dae Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 20 Jan 2025 19:14:14 +0100 Subject: [PATCH 175/327] Further improve docs --- .../share/classes/java/lang/StableValue.java | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index d745073a63e35..48e6d9df06984 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -44,10 +44,10 @@ /** * A stable value is an immutable holder of deferred content. *

    - * A {@linkplain StableValue {@code StableValue}} is created using the factory method - * {@linkplain StableValue#ofUnset() {@code StableValue.ofUnset()}}. When created, the - * stable value is unset, which means it holds no content. Its content - * , of type {@code T}, can be set by calling + * A {@linkplain StableValue {@code StableValue}} can be created using the factory + * method {@linkplain StableValue#ofUnset() {@code StableValue.ofUnset()}}. When created + * this way, the stable value is unset, which means it holds no content. + * Its content, of type {@code T}, can be set by calling * {@linkplain #trySet(Object) trySet()}, {@linkplain #setOrThrow(Object) setOrThrow()}, * or {@linkplain #orElseSet(Supplier) orElseSet()}. Once set, the content * can never change and can be retrieved by calling {@linkplain #orElseThrow() orElseThrow()} @@ -148,7 +148,7 @@ * stable supplier, without having to go through an accessor method like {@code getLogger()}. *

    * A stable int function is a function that takes an {@code int} parameter and - * uses it to compute a result that is then cached into the backing stable value storage + * uses it to compute a result that is then cached by the backing stable value storage * for that parameter value. A stable int function is created via the * {@linkplain StableValue#intFunction(int, IntFunction) StableValue.intFunction()} * factory. Upon creation, the input range (i.e. [0, size)) is specified together with @@ -323,8 +323,8 @@ * As objects can be set via stable values but never removed, this can be a source * of unintended memory leaks. A stable value's content is * {@linkplain java.lang.ref##reachability strongly reachable}. Clients are advised that - * {@linkplain java.lang.ref##reachability reachable} stable values will hold set content - * perpetually. + * {@linkplain java.lang.ref##reachability reachable} stable values will hold their set + * content perpetually. * * @implSpec Implementing classes of {@linkplain StableValue} are free to synchronize on * {@code this} and consequently, care should be taken whenever @@ -336,11 +336,12 @@ * a class and is usually neither exposed directly via accessors nor passed as * a method parameter. * Instance fields explicitly declared as {@code StableValue} or one-dimensional - * arrays thereof are eligible for certain JVM optimizations where normal - * instance fields are not. This comes with restrictions on reflective - * modifications. Although most ways of reflective modification of such fields - * are disabled, it is strongly discouraged to circumvent these protection means - * as reflectively modifying such fields may lead to unspecified behavior. + * arrays thereof might in some VM implementation be eligible for certain + * JVM optimizations where normal instance fields are not. In such cases, this + * comes with restrictions on reflective modifications -- although most ways of + * reflective modification of such fields are disabled, it is strongly + * discouraged to circumvent these protection means as reflectively modifying + * such fields may lead to unspecified behavior. * * @param type of the content * @@ -461,7 +462,7 @@ static StableValue ofUnset() { } /** - * {@return a new set stable value holding the provided {@code content}} + * {@return a new pre-set stable value with the provided {@code content}} * * @param content to set * @param type of the content From c4322b09e51930d85359676c2435d8cbbfba3eae Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 4 Feb 2025 10:19:01 +0000 Subject: [PATCH 176/327] Remove VM impl note --- src/java.base/share/classes/java/lang/StableValue.java | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 48e6d9df06984..eeb2ecf2114b9 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -42,7 +42,7 @@ import java.util.function.Supplier; /** - * A stable value is an immutable holder of deferred content. + * A stable value is a shallowly immutable holder of deferred content. *

    * A {@linkplain StableValue {@code StableValue}} can be created using the factory * method {@linkplain StableValue#ofUnset() {@code StableValue.ofUnset()}}. When created @@ -335,13 +335,6 @@ * @implNote A {@linkplain StableValue} is mainly intended to be a non-public field in * a class and is usually neither exposed directly via accessors nor passed as * a method parameter. - * Instance fields explicitly declared as {@code StableValue} or one-dimensional - * arrays thereof might in some VM implementation be eligible for certain - * JVM optimizations where normal instance fields are not. In such cases, this - * comes with restrictions on reflective modifications -- although most ways of - * reflective modification of such fields are disabled, it is strongly - * discouraged to circumvent these protection means as reflectively modifying - * such fields may lead to unspecified behavior. * * @param type of the content * From 52426409a96dab0408853569f2c789fb4bb49c59 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 4 Feb 2025 10:55:07 +0000 Subject: [PATCH 177/327] Update docs --- .../share/classes/java/lang/StableValue.java | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index eeb2ecf2114b9..88faedeeaae43 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -59,9 +59,9 @@ * at-most-once update semantics is crucial, but where the eager initialization * semantics associated with {@code final} fields is too restrictive. *

    - * Consider the following example where a stable value field "{@code logger}" is an - * immutable holder of content of type {@code Logger} and that is initially created - * as unset, which means it holds no value. Later in the example, the + * Consider the following example where a stable value field "{@code logger}" is a + * shallowly immutable holder of content of type {@code Logger} and that is initially + * created as unset, which means it holds no content. Later in the example, the * state of the "{@code logger}" field is checked and if it is still unset, * the content is set: * @@ -90,7 +90,9 @@ *

    * To guarantee that, even under races, only one instance of {@code Logger} is ever * created, the {@linkplain #orElseSet(Supplier) orElseSet()} method can be used - * instead, where the content is atomically and lazily computed via a lambda expression: + * instead, where the content is atomically and lazily computed via a + * {@linkplain Supplier supplier}. In the example below, the supplier is provided in the + * form of a lambda expression: * * {@snippet lang = java: * class Component { @@ -116,9 +118,9 @@ * words, {@code orElseSet()} guarantees that a stable value's content is set * before it is used. *

    - * Furthermore, {@code orElseSet()} guarantees that the lambda expression provided is + * Furthermore, {@code orElseSet()} guarantees that the supplier provided is * evaluated only once, even when {@code logger.orElseSet()} is invoked concurrently. - * This property is crucial as evaluation of the lambda expression may have side effects, + * This property is crucial as evaluation of the supplier may have side effects, * e.g., the call above to {@code Logger.getLogger()} may result in storage resources * being prepared. * @@ -170,8 +172,8 @@ *} *

    * A stable function is a function that takes a parameter (of type {@code T}) and - * uses it to compute a result that is then cached into the backing stable value storage - * for that parameter value. A stable function is created via the + * uses it to compute a result (of type {@code R}) that is then cached into a backing + * stable value storage for that parameter value. A stable function is created via the * {@linkplain StableValue#function(Set, Function) StableValue.function()} factory. * Upon creation, the input {@linkplain Set} is specified together with an original * {@linkplain Function} which is invoked at most once per input value. In effect, the @@ -211,9 +213,6 @@ * } *} *

    - * Note: In the example above, there is a constructor in the {@code Component} - * class that takes an {@code int} parameter. - *

    * Similarly, a stable map is an unmodifiable map whose keys are known at * construction. The stable map values are computed when they are first accessed, * using a provided {@linkplain Function}: @@ -289,7 +288,7 @@ * Both {@code FIB} and {@code Fibonacci::fib} recurses into each other. Because the * stable int function {@code FIB} caches intermediate results, the initial * computational complexity is reduced from exponential to linear compared to a - * traditional non-caching recursive fibonacci method. Once computed, the VM can + * traditional non-caching recursive fibonacci method. Once computed, the VM is free to * constant-fold expressions like {@code Fibonacci.fib(10)}. *

    * The fibonacci example above is a dependency graph with no circular dependencies (i.e., @@ -302,7 +301,8 @@ * threads are racing to set a stable value, only one update succeeds, while other updates * are blocked until the stable value becomes set. *

    - * The at-most-once write operation on a stable value (e.g. {@linkplain #trySet(Object) trySet()}) + * The at-most-once write operation on a stable value that succeeds + * (e.g. {@linkplain #trySet(Object) trySet()}) * happens-before * any subsequent read operation (e.g. {@linkplain #orElseThrow()}). *

    From a30540b3eeec9373c9f2665966af4b0fe80432cc Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 4 Feb 2025 11:57:42 +0000 Subject: [PATCH 178/327] Update docs and factory method name --- .../share/classes/java/lang/StableValue.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 88faedeeaae43..cac1bcfe4a957 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -45,7 +45,7 @@ * A stable value is a shallowly immutable holder of deferred content. *

    * A {@linkplain StableValue {@code StableValue}} can be created using the factory - * method {@linkplain StableValue#ofUnset() {@code StableValue.ofUnset()}}. When created + * method {@linkplain StableValue#of() {@code StableValue.of()}}. When created * this way, the stable value is unset, which means it holds no content. * Its content, of type {@code T}, can be set by calling * {@linkplain #trySet(Object) trySet()}, {@linkplain #setOrThrow(Object) setOrThrow()}, @@ -69,8 +69,8 @@ * class Component { * * // Creates a new unset stable value with no content - * // @link substring="ofUnset" target="#ofUnset" : - * private final StableValue logger = StableValue.ofUnset(); + * // @link substring="of" target="#of" : + * private final StableValue logger = StableValue.of(); * * Logger getLogger() { * if (!logger.isSet()) { @@ -98,8 +98,8 @@ * class Component { * * // Creates a new unset stable value with no content - * // @link substring="ofUnset" target="#ofUnset" : - * private final StableValue logger = StableValue.ofUnset(); + * // @link substring="of" target="#of" : + * private final StableValue logger = StableValue.of(); * * Logger getLogger() { * return logger.orElseSet( () -> Logger.create(Component.class) ); @@ -450,8 +450,8 @@ public sealed interface StableValue * * @param type of the content */ - static StableValue ofUnset() { - return StableValueFactories.ofUnset(); + static StableValue of() { + return StableValueFactories.of(); } /** From ee97236d7c9fadfb89dd2a632b14653bdfb3d23b Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 4 Feb 2025 11:58:06 +0000 Subject: [PATCH 179/327] Call renames factories --- .../lang/stable/StableValueFactories.java | 4 +- test/jdk/java/lang/StableValue/JepTest.java | 44 +++++++++---------- .../lang/StableValue/StableFunctionTest.java | 22 +++++----- .../StableValue/StableIntFunctionTest.java | 16 +++---- .../java/lang/StableValue/StableListTest.java | 20 ++++----- .../java/lang/StableValue/StableMapTest.java | 16 +++---- .../lang/StableValue/StableSupplierTest.java | 14 +++--- .../lang/StableValue/StableValueTest.java | 36 +++++++-------- .../StableValuesSafePublicationTest.java | 2 +- .../StableValue/TrustedFieldTypeTest.java | 20 ++++----- .../CustomStableBiFunctionBenchmark.java | 14 +++--- .../CustomStablePredicateBenchmark.java | 2 +- .../lang/stable/StableSupplierBenchmark.java | 8 ++-- .../lang/stable/StableValueBenchmark.java | 20 ++++----- 14 files changed, 119 insertions(+), 119 deletions(-) diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableValueFactories.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableValueFactories.java index a1b607d1bc6fa..b357231d0beb6 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableValueFactories.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableValueFactories.java @@ -17,12 +17,12 @@ private StableValueFactories() {} // Factories - public static StableValueImpl ofUnset() { + public static StableValueImpl of() { return StableValueImpl.newInstance(); } public static StableValueImpl of(T value) { - final StableValueImpl stableValue = ofUnset(); + final StableValueImpl stableValue = of(); stableValue.trySet(value); return stableValue; } diff --git a/test/jdk/java/lang/StableValue/JepTest.java b/test/jdk/java/lang/StableValue/JepTest.java index 8896517b6fb0f..1db28a638b984 100644 --- a/test/jdk/java/lang/StableValue/JepTest.java +++ b/test/jdk/java/lang/StableValue/JepTest.java @@ -132,7 +132,7 @@ public Logger logger(int i) { class Foo { // 1. Declare a Stable field - private static final StableValue LOGGER = StableValue.unset(); + private static final StableValue LOGGER = StableValue.of(); static Logger logger() { @@ -150,7 +150,7 @@ class Foo2 { // 1. Centrally declare a caching supplier and define how it should be computed private static final Supplier LOGGER = - StableValue.ofSupplier( () -> Logger.getLogger("com.company.Foo") ); + StableValue.supplier( () -> Logger.getLogger("com.company.Foo") ); static Logger logger() { @@ -165,7 +165,7 @@ class MapDemo { // 1. Declare a lazy stable map of loggers with two allowable keys: // "com.company.Bar" and "com.company.Baz" static final Map LOGGERS = - StableValue.ofMap(Set.of("com.company.Foo", "com.company.Bar"), Logger::getLogger); + StableValue.map(Set.of("com.company.Foo", "com.company.Bar"), Logger::getLogger); // 2. Access the lazy map with as-declared-final performance // (evaluation made before the first access) @@ -178,7 +178,7 @@ static Logger logger(String name) { class CachedNum { // 1. Centrally declare a cached IntFunction backed by a list of StableValue elements private static final IntFunction LOGGERS = - StableValue.ofIntFunction(2, CachedNum::fromNumber); + StableValue.intFunction(2, CachedNum::fromNumber); // 2. Define a function that is to be called the first // time a particular message number is referenced @@ -200,7 +200,7 @@ class Cached { // 1. Centrally declare a cached function backed by a map of stable values private static final Function LOGGERS = - StableValue.ofFunction(Set.of("com.company.Foo", "com.company.Bar"), + StableValue.function(Set.of("com.company.Foo", "com.company.Bar"), Logger::getLogger); private static final String NAME = "com.company.Foo"; @@ -218,7 +218,7 @@ class ErrorMessages { // 1. Centrally declare a cached IntFunction backed by a list of StableValue elements private static final IntFunction ERROR_FUNCTION = - StableValue.ofIntFunction(SIZE, ErrorMessages::readFromFile); + StableValue.intFunction(SIZE, ErrorMessages::readFromFile); // 2. Define a function that is to be called the first // time a particular message number is referenced @@ -241,7 +241,7 @@ record CachingPredicate(Map> delegate, public CachingPredicate(Set inputs, Predicate original) { this(inputs.stream() - .collect(Collectors.toMap(Function.identity(), _ -> StableValue.unset())), + .collect(Collectors.toMap(Function.identity(), _ -> StableValue.of())), original::test ); } @@ -253,14 +253,14 @@ public boolean test(T t) { throw new IllegalArgumentException(t.toString()); } - return stable.computeIfUnset(() -> original.apply(t)); + return stable.orElseSet(() -> original.apply(t)); } } record CachingPredicate2(Map delegate) implements Predicate { public CachingPredicate2(Set inputs, Predicate original) { - this(StableValue.ofMap(inputs, original::test)); + this(StableValue.map(inputs, original::test)); } @Override @@ -271,7 +271,7 @@ public boolean test(T t) { public static void main(String[] args) { Predicate even = i -> i % 2 == 0; - Predicate cachingPredicate = StableValue.ofMap(Set.of(1, 2), even::test)::get; + Predicate cachingPredicate = StableValue.map(Set.of(1, 2), even::test)::get; } record Pair(L left, R right){} @@ -286,13 +286,13 @@ public R apply(T t, U u) { if (stable == null) { throw new IllegalArgumentException(t + ", " + u); } - return stable.computeIfUnset(() -> original.apply(key)); + return stable.orElseSet(() -> original.apply(key)); } static CachingBiFunction of(Set> inputs, BiFunction original) { Map, StableValue> map = inputs.stream() - .collect(Collectors.toMap(Function.identity(), _ -> StableValue.unset())); + .collect(Collectors.toMap(Function.identity(), _ -> StableValue.of())); return new CachingBiFunction<>(map, pair -> original.apply(pair.left(), pair.right())); } @@ -309,13 +309,13 @@ public R apply(T t, U u) { if (stable == null) { throw new IllegalArgumentException(t + ", " + u); } - return stable.computeIfUnset(() -> original.apply(key.left(), key.right())); + return stable.orElseSet(() -> original.apply(key.left(), key.right())); } static BiFunction of(Set> inputs, BiFunction original) { Map, StableValue> map = inputs.stream() - .collect(Collectors.toMap(Function.identity(), _ -> StableValue.unset())); + .collect(Collectors.toMap(Function.identity(), _ -> StableValue.of())); return new CachingBiFunction2<>(map, original); } @@ -324,10 +324,10 @@ static BiFunction of(Set> inputs, BiFunction LOGGER = StableValue.unset(); + private final StableValue LOGGER = StableValue.of(); public Logger getLogger() { - return LOGGER.computeIfUnset(() -> Logger.getLogger("com.company.Foo")); + return LOGGER.orElseSet(() -> Logger.getLogger("com.company.Foo")); } } @@ -335,12 +335,12 @@ record CachingIntPredicate(List> outputs, IntPredicate resultFunction) implements IntPredicate { CachingIntPredicate(int size, IntPredicate resultFunction) { - this(Stream.generate(StableValue::unset).limit(size).toList(), resultFunction); + this(Stream.generate(StableValue::of).limit(size).toList(), resultFunction); } @Override public boolean test(int value) { - return outputs.get(value).computeIfUnset(() -> resultFunction.test(value)); + return outputs.get(value).orElseSet(() -> resultFunction.test(value)); } } @@ -351,7 +351,7 @@ class FixedStableList extends AbstractList { private final StableValue[] elements; FixedStableList(int size) { - this.elements = Stream.generate(StableValue::unset) + this.elements = Stream.generate(StableValue::of) .limit(size) .toArray(StableValue[]::new); } @@ -390,8 +390,8 @@ public Bar(Foo foo) { } } - private static final Supplier FOO = StableValue.ofSupplier(Foo::new); - private static final Supplier BAR = StableValue.ofSupplier(() -> new Bar(FOO.get())); + private static final Supplier FOO = StableValue.supplier(Foo::new); + private static final Supplier BAR = StableValue.supplier(() -> new Bar(FOO.get())); public static Foo foo() { return FOO.get(); @@ -409,7 +409,7 @@ class Fibonacci { private static final int MAX_SIZE_INT = 46; private static final IntFunction FIB = - StableValue.ofIntFunction(MAX_SIZE_INT, Fibonacci::fib); + StableValue.intFunction(MAX_SIZE_INT, Fibonacci::fib); public static int fib(int n) { return n < 2 diff --git a/test/jdk/java/lang/StableValue/StableFunctionTest.java b/test/jdk/java/lang/StableValue/StableFunctionTest.java index c57b7fcdf45a8..b9c8157c1a589 100644 --- a/test/jdk/java/lang/StableValue/StableFunctionTest.java +++ b/test/jdk/java/lang/StableValue/StableFunctionTest.java @@ -72,8 +72,8 @@ int asInt() { @ParameterizedTest @MethodSource("allSets") void factoryInvariants(Set inputs) { - assertThrows(NullPointerException.class, () -> StableValue.ofFunction(null, MAPPER)); - assertThrows(NullPointerException.class, () -> StableValue.ofFunction(inputs, null)); + assertThrows(NullPointerException.class, () -> StableValue.function(null, MAPPER)); + assertThrows(NullPointerException.class, () -> StableValue.function(inputs, null)); } @ParameterizedTest @@ -85,7 +85,7 @@ void basic(Set inputs) { void basic(Set inputs, Function mapper) { StableTestUtil.CountingFunction cif = new StableTestUtil.CountingFunction<>(mapper); - var cached = StableValue.ofFunction(inputs, cif); + var cached = StableValue.function(inputs, cif); assertEquals(mapper.apply(Value.FORTY_TWO), cached.apply(Value.FORTY_TWO)); assertEquals(1, cif.cnt()); assertEquals(mapper.apply(Value.FORTY_TWO), cached.apply(Value.FORTY_TWO)); @@ -104,7 +104,7 @@ void basic(Set inputs, Function mapper) { @ParameterizedTest @MethodSource("emptySets") void empty(Set inputs) { - Function f0 = StableValue.ofFunction(inputs, Value::asInt); + Function f0 = StableValue.function(inputs, Value::asInt); assertTrue(f0.toString().contains("{}")); } @@ -114,7 +114,7 @@ void exception(Set inputs) { StableTestUtil.CountingFunction cif = new StableTestUtil.CountingFunction<>(_ -> { throw new UnsupportedOperationException(); }); - var cached = StableValue.ofFunction(inputs, cif); + var cached = StableValue.function(inputs, cif); assertThrows(UnsupportedOperationException.class, () -> cached.apply(Value.FORTY_TWO)); assertEquals(1, cif.cnt()); assertThrows(UnsupportedOperationException.class, () -> cached.apply(Value.FORTY_TWO)); @@ -130,7 +130,7 @@ void exception(Set inputs) { @MethodSource("nonEmptySets") void circular(Set inputs) { final AtomicReference> ref = new AtomicReference<>(); - Function> cached = StableValue.ofFunction(inputs, _ -> ref.get()); + Function> cached = StableValue.function(inputs, _ -> ref.get()); ref.set(cached); cached.apply(Value.FORTY_TWO); String toString = cached.toString(); @@ -143,8 +143,8 @@ void circular(Set inputs) { @MethodSource("allSets") void equality(Set inputs) { Function mapper = Value::asInt; - Function f0 = StableValue.ofFunction(inputs, mapper); - Function f1 = StableValue.ofFunction(inputs, mapper); + Function f0 = StableValue.function(inputs, mapper); + Function f1 = StableValue.function(inputs, mapper); // No function is equal to another function assertNotEquals(f0, f1); } @@ -152,7 +152,7 @@ void equality(Set inputs) { @ParameterizedTest @MethodSource("allSets") void hashCodeStable(Set inputs) { - Function f0 = StableValue.ofFunction(inputs, Value::asInt); + Function f0 = StableValue.function(inputs, Value::asInt); assertEquals(System.identityHashCode(f0), f0.hashCode()); if (!inputs.isEmpty()) { f0.apply(Value.FORTY_TWO); @@ -162,9 +162,9 @@ void hashCodeStable(Set inputs) { @Test void usesOptimizedVersion() { - Function enumFunction = StableValue.ofFunction(EnumSet.of(Value.FORTY_TWO), Value::asInt); + Function enumFunction = StableValue.function(EnumSet.of(Value.FORTY_TWO), Value::asInt); assertEquals("jdk.internal.lang.stable.StableEnumFunction", enumFunction.getClass().getName()); - Function emptyFunction = StableValue.ofFunction(Set.of(), Value::asInt); + Function emptyFunction = StableValue.function(Set.of(), Value::asInt); assertEquals("jdk.internal.lang.stable.EmptyStableFunction", emptyFunction.getClass().getName()); } diff --git a/test/jdk/java/lang/StableValue/StableIntFunctionTest.java b/test/jdk/java/lang/StableValue/StableIntFunctionTest.java index 0141f92a91fb8..a7c73b767979e 100644 --- a/test/jdk/java/lang/StableValue/StableIntFunctionTest.java +++ b/test/jdk/java/lang/StableValue/StableIntFunctionTest.java @@ -41,8 +41,8 @@ final class StableIntFunctionTest { @Test void factoryInvariants() { - assertThrows(IllegalArgumentException.class, () -> StableValue.ofIntFunction(-1, MAPPER)); - assertThrows(NullPointerException.class, () -> StableValue.ofIntFunction(SIZE, null)); + assertThrows(IllegalArgumentException.class, () -> StableValue.intFunction(-1, MAPPER)); + assertThrows(NullPointerException.class, () -> StableValue.intFunction(SIZE, null)); } @Test @@ -53,7 +53,7 @@ void basic() { void basic(IntFunction mapper) { StableTestUtil.CountingIntFunction cif = new StableTestUtil.CountingIntFunction<>(mapper); - var cached = StableValue.ofIntFunction(SIZE, cif); + var cached = StableValue.intFunction(SIZE, cif); assertEquals("StableIntFunction[values=[.unset, .unset], original=" + cif + "]", cached.toString()); assertEquals(mapper.apply(1), cached.apply(1)); assertEquals(1, cif.cnt()); @@ -70,7 +70,7 @@ void exception() { StableTestUtil.CountingIntFunction cif = new StableTestUtil.CountingIntFunction<>(_ -> { throw new UnsupportedOperationException(); }); - var cached = StableValue.ofIntFunction(SIZE, cif); + var cached = StableValue.intFunction(SIZE, cif); assertThrows(UnsupportedOperationException.class, () -> cached.apply(1)); assertEquals(1, cif.cnt()); assertThrows(UnsupportedOperationException.class, () -> cached.apply(1)); @@ -81,7 +81,7 @@ void exception() { @Test void circular() { final AtomicReference> ref = new AtomicReference<>(); - IntFunction> cached = StableValue.ofIntFunction(SIZE, _ -> ref.get()); + IntFunction> cached = StableValue.intFunction(SIZE, _ -> ref.get()); ref.set(cached); cached.apply(0); String toString = cached.toString(); @@ -92,15 +92,15 @@ void circular() { @Test void equality() { - IntFunction f0 = StableValue.ofIntFunction(8, MAPPER); - IntFunction f1 = StableValue.ofIntFunction(8, MAPPER); + IntFunction f0 = StableValue.intFunction(8, MAPPER); + IntFunction f1 = StableValue.intFunction(8, MAPPER); // No function is equal to another function assertNotEquals(f0, f1); } @Test void hashCodeStable() { - IntFunction f0 = StableValue.ofIntFunction(8, MAPPER); + IntFunction f0 = StableValue.intFunction(8, MAPPER); assertEquals(System.identityHashCode(f0), f0.hashCode()); f0.apply(4); assertEquals(System.identityHashCode(f0), f0.hashCode()); diff --git a/test/jdk/java/lang/StableValue/StableListTest.java b/test/jdk/java/lang/StableValue/StableListTest.java index e84bbbfa082c3..76cda1fb2c030 100644 --- a/test/jdk/java/lang/StableValue/StableListTest.java +++ b/test/jdk/java/lang/StableValue/StableListTest.java @@ -60,8 +60,8 @@ final class StableListTest { @Test void factoryInvariants() { - assertThrows(NullPointerException.class, () -> StableValue.ofList(SIZE, null)); - assertThrows(IllegalArgumentException.class, () -> StableValue.ofList(-1, IDENTITY)); + assertThrows(NullPointerException.class, () -> StableValue.list(SIZE, null)); + assertThrows(IllegalArgumentException.class, () -> StableValue.list(-1, IDENTITY)); } @Test @@ -79,7 +79,7 @@ void size() { @Test void get() { StableTestUtil.CountingIntFunction cif = new StableTestUtil.CountingIntFunction<>(IDENTITY); - var lazy = StableValue.ofList(SIZE, cif); + var lazy = StableValue.list(SIZE, cif); for (int i = 0; i < SIZE; i++) { assertEquals(i, lazy.get(i)); assertEquals(i + 1, cif.cnt()); @@ -93,7 +93,7 @@ void getException() { StableTestUtil.CountingIntFunction cif = new StableTestUtil.CountingIntFunction<>(_ -> { throw new UnsupportedOperationException(); }); - var lazy = StableValue.ofList(SIZE, cif); + var lazy = StableValue.list(SIZE, cif); assertThrows(UnsupportedOperationException.class, () -> lazy.get(INDEX)); assertEquals(1, cif.cnt()); assertThrows(UnsupportedOperationException.class, () -> lazy.get(INDEX)); @@ -110,7 +110,7 @@ void toArray() { void toArrayWithArrayLarger() { Integer[] arr = new Integer[SIZE]; arr[INDEX] = 1; - assertSame(arr, StableValue.ofList(INDEX, IDENTITY).toArray(arr)); + assertSame(arr, StableValue.list(INDEX, IDENTITY).toArray(arr)); assertNull(arr[INDEX]); } @@ -151,7 +151,7 @@ void lastIndex() { @Test void toStringTest() { assertEquals("[]", newEmptyList().toString()); - assertEquals("[0, 1]", StableValue.ofList(2, IDENTITY).toString()); + assertEquals("[0, 1]", StableValue.list(2, IDENTITY).toString()); assertEquals(newRegularList().toString(), newList().toString()); } @@ -205,7 +205,7 @@ void iteratorPartial() { @Test void recursiveCall() { AtomicReference> ref = new AtomicReference<>(); - var lazy = StableValue.ofList(SIZE, i -> ref.get().apply(i)); + var lazy = StableValue.list(SIZE, i -> ref.get().apply(i)); ref.set(lazy::get); assertThrows(StackOverflowError.class, () -> lazy.get(INDEX)); } @@ -268,7 +268,7 @@ void randomAccess() { @Test void distinct() { - StableValueImpl[] array = StableValueFactories.ofArray(SIZE); + StableValueImpl[] array = StableValueFactories.array(SIZE); assertEquals(SIZE, array.length); // Check, every StableValue is distinct Map, Boolean> idMap = new IdentityHashMap<>(); @@ -332,11 +332,11 @@ static Stream unsupportedOperations() { } static List newList() { - return StableValue.ofList(SIZE, IDENTITY); + return StableValue.list(SIZE, IDENTITY); } static List newEmptyList() { - return StableValue.ofList(ZERO, IDENTITY); + return StableValue.list(ZERO, IDENTITY); } static List newRegularList() { diff --git a/test/jdk/java/lang/StableValue/StableMapTest.java b/test/jdk/java/lang/StableValue/StableMapTest.java index 8d7058a4064f0..c895987c0c668 100644 --- a/test/jdk/java/lang/StableValue/StableMapTest.java +++ b/test/jdk/java/lang/StableValue/StableMapTest.java @@ -57,8 +57,8 @@ final class StableMapTest { @Test void factoryInvariants() { - assertThrows(NullPointerException.class, () -> StableValue.ofMap(KEYS, null)); - assertThrows(NullPointerException.class, () -> StableValue.ofMap(null, IDENTITY)); + assertThrows(NullPointerException.class, () -> StableValue.map(KEYS, null)); + assertThrows(NullPointerException.class, () -> StableValue.map(null, IDENTITY)); } @Test @@ -76,7 +76,7 @@ void size() { @Test void get() { StableTestUtil.CountingFunction cf = new StableTestUtil.CountingFunction<>(IDENTITY); - var lazy = StableValue.ofMap(KEYS, cf); + var lazy = StableValue.map(KEYS, cf); int cnt = 1; for (int i : KEYS) { assertEquals(i, lazy.get(i)); @@ -92,7 +92,7 @@ void getException() { StableTestUtil.CountingFunction cf = new StableTestUtil.CountingFunction<>(_ -> { throw new UnsupportedOperationException(); }); - var lazy = StableValue.ofMap(KEYS, cf); + var lazy = StableValue.map(KEYS, cf); assertThrows(UnsupportedOperationException.class, () -> lazy.get(KEY)); assertEquals(1, cf.cnt()); assertThrows(UnsupportedOperationException.class, () -> lazy.get(KEY)); @@ -131,7 +131,7 @@ void forEach() { @Test void toStringTest() { assertEquals("{}", newEmptyMap().toString()); - assertEquals("{" + KEY + "=" + KEY + "}", StableValue.ofMap(Set.of(KEY), IDENTITY).toString()); + assertEquals("{" + KEY + "=" + KEY + "}", StableValue.map(Set.of(KEY), IDENTITY).toString()); String actual = newMap().toString(); assertTrue(actual.startsWith("{")); for (int key:KEYS) { @@ -210,7 +210,7 @@ void serializable(Map map) { @Test void distinct() { - Map> map = StableValueFactories.ofMap(Set.of(1, 2, 3)); + Map> map = StableValueFactories.map(Set.of(1, 2, 3)); assertEquals(3, map.size()); // Check, every StableValue is distinct Map, Boolean> idMap = new IdentityHashMap<>(); @@ -252,11 +252,11 @@ static Stream unsupportedOperations() { } static Map newMap() { - return StableValue.ofMap(KEYS, IDENTITY); + return StableValue.map(KEYS, IDENTITY); } static Map newEmptyMap() { - return StableValue.ofMap(EMPTY, IDENTITY); + return StableValue.map(EMPTY, IDENTITY); } static Map newRegularMap() { diff --git a/test/jdk/java/lang/StableValue/StableSupplierTest.java b/test/jdk/java/lang/StableValue/StableSupplierTest.java index e4fbd7729c16f..d6ed324f8bcd4 100644 --- a/test/jdk/java/lang/StableValue/StableSupplierTest.java +++ b/test/jdk/java/lang/StableValue/StableSupplierTest.java @@ -40,7 +40,7 @@ final class StableSupplierTest { @Test void factoryInvariants() { - assertThrows(NullPointerException.class, () -> StableValue.ofSupplier(null)); + assertThrows(NullPointerException.class, () -> StableValue.supplier(null)); } @Test @@ -51,7 +51,7 @@ void basic() { void basic(Supplier supplier) { StableTestUtil.CountingSupplier cs = new StableTestUtil.CountingSupplier<>(supplier); - var cached = StableValue.ofSupplier(cs); + var cached = StableValue.supplier(cs); assertEquals("StableSupplier[value=.unset, original=" + cs + "]", cached.toString()); assertEquals(supplier.get(), cached.get()); assertEquals(1, cs.cnt()); @@ -65,7 +65,7 @@ void exception() { StableTestUtil.CountingSupplier cs = new StableTestUtil.CountingSupplier<>(() -> { throw new UnsupportedOperationException(); }); - var cached = StableValue.ofSupplier(cs); + var cached = StableValue.supplier(cs); assertThrows(UnsupportedOperationException.class, cached::get); assertEquals(1, cs.cnt()); assertThrows(UnsupportedOperationException.class, cached::get); @@ -76,7 +76,7 @@ void exception() { @Test void circular() { final AtomicReference> ref = new AtomicReference<>(); - Supplier> cached = StableValue.ofSupplier(ref::get); + Supplier> cached = StableValue.supplier(ref::get); ref.set(cached); cached.get(); String toString = cached.toString(); @@ -86,15 +86,15 @@ void circular() { @Test void equality() { - Supplier f0 = StableValue.ofSupplier(SUPPLIER); - Supplier f1 = StableValue.ofSupplier(SUPPLIER); + Supplier f0 = StableValue.supplier(SUPPLIER); + Supplier f1 = StableValue.supplier(SUPPLIER); // No function is equal to another function assertNotEquals(f0, f1); } @Test void hashCodeStable() { - Supplier f0 = StableValue.ofSupplier(SUPPLIER); + Supplier f0 = StableValue.supplier(SUPPLIER); assertEquals(System.identityHashCode(f0), f0.hashCode()); f0.get(); assertEquals(System.identityHashCode(f0), f0.hashCode()); diff --git a/test/jdk/java/lang/StableValue/StableValueTest.java b/test/jdk/java/lang/StableValue/StableValueTest.java index ceaa4d7a22819..db8cfbe42a13e 100644 --- a/test/jdk/java/lang/StableValue/StableValueTest.java +++ b/test/jdk/java/lang/StableValue/StableValueTest.java @@ -58,13 +58,13 @@ void preSet() { assertTrue(stable.isSet()); assertEquals(VALUE, stable.orElseThrow()); assertEquals(VALUE, stable.orElse(VALUE2)); - assertEquals(VALUE, stable.computeIfUnset(() -> VALUE2)); + assertEquals(VALUE, stable.orElseSet(() -> VALUE2)); assertFalse(stable.trySet(VALUE2)); assertThrows(IllegalStateException.class, () -> stable.setOrThrow(VALUE2)); } void trySet(Integer initial) { - StableValue stable = StableValue.unset(); + StableValue stable = StableValue.of(); assertTrue(stable.trySet(initial)); assertFalse(stable.trySet(null)); assertFalse(stable.trySet(VALUE)); @@ -74,7 +74,7 @@ void trySet(Integer initial) { @Test void orElse() { - StableValue stable = StableValue.unset(); + StableValue stable = StableValue.of(); assertEquals(VALUE, stable.orElse(VALUE)); stable.trySet(VALUE); assertEquals(VALUE, stable.orElse(VALUE2)); @@ -82,7 +82,7 @@ void orElse() { @Test void orElseThrow() { - StableValue stable = StableValue.unset(); + StableValue stable = StableValue.of(); var e = assertThrows(NoSuchElementException.class, stable::orElseThrow); assertEquals("No underlying data set", e.getMessage()); stable.trySet(VALUE); @@ -96,7 +96,7 @@ void isSet() { } void isSet(Integer initial) { - StableValue stable = StableValue.unset(); + StableValue stable = StableValue.of(); assertFalse(stable.isSet()); stable.trySet(initial); assertTrue(stable.isSet()); @@ -105,31 +105,31 @@ void isSet(Integer initial) { @Test void testComputeIfUnsetSupplier() { StableTestUtil.CountingSupplier cs = new StableTestUtil.CountingSupplier<>(() -> VALUE); - StableValue stable = StableValue.unset(); - assertEquals(VALUE, stable.computeIfUnset(cs)); + StableValue stable = StableValue.of(); + assertEquals(VALUE, stable.orElseSet(cs)); assertEquals(1, cs.cnt()); - assertEquals(VALUE, stable.computeIfUnset(cs)); + assertEquals(VALUE, stable.orElseSet(cs)); assertEquals(1, cs.cnt()); } @Test void testHashCode() { - StableValue stableValue = StableValue.unset(); + StableValue stableValue = StableValue.of(); // Should be Object::hashCode assertEquals(System.identityHashCode(stableValue), stableValue.hashCode()); } @Test void testEquals() { - StableValue s0 = StableValue.unset(); - StableValue s1 = StableValue.unset(); + StableValue s0 = StableValue.of(); + StableValue s1 = StableValue.of(); assertNotEquals(s0, s1); // Identity based s0.setOrThrow(42); s1.setOrThrow(42); assertNotEquals(s0, s1); assertNotEquals(s0, "a"); - StableValue null0 = StableValue.unset(); - StableValue null1 = StableValue.unset(); + StableValue null0 = StableValue.of(); + StableValue null1 = StableValue.of(); null0.setOrThrow(null); null1.setOrThrow(null); assertNotEquals(null0, null1); @@ -137,27 +137,27 @@ void testEquals() { @Test void toStringUnset() { - StableValue stable = StableValue.unset(); + StableValue stable = StableValue.of(); assertEquals("StableValue.unset", stable.toString()); } @Test void toStringNull() { - StableValue stable = StableValue.unset(); + StableValue stable = StableValue.of(); assertTrue(stable.trySet(null)); assertEquals("StableValue[null]", stable.toString()); } @Test void toStringNonNull() { - StableValue stable = StableValue.unset(); + StableValue stable = StableValue.of(); assertTrue(stable.trySet(VALUE)); assertEquals("StableValue[" + VALUE + "]", stable.toString()); } @Test void toStringCircular() { - StableValue> stable = StableValue.unset(); + StableValue> stable = StableValue.of(); stable.trySet(stable); String toString = stable.toString(); assertEquals("(this StableValue)", toString); @@ -197,7 +197,7 @@ void raceMixed() { void race(BiPredicate, Integer> winnerPredicate) { int noThreads = 10; CountDownLatch starter = new CountDownLatch(1); - StableValue stable = StableValue.unset(); + StableValue stable = StableValue.of(); BitSet winner = new BitSet(noThreads); List threads = IntStream.range(0, noThreads).mapToObj(i -> new Thread(() -> { try { diff --git a/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java b/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java index 7d7e5893a3e47..3531793ab9bfa 100644 --- a/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java +++ b/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java @@ -52,7 +52,7 @@ static StableValue[] stables() { @SuppressWarnings("unchecked") StableValue[] stables = (StableValue[]) new StableValue[SIZE]; for (int i = 0; i < SIZE; i++) { - stables[i] = StableValue.unset(); + stables[i] = StableValue.of(); } return stables; } diff --git a/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java b/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java index fb5a7381f6c54..ad8b1baa97618 100644 --- a/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java +++ b/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java @@ -44,10 +44,10 @@ final class TrustedFieldTypeTest { @Test void reflection() throws NoSuchFieldException, IllegalAccessException { final class Holder { - private final StableValue value = StableValue.unset(); + private final StableValue value = StableValue.of(); } final class HolderNonFinal { - private StableValue value = StableValue.unset(); + private StableValue value = StableValue.of(); } final class ArrayHolder { private final StableValue[] array = (StableValue[]) new StableValue[]{}; @@ -60,7 +60,7 @@ final class ArrayHolder { Object read = valueField.get(holder); // We should NOT be able to write to the StableValue field assertThrows(IllegalAccessException.class, () -> - valueField.set(holder, StableValue.unset()) + valueField.set(holder, StableValue.of()) ); Field valueNonFinal = HolderNonFinal.class.getDeclaredField("value"); @@ -68,7 +68,7 @@ final class ArrayHolder { HolderNonFinal holderNonFinal = new HolderNonFinal(); // As the field is not final, both read and write should be ok (not trusted) Object readNonFinal = valueNonFinal.get(holderNonFinal); - valueNonFinal.set(holderNonFinal, StableValue.unset()); + valueNonFinal.set(holderNonFinal, StableValue.of()); Field arrayField = ArrayHolder.class.getDeclaredField("array"); arrayField.setAccessible(true); @@ -89,7 +89,7 @@ void sunMiscUnsafe() throws NoSuchFieldException, IllegalAccessException { sun.misc.Unsafe unsafe = (sun.misc.Unsafe)unsafeField.get(null); final class Holder { - private final StableValue value = StableValue.unset(); + private final StableValue value = StableValue.of(); } final class ArrayHolder { private final StableValue[] array = (StableValue[]) new StableValue[]{}; @@ -107,7 +107,7 @@ final class ArrayHolder { ); // Test direct access - StableValue stableValue = StableValue.unset(); + StableValue stableValue = StableValue.of(); Class clazz = stableValue.getClass(); System.out.println("clazz = " + clazz); assertThrows(NoSuchFieldException.class, () -> clazz.getField("value")); @@ -117,7 +117,7 @@ final class ArrayHolder { void varHandle() throws NoSuchFieldException, IllegalAccessException { MethodHandles.Lookup lookup = MethodHandles.lookup(); - StableValue originalValue = StableValue.unset(); + StableValue originalValue = StableValue.of(); @SuppressWarnings("unchecked") StableValue[] originalArrayValue = new StableValue[10]; @@ -133,11 +133,11 @@ final class ArrayHolder { Holder holder = new Holder(); assertThrows(UnsupportedOperationException.class, () -> - valueVarHandle.set(holder, StableValue.unset()) + valueVarHandle.set(holder, StableValue.of()) ); assertThrows(UnsupportedOperationException.class, () -> - valueVarHandle.compareAndSet(holder, originalValue, StableValue.unset()) + valueVarHandle.compareAndSet(holder, originalValue, StableValue.of()) ); VarHandle arrayVarHandle = lookup.findVarHandle(ArrayHolder.class, "array", StableValue[].class); @@ -155,7 +155,7 @@ final class ArrayHolder { @Test void updateStableValueUnderlyingData() { - StableValue stableValue = StableValue.unset(); + StableValue stableValue = StableValue.of(); stableValue.trySet(42); jdk.internal.misc.Unsafe unsafe = Unsafe.getUnsafe(); diff --git a/test/micro/org/openjdk/bench/java/lang/stable/CustomStableBiFunctionBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/CustomStableBiFunctionBenchmark.java index 0f39195bc2c6c..64b33ccf9e1d0 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/CustomStableBiFunctionBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/CustomStableBiFunctionBenchmark.java @@ -90,8 +90,8 @@ public class CustomStableBiFunctionBenchmark { private static final BiFunction function = cachingBiFunction(SET, ORIGINAL);; private static final BiFunction function2 = cachingBiFunction(SET, ORIGINAL);; - private static final StableValue STABLE_VALUE = StableValue.ofUnset(); - private static final StableValue STABLE_VALUE2 = StableValue.ofUnset(); + private static final StableValue STABLE_VALUE = StableValue.of(); + private static final StableValue STABLE_VALUE2 = StableValue.of(); static { STABLE_VALUE.trySet(ORIGINAL.apply(VALUE, VALUE2)); @@ -153,7 +153,7 @@ record CachingBiFunction( public CachingBiFunction(Set> inputs, BiFunction original) { this(Map.copyOf(inputs.stream() - .collect(Collectors.toMap(Function.identity(), _ -> StableValue.ofUnset()))), + .collect(Collectors.toMap(Function.identity(), _ -> StableValue.of()))), original ); } @@ -196,8 +196,8 @@ static Map>> delegate(Set>> map = inputs.stream() .collect(Collectors.groupingBy(Pair::left, Collectors.groupingBy(Pair::right, - Collectors.mapping((Function, ? extends StableValue>) _ -> StableValue.ofUnset(), - Collectors.reducing(StableValue.ofUnset(), _ -> StableValue.ofUnset(), (StableValue a, StableValue b) -> a))))); + Collectors.mapping((Function, ? extends StableValue>) _ -> StableValue.of(), + Collectors.reducing(StableValue.of(), _ -> StableValue.of(), (StableValue a, StableValue b) -> a))))); @SuppressWarnings("unchecked") Map>> copy = Map.ofEntries(map.entrySet().stream() @@ -248,8 +248,8 @@ static Map> delegate(Set>> map = inputs.stream() .collect(Collectors.groupingBy(Pair::left, Collectors.groupingBy(Pair::right, - Collectors.mapping((Function, ? extends StableValue>) _ -> StableValue.ofUnset(), - Collectors.reducing(StableValue.ofUnset(), _ -> StableValue.ofUnset(), (StableValue a, StableValue b) -> b))))); + Collectors.mapping((Function, ? extends StableValue>) _ -> StableValue.of(), + Collectors.reducing(StableValue.of(), _ -> StableValue.of(), (StableValue a, StableValue b) -> b))))); @SuppressWarnings("unchecked") Map> copy = Map.ofEntries(map.entrySet().stream() diff --git a/test/micro/org/openjdk/bench/java/lang/stable/CustomStablePredicateBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/CustomStablePredicateBenchmark.java index a10287e78eb7c..b4daab8c3d589 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/CustomStablePredicateBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/CustomStablePredicateBenchmark.java @@ -95,7 +95,7 @@ record CachingPredicate(Map> delegate, public CachingPredicate(Set inputs, Predicate original) { this(inputs.stream() - .collect(Collectors.toUnmodifiableMap(Function.identity(), _ -> StableValue.ofUnset())), + .collect(Collectors.toUnmodifiableMap(Function.identity(), _ -> StableValue.of())), original ); } diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableSupplierBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableSupplierBenchmark.java index ead18e24f0d9f..d587b7877c13e 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/StableSupplierBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableSupplierBenchmark.java @@ -56,13 +56,13 @@ public class StableSupplierBenchmark { private static final int VALUE = 42; private static final int VALUE2 = 23; - private static final StableValue STABLE = init(StableValue.ofUnset(), VALUE); - private static final StableValue STABLE2 = init(StableValue.ofUnset(), VALUE2); + private static final StableValue STABLE = init(StableValue.of(), VALUE); + private static final StableValue STABLE2 = init(StableValue.of(), VALUE2); private static final Supplier SUPPLIER = StableValue.supplier(() -> VALUE); private static final Supplier SUPPLIER2 = StableValue.supplier(() -> VALUE); - private final StableValue stable = init(StableValue.ofUnset(), VALUE); - private final StableValue stable2 = init(StableValue.ofUnset(), VALUE2); + private final StableValue stable = init(StableValue.of(), VALUE); + private final StableValue stable2 = init(StableValue.of(), VALUE2); private final Supplier supplier = StableValue.supplier(() -> VALUE); private final Supplier supplier2 = StableValue.supplier(() -> VALUE2); diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java index 6ac027d8c3db5..d8eec559e4e2c 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java @@ -47,19 +47,19 @@ public class StableValueBenchmark { private static final int VALUE = 42; private static final int VALUE2 = 23; - private static final StableValue STABLE = init(StableValue.ofUnset(), VALUE); - private static final StableValue STABLE2 = init(StableValue.ofUnset(), VALUE2); - private static final StableValue DCL = init(StableValue.ofUnset(), VALUE); - private static final StableValue DCL2 = init(StableValue.ofUnset(), VALUE2); + private static final StableValue STABLE = init(StableValue.of(), VALUE); + private static final StableValue STABLE2 = init(StableValue.of(), VALUE2); + private static final StableValue DCL = init(StableValue.of(), VALUE); + private static final StableValue DCL2 = init(StableValue.of(), VALUE2); private static final AtomicReference ATOMIC = new AtomicReference<>(VALUE); private static final AtomicReference ATOMIC2 = new AtomicReference<>(VALUE2); private static final Holder HOLDER = new Holder(VALUE); private static final Holder HOLDER2 = new Holder(VALUE2); - private final StableValue stable = init(StableValue.ofUnset(), VALUE); - private final StableValue stable2 = init(StableValue.ofUnset(), VALUE2); - private final StableValue stableNull = StableValue.ofUnset(); - private final StableValue stableNull2 = StableValue.ofUnset(); + private final StableValue stable = init(StableValue.of(), VALUE); + private final StableValue stable2 = init(StableValue.of(), VALUE2); + private final StableValue stableNull = StableValue.of(); + private final StableValue stableNull2 = StableValue.of(); private final Supplier dcl = new Dcl<>(() -> VALUE); private final Supplier dcl2 = new Dcl<>(() -> VALUE2); private final AtomicReference atomic = new AtomicReference<>(VALUE); @@ -78,7 +78,7 @@ public void setup() { final int v = i; Dcl dclX = new Dcl<>(() -> v); sum += dclX.get(); - StableValue stableX = StableValue.ofUnset(); + StableValue stableX = StableValue.of(); stableX.trySet(i); sum += stableX.orElseThrow(); } @@ -141,7 +141,7 @@ private static StableValue init(StableValue m, Integer value) // because StableValue fields have a special meaning. private static final class Holder { - private final StableValue delegate = StableValue.ofUnset(); + private final StableValue delegate = StableValue.of(); Holder(int value) { delegate.setOrThrow(value); From d255b58a9f7b8de178c5bf904a2fa6693070c4e0 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Sun, 9 Feb 2025 20:46:50 +0000 Subject: [PATCH 180/327] Fix typos --- .../share/classes/java/lang/StableValue.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index cac1bcfe4a957..97de32da23af5 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -172,7 +172,7 @@ *} *

    * A stable function is a function that takes a parameter (of type {@code T}) and - * uses it to compute a result (of type {@code R}) that is then cached into a backing + * uses it to compute a result (of type {@code R}) that is then cached by the backing * stable value storage for that parameter value. A stable function is created via the * {@linkplain StableValue#function(Set, Function) StableValue.function()} factory. * Upon creation, the input {@linkplain Set} is specified together with an original @@ -207,7 +207,7 @@ * StableValue.list(10, StrictMath::sqrt); * * double sqrt9() { - * return SQRT.apply(9); // May eventually constant fold to 3.0 at runtime + * return SQRT.get(9); // May eventually constant fold to 3.0 at runtime * } * * } @@ -225,7 +225,7 @@ * StableValue.map(Set.of(1, 2, 4, 8, 16, 32), StrictMath::sqrt); * * double sqrt16() { - * return SQRT.apply(16); // May eventually constant fold to 4.0 at runtime + * return SQRT.get(16); // May eventually constant fold to 4.0 at runtime * } * * } @@ -313,8 +313,9 @@ * thread safe and guarantee at-most-once-per-input invocation. * *

    Miscellaneous

    - * Except for a StableValue's content itself, all method parameters must be - * non-null or a {@link NullPointerException} will be thrown. + * Except for a StableValue's content itself, an {@linkplain #orElse(Object) orElse(other)} + * parameter, and an {@linkplain #equals(Object) equals(obj)} parameter; all method + * parameters must be non-null or a {@link NullPointerException} will be thrown. *

    * Stable functions and collections are not {@link Serializable} as this would require * {@linkplain #list(int, IntFunction) mappers} to be {@link Serializable} as well, @@ -399,7 +400,7 @@ public sealed interface StableValue * if (stable.isSet()) { * return stable.get(); * } else { - * V newValue = supplier.get(); + * T newValue = supplier.get(); * stable.setOrThrow(newValue); * return newValue; * } @@ -416,7 +417,7 @@ public sealed interface StableValue // Convenience methods /** - * Sets the cintent to the provided {@code value}, or, if already set, + * Sets the content to the provided {@code value}, or, if already set, * throws {@code IllegalStateException}. *

    * When this method returns (or throws an exception), the content is always set. From 158c5c448676e053f41c9b4326b767d8ca7fedcc Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 10 Feb 2025 09:50:23 +0100 Subject: [PATCH 181/327] Add some tests --- .../java/lang/StableValue/StableListTest.java | 8 +++++++ .../java/lang/StableValue/StableMapTest.java | 23 ++++++++++++++++--- .../lang/StableValue/StableValueTest.java | 7 ++++-- .../StableValue/TrustedFieldTypeTest.java | 1 + 4 files changed, 34 insertions(+), 5 deletions(-) diff --git a/test/jdk/java/lang/StableValue/StableListTest.java b/test/jdk/java/lang/StableValue/StableListTest.java index 76cda1fb2c030..67e7d4c075b92 100644 --- a/test/jdk/java/lang/StableValue/StableListTest.java +++ b/test/jdk/java/lang/StableValue/StableListTest.java @@ -201,6 +201,14 @@ void iteratorPartial() { assertThrows(NoSuchElementException.class, iterator::next); } + @Test + void subList() { + var lazy = newList(); + var lazySubList = lazy.subList(1, SIZE); + var regularList = newRegularList(); + var regularSubList = regularList.subList(1, SIZE); + assertEquals(regularSubList, lazySubList); + } @Test void recursiveCall() { diff --git a/test/jdk/java/lang/StableValue/StableMapTest.java b/test/jdk/java/lang/StableValue/StableMapTest.java index c895987c0c668..c60dbaf987e4c 100644 --- a/test/jdk/java/lang/StableValue/StableMapTest.java +++ b/test/jdk/java/lang/StableValue/StableMapTest.java @@ -165,12 +165,29 @@ void entrySet() { } @Test - void iterator() { - System.out.println("ITERATOR"); + void iteratorNext() { + Set encountered = new HashSet<>(); var iterator = newMap().entrySet().iterator(); while (iterator.hasNext()) { - System.out.println("iterator.next() = " + iterator.next()); + var entry = iterator.next(); + assertEquals(entry.getKey(), entry.getValue()); + encountered.add(entry.getValue()); } + assertEquals(KEYS, encountered); + } + + @Test + void iteratorForEachRemaining() { + Set encountered = new HashSet<>(); + var iterator = newMap().entrySet().iterator(); + var entry = iterator.next(); + assertEquals(entry.getKey(), entry.getValue()); + encountered.add(entry.getValue()); + iterator.forEachRemaining(e -> { + assertEquals(e.getKey(), e.getValue()); + encountered.add(e.getValue()); + }); + assertEquals(KEYS, encountered); } // Immutability diff --git a/test/jdk/java/lang/StableValue/StableValueTest.java b/test/jdk/java/lang/StableValue/StableValueTest.java index db8cfbe42a13e..081a9b7d7497d 100644 --- a/test/jdk/java/lang/StableValue/StableValueTest.java +++ b/test/jdk/java/lang/StableValue/StableValueTest.java @@ -76,6 +76,7 @@ void trySet(Integer initial) { void orElse() { StableValue stable = StableValue.of(); assertEquals(VALUE, stable.orElse(VALUE)); + assertNull(stable.orElse(null)); stable.trySet(VALUE); assertEquals(VALUE, stable.orElse(VALUE2)); } @@ -103,9 +104,10 @@ void isSet(Integer initial) { } @Test - void testComputeIfUnsetSupplier() { + void testOrElseSetSupplier() { StableTestUtil.CountingSupplier cs = new StableTestUtil.CountingSupplier<>(() -> VALUE); StableValue stable = StableValue.of(); + assertThrows(NullPointerException.class, () -> stable.orElseSet(null)); assertEquals(VALUE, stable.orElseSet(cs)); assertEquals(1, cs.cnt()); assertEquals(VALUE, stable.orElseSet(cs)); @@ -122,12 +124,13 @@ void testHashCode() { @Test void testEquals() { StableValue s0 = StableValue.of(); + assertNotEquals(null, s0); StableValue s1 = StableValue.of(); assertNotEquals(s0, s1); // Identity based s0.setOrThrow(42); s1.setOrThrow(42); assertNotEquals(s0, s1); - assertNotEquals(s0, "a"); + assertNotEquals("a", s0); StableValue null0 = StableValue.of(); StableValue null1 = StableValue.of(); null0.setOrThrow(null); diff --git a/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java b/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java index ad8b1baa97618..e9c52749eb73b 100644 --- a/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java +++ b/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java @@ -92,6 +92,7 @@ final class Holder { private final StableValue value = StableValue.of(); } final class ArrayHolder { + @SuppressWarnings("unchecked") private final StableValue[] array = (StableValue[]) new StableValue[]{}; } From 493973d9b77e79f940736c3a270d13fc4c0170b3 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 10 Feb 2025 20:50:49 +0100 Subject: [PATCH 182/327] Rename methods and improve test coverage --- .../stable/StableHeterogeneousContainer.java | 36 ++++++++-------- .../lang/StableValue/StableFunctionTest.java | 4 ++ .../StableHeterogeneousContainerTest.java | 39 +++++++++-------- .../StableValue/StableValueFactoriesTest.java | 43 +++++++++++++++++++ 4 files changed, 86 insertions(+), 36 deletions(-) create mode 100644 test/jdk/java/lang/StableValue/StableValueFactoriesTest.java diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableHeterogeneousContainer.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableHeterogeneousContainer.java index 5fbb78b6447a6..46bab09d5c3ae 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableHeterogeneousContainer.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableHeterogeneousContainer.java @@ -1,5 +1,6 @@ package jdk.internal.lang.stable; +import jdk.internal.ValueBased; import jdk.internal.vm.annotation.DontInline; import jdk.internal.vm.annotation.ForceInline; import jdk.internal.vm.annotation.Stable; @@ -15,10 +16,10 @@ * A stable heterogeneous container that can associate types to implementing instances. *

    * Attempting to associate a type with an instance that is {@code null} or attempting to - * provide a {@code type} that is {@code null} will result in a {@linkplain NullPointerException} - * for all methods in this class. + * provide a {@code type} that is {@code null} will result in a + * {@linkplain NullPointerException} for all methods in this class. * - * @implNote Implementations of this interface are thread-safe + * @implNote Implementations of this interface are thread-safe and can be a value class */ public sealed interface StableHeterogeneousContainer { @@ -36,7 +37,7 @@ public sealed interface StableHeterogeneousContainer { * @throws IllegalArgumentException if the provided {@code type} is not a type * specified at creation */ - boolean tryPut(Class type, T instance); + boolean trySet(Class type, T instance); /** * {@return the instance associated with the provided {@code type}, {@code null} @@ -49,7 +50,7 @@ public sealed interface StableHeterogeneousContainer { * @throws IllegalArgumentException if the provided {@code type} is not a type * specified at creation */ - T get(Class type); + T orElseNull(Class type); /** * {@return the instance associated with the provided {@code type}, or throws @@ -64,7 +65,7 @@ public sealed interface StableHeterogeneousContainer { * @throws NoSuchElementException if the provided {@code type} is not associated * with an instance */ - T getOrThrow(Class type); + T orElseThrow(Class type); /** * {@return the instance associated with the provided {@code type}, if no association @@ -84,8 +85,9 @@ public sealed interface StableHeterogeneousContainer { * @param type of the associated instance * @param mapper to be used for computing the associated instance */ - T computeIfAbsent(Class type, Function, T> mapper); + T orElseSet(Class type, Function, ? extends T> mapper); + @ValueBased final class Impl implements StableHeterogeneousContainer { @Stable @@ -97,7 +99,7 @@ public Impl(Set> types) { @ForceInline @Override - public boolean tryPut(Class type, T instance) { + public boolean trySet(Class type, T instance) { Objects.requireNonNull(type); Objects.requireNonNull(instance, "The provided instance for '" + type + "' was null"); return of(type) @@ -109,22 +111,22 @@ public boolean tryPut(Class type, T instance) { @SuppressWarnings("unchecked") @ForceInline @Override - public T computeIfAbsent(Class type, Function, T> mapper) { + public T orElseSet(Class type, Function, ? extends T> mapper) { Objects.requireNonNull(type); Objects.requireNonNull(mapper); final StableValue stableValue = of(type); if (stableValue.isSet()) { return (T) stableValue.orElseThrow(); } - return computeIfAbsentSlowPath(type, mapper, stableValue); + return orElseSetSlowPath(type, mapper, stableValue); } @SuppressWarnings("unchecked") @DontInline - public T computeIfAbsentSlowPath(Class type, - Function, T> constructor, - StableValue stableValue) { - return (T) stableValue.orElseSet(new Supplier() { + public T orElseSetSlowPath(Class type, + Function, ? extends T> constructor, + StableValue stableValue) { + return (T) stableValue.orElseSet(new Supplier<>() { @Override public Object get() { return type.cast( @@ -138,7 +140,7 @@ public Object get() { @ForceInline @Override - public T get(Class type) { + public T orElseNull(Class type) { Objects.requireNonNull(type); return type.cast( of(type) @@ -148,8 +150,8 @@ public T get(Class type) { @ForceInline @Override - public T getOrThrow(Class type) { - final T t = get(type); + public T orElseThrow(Class type) { + final T t = orElseNull(type); if (t == null) { throw new NoSuchElementException("The type `" + type + "` is know but there is no instance associated with it"); } diff --git a/test/jdk/java/lang/StableValue/StableFunctionTest.java b/test/jdk/java/lang/StableValue/StableFunctionTest.java index b9c8157c1a589..d7f68af0952cc 100644 --- a/test/jdk/java/lang/StableValue/StableFunctionTest.java +++ b/test/jdk/java/lang/StableValue/StableFunctionTest.java @@ -105,7 +105,11 @@ void basic(Set inputs, Function mapper) { @MethodSource("emptySets") void empty(Set inputs) { Function f0 = StableValue.function(inputs, Value::asInt); + Function f1 = StableValue.function(inputs, Value::asInt); assertTrue(f0.toString().contains("{}")); + assertThrows(IllegalArgumentException.class, () -> f0.apply(null)); + assertNotEquals(f0, f1); + assertNotEquals(null, f0); } @ParameterizedTest diff --git a/test/jdk/java/lang/StableValue/StableHeterogeneousContainerTest.java b/test/jdk/java/lang/StableValue/StableHeterogeneousContainerTest.java index fcedd7e70f04d..10aae852da923 100644 --- a/test/jdk/java/lang/StableValue/StableHeterogeneousContainerTest.java +++ b/test/jdk/java/lang/StableValue/StableHeterogeneousContainerTest.java @@ -78,44 +78,45 @@ void factoryInvariants() { @ParameterizedTest @MethodSource("nonEmptySets") - void tryPut(Set inputs) { + void trySet(Set inputs) { var container = StableValueFactories.ofHeterogeneousContainer(classes(inputs)); - assertTrue(container.tryPut(Integer.class, Value.INTEGER.valueAs(Integer.class))); - assertFalse(container.tryPut(Integer.class, Value.INTEGER.valueAs(Integer.class))); - assertEquals(Value.INTEGER.value(), container.get(Integer.class)); - var iae = assertThrows(IllegalArgumentException.class, () -> container.tryPut(Long.class, 8L)); + assertTrue(container.trySet(Integer.class, Value.INTEGER.valueAs(Integer.class))); + assertFalse(container.trySet(Integer.class, Value.INTEGER.valueAs(Integer.class))); + assertEquals(Value.INTEGER.value(), container.orElseNull(Integer.class)); + var iae = assertThrows(IllegalArgumentException.class, () -> container.trySet(Long.class, 8L)); assertEquals("No such type: " + Long.class, iae.getMessage()); - var npe = assertThrows(NullPointerException.class, () -> container.tryPut(Short.class, null)); + var npe = assertThrows(NullPointerException.class, () -> container.trySet(Short.class, null)); assertEquals("The provided instance for '" + Short.class + "' was null", npe.getMessage()); } @ParameterizedTest @MethodSource("nonEmptySets") - void computeIfAbsent(Set inputs) { + void orElseSet(Set inputs) { var container = StableValueFactories.ofHeterogeneousContainer(classes(inputs)); - assertEquals(Value.INTEGER.value(), container.computeIfAbsent(Integer.class, Value.INTEGER::valueAs)); - var iae = assertThrows(IllegalArgumentException.class, () -> container.computeIfAbsent(Long.class, _ -> 8L)); + assertEquals(Value.INTEGER.value(), container.orElseSet(Integer.class, Value.INTEGER::valueAs)); + assertEquals(Value.INTEGER.value(), container.orElseSet(Integer.class, Value.INTEGER::valueAs)); + var iae = assertThrows(IllegalArgumentException.class, () -> container.orElseSet(Long.class, _ -> 8L)); assertEquals("No such type: " + Long.class, iae.getMessage()); - var npe = assertThrows(NullPointerException.class, () -> container.computeIfAbsent(Short.class, _ -> null)); + var npe = assertThrows(NullPointerException.class, () -> container.orElseSet(Short.class, _ -> null)); assertEquals("The constructor for `" + Short.class + "` returned null", npe.getMessage()); } @ParameterizedTest @MethodSource("nonEmptySets") - void get(Set inputs) { + void orElseNull(Set inputs) { var container = StableValueFactories.ofHeterogeneousContainer(classes(inputs)); - assertTrue(container.tryPut(Integer.class, Value.INTEGER.valueAs(Integer.class))); - assertEquals(Value.INTEGER.value(), container.get(Integer.class)); - assertNull(container.get(Value.SHORT.clazz())); + assertTrue(container.trySet(Integer.class, Value.INTEGER.valueAs(Integer.class))); + assertEquals(Value.INTEGER.value(), container.orElseNull(Integer.class)); + assertNull(container.orElseNull(Value.SHORT.clazz())); } @ParameterizedTest @MethodSource("nonEmptySets") - void getOrThrow(Set inputs) { + void orElseThrow(Set inputs) { var container = StableValueFactories.ofHeterogeneousContainer(classes(inputs)); - assertTrue(container.tryPut(Integer.class, Value.INTEGER.valueAs(Integer.class))); - assertEquals(Value.INTEGER.value(), container.getOrThrow(Integer.class)); - var e = assertThrows(NoSuchElementException.class , () -> container.getOrThrow(Value.SHORT.clazz())); + assertTrue(container.trySet(Integer.class, Value.INTEGER.valueAs(Integer.class))); + assertEquals(Value.INTEGER.value(), container.orElseThrow(Integer.class)); + var e = assertThrows(NoSuchElementException.class , () -> container.orElseThrow(Value.SHORT.clazz())); assertEquals("The type `" + Short.class + "` is know but there is no instance associated with it", e.getMessage()); } @@ -124,7 +125,7 @@ void getOrThrow(Set inputs) { void toString(Set inputs) { var container = StableValueFactories.ofHeterogeneousContainer(classes(inputs)); assertTrue(container.toString().contains("class java.lang.Integer=StableValue.unset")); - container.tryPut(Integer.class, Value.INTEGER.valueAs(Integer.class)); + container.trySet(Integer.class, Value.INTEGER.valueAs(Integer.class)); assertTrue(container.toString().contains("class java.lang.Integer=StableValue[" + Value.INTEGER.value() + "]"), container.toString()); } diff --git a/test/jdk/java/lang/StableValue/StableValueFactoriesTest.java b/test/jdk/java/lang/StableValue/StableValueFactoriesTest.java new file mode 100644 index 0000000000000..e02dcabc16ab3 --- /dev/null +++ b/test/jdk/java/lang/StableValue/StableValueFactoriesTest.java @@ -0,0 +1,43 @@ +/* + * 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 StableValueFactoriesTest implementations + * @modules java.base/jdk.internal.lang.stable + * @compile --enable-preview -source ${jdk.version} StableValueFactoriesTest.java + * @run junit/othervm --enable-preview StableValueFactoriesTest + */ + +import jdk.internal.lang.stable.StableValueFactories; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +final class StableValueFactoriesTest { + + @Test + void array() { + assertThrows(IllegalArgumentException.class, () -> StableValueFactories.array(-1)); + } + +} From f0cc9a7cff0cb85c25cb0eac7d92fb31cd2b49ed Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 11 Feb 2025 08:32:04 +0100 Subject: [PATCH 183/327] Revert changes to method names in StableHeterogeneousContainer --- .../stable/StableHeterogeneousContainer.java | 26 ++++++------- .../StableHeterogeneousContainerTest.java | 38 +++++++++---------- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableHeterogeneousContainer.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableHeterogeneousContainer.java index 46bab09d5c3ae..a78aa6856e6f4 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableHeterogeneousContainer.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableHeterogeneousContainer.java @@ -37,7 +37,7 @@ public sealed interface StableHeterogeneousContainer { * @throws IllegalArgumentException if the provided {@code type} is not a type * specified at creation */ - boolean trySet(Class type, T instance); + boolean tryPut(Class type, T instance); /** * {@return the instance associated with the provided {@code type}, {@code null} @@ -50,7 +50,7 @@ public sealed interface StableHeterogeneousContainer { * @throws IllegalArgumentException if the provided {@code type} is not a type * specified at creation */ - T orElseNull(Class type); + T get(Class type); /** * {@return the instance associated with the provided {@code type}, or throws @@ -65,7 +65,7 @@ public sealed interface StableHeterogeneousContainer { * @throws NoSuchElementException if the provided {@code type} is not associated * with an instance */ - T orElseThrow(Class type); + T getOrElseThrow(Class type); /** * {@return the instance associated with the provided {@code type}, if no association @@ -85,7 +85,7 @@ public sealed interface StableHeterogeneousContainer { * @param type of the associated instance * @param mapper to be used for computing the associated instance */ - T orElseSet(Class type, Function, ? extends T> mapper); + T getOrElseSet(Class type, Function, ? extends T> mapper); @ValueBased final class Impl implements StableHeterogeneousContainer { @@ -99,10 +99,10 @@ public Impl(Set> types) { @ForceInline @Override - public boolean trySet(Class type, T instance) { + public boolean tryPut(Class type, T instance) { Objects.requireNonNull(type); Objects.requireNonNull(instance, "The provided instance for '" + type + "' was null"); - return of(type) + return stableOf(type) .trySet( type.cast(instance) ); @@ -111,10 +111,10 @@ public boolean trySet(Class type, T instance) { @SuppressWarnings("unchecked") @ForceInline @Override - public T orElseSet(Class type, Function, ? extends T> mapper) { + public T getOrElseSet(Class type, Function, ? extends T> mapper) { Objects.requireNonNull(type); Objects.requireNonNull(mapper); - final StableValue stableValue = of(type); + final StableValue stableValue = stableOf(type); if (stableValue.isSet()) { return (T) stableValue.orElseThrow(); } @@ -140,18 +140,18 @@ public Object get() { @ForceInline @Override - public T orElseNull(Class type) { + public T get(Class type) { Objects.requireNonNull(type); return type.cast( - of(type) + stableOf(type) .orElse(null) ); } @ForceInline @Override - public T orElseThrow(Class type) { - final T t = orElseNull(type); + public T getOrElseThrow(Class type) { + final T t = get(type); if (t == null) { throw new NoSuchElementException("The type `" + type + "` is know but there is no instance associated with it"); } @@ -159,7 +159,7 @@ public T orElseThrow(Class type) { } @ForceInline - private StableValue of(Class type) { + private StableValue stableOf(Class type) { final StableValue stableValue = map.get(type); if (stableValue == null) { throw new IllegalArgumentException("No such type: " + type); diff --git a/test/jdk/java/lang/StableValue/StableHeterogeneousContainerTest.java b/test/jdk/java/lang/StableValue/StableHeterogeneousContainerTest.java index 10aae852da923..3996617ffd3a9 100644 --- a/test/jdk/java/lang/StableValue/StableHeterogeneousContainerTest.java +++ b/test/jdk/java/lang/StableValue/StableHeterogeneousContainerTest.java @@ -80,43 +80,43 @@ void factoryInvariants() { @MethodSource("nonEmptySets") void trySet(Set inputs) { var container = StableValueFactories.ofHeterogeneousContainer(classes(inputs)); - assertTrue(container.trySet(Integer.class, Value.INTEGER.valueAs(Integer.class))); - assertFalse(container.trySet(Integer.class, Value.INTEGER.valueAs(Integer.class))); - assertEquals(Value.INTEGER.value(), container.orElseNull(Integer.class)); - var iae = assertThrows(IllegalArgumentException.class, () -> container.trySet(Long.class, 8L)); + assertTrue(container.tryPut(Integer.class, Value.INTEGER.valueAs(Integer.class))); + assertFalse(container.tryPut(Integer.class, Value.INTEGER.valueAs(Integer.class))); + assertEquals(Value.INTEGER.value(), container.get(Integer.class)); + var iae = assertThrows(IllegalArgumentException.class, () -> container.tryPut(Long.class, 8L)); assertEquals("No such type: " + Long.class, iae.getMessage()); - var npe = assertThrows(NullPointerException.class, () -> container.trySet(Short.class, null)); + var npe = assertThrows(NullPointerException.class, () -> container.tryPut(Short.class, null)); assertEquals("The provided instance for '" + Short.class + "' was null", npe.getMessage()); } @ParameterizedTest @MethodSource("nonEmptySets") - void orElseSet(Set inputs) { + void getOrElseSet(Set inputs) { var container = StableValueFactories.ofHeterogeneousContainer(classes(inputs)); - assertEquals(Value.INTEGER.value(), container.orElseSet(Integer.class, Value.INTEGER::valueAs)); - assertEquals(Value.INTEGER.value(), container.orElseSet(Integer.class, Value.INTEGER::valueAs)); - var iae = assertThrows(IllegalArgumentException.class, () -> container.orElseSet(Long.class, _ -> 8L)); + assertEquals(Value.INTEGER.value(), container.getOrElseSet(Integer.class, Value.INTEGER::valueAs)); + assertEquals(Value.INTEGER.value(), container.getOrElseSet(Integer.class, Value.INTEGER::valueAs)); + var iae = assertThrows(IllegalArgumentException.class, () -> container.getOrElseSet(Long.class, _ -> 8L)); assertEquals("No such type: " + Long.class, iae.getMessage()); - var npe = assertThrows(NullPointerException.class, () -> container.orElseSet(Short.class, _ -> null)); + var npe = assertThrows(NullPointerException.class, () -> container.getOrElseSet(Short.class, _ -> null)); assertEquals("The constructor for `" + Short.class + "` returned null", npe.getMessage()); } @ParameterizedTest @MethodSource("nonEmptySets") - void orElseNull(Set inputs) { + void get(Set inputs) { var container = StableValueFactories.ofHeterogeneousContainer(classes(inputs)); - assertTrue(container.trySet(Integer.class, Value.INTEGER.valueAs(Integer.class))); - assertEquals(Value.INTEGER.value(), container.orElseNull(Integer.class)); - assertNull(container.orElseNull(Value.SHORT.clazz())); + assertTrue(container.tryPut(Integer.class, Value.INTEGER.valueAs(Integer.class))); + assertEquals(Value.INTEGER.value(), container.get(Integer.class)); + assertNull(container.get(Value.SHORT.clazz())); } @ParameterizedTest @MethodSource("nonEmptySets") - void orElseThrow(Set inputs) { + void getOrElseThrow(Set inputs) { var container = StableValueFactories.ofHeterogeneousContainer(classes(inputs)); - assertTrue(container.trySet(Integer.class, Value.INTEGER.valueAs(Integer.class))); - assertEquals(Value.INTEGER.value(), container.orElseThrow(Integer.class)); - var e = assertThrows(NoSuchElementException.class , () -> container.orElseThrow(Value.SHORT.clazz())); + assertTrue(container.tryPut(Integer.class, Value.INTEGER.valueAs(Integer.class))); + assertEquals(Value.INTEGER.value(), container.getOrElseThrow(Integer.class)); + var e = assertThrows(NoSuchElementException.class , () -> container.getOrElseThrow(Value.SHORT.clazz())); assertEquals("The type `" + Short.class + "` is know but there is no instance associated with it", e.getMessage()); } @@ -125,7 +125,7 @@ void orElseThrow(Set inputs) { void toString(Set inputs) { var container = StableValueFactories.ofHeterogeneousContainer(classes(inputs)); assertTrue(container.toString().contains("class java.lang.Integer=StableValue.unset")); - container.trySet(Integer.class, Value.INTEGER.valueAs(Integer.class)); + container.tryPut(Integer.class, Value.INTEGER.valueAs(Integer.class)); assertTrue(container.toString().contains("class java.lang.Integer=StableValue[" + Value.INTEGER.value() + "]"), container.toString()); } From 9abefb3131f3851ffbc8bc5de4c7fa3c64181bf6 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 11 Feb 2025 12:49:28 +0100 Subject: [PATCH 184/327] Improve the custom stable benchmarks --- .../CustomStableBiFunctionBenchmark.java | 49 ++++++++++++++++-- .../lang/stable/CustomStableFunctions.java | 8 +++ .../CustomStablePredicateBenchmark.java | 50 +++++++++++++++---- 3 files changed, 94 insertions(+), 13 deletions(-) diff --git a/test/micro/org/openjdk/bench/java/lang/stable/CustomStableBiFunctionBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/CustomStableBiFunctionBenchmark.java index 64b33ccf9e1d0..848f873f6ab3a 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/CustomStableBiFunctionBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/CustomStableBiFunctionBenchmark.java @@ -84,11 +84,16 @@ public class CustomStableBiFunctionBenchmark { return l * 2 + r; }; - private static final BiFunction FUNCTION = cachingBiFunction(SET, ORIGINAL); - private static final BiFunction FUNCTION2 = cachingBiFunction(SET, ORIGINAL); + private static final BiFunction FUNCTION = new StableBiFunction<>(SET, ORIGINAL); + private static final BiFunction FUNCTION2 = new StableBiFunction<>(SET, ORIGINAL); +// private static final BiFunction FUNCTION = cachingBiFunction(SET, ORIGINAL); +// private static final BiFunction FUNCTION2 = cachingBiFunction(SET, ORIGINAL); - private static final BiFunction function = cachingBiFunction(SET, ORIGINAL);; - private static final BiFunction function2 = cachingBiFunction(SET, ORIGINAL);; + private final BiFunction function = new StableBiFunction<>(SET, ORIGINAL); //cachingBiFunction(SET, ORIGINAL);; + private final BiFunction function2 = new StableBiFunction<>(SET, ORIGINAL); //cachingBiFunction(SET, ORIGINAL);; + +// private final BiFunction function = cachingBiFunction(SET, ORIGINAL);; +// private final BiFunction function2 = cachingBiFunction(SET, ORIGINAL);; private static final StableValue STABLE_VALUE = StableValue.of(); private static final StableValue STABLE_VALUE2 = StableValue.of(); @@ -229,6 +234,42 @@ public R apply(T t, U u) { } } +/* +This seams like a good solution: + +Benchmark Mode Cnt Score Error Units +CustomStableBiFunctionBenchmark.function avgt 10 6.460 ? 0.163 ns/op +CustomStableBiFunctionBenchmark.staticFunction avgt 10 0.361 ? 0.015 ns/op +CustomStableBiFunctionBenchmark.staticStableValue avgt 10 0.348 ? 0.053 ns/op + + */ + + record StableBiFunction( + Map>> delegate, + BiFunction original) implements BiFunction { + + public StableBiFunction(Set> inputs, BiFunction original) { + this(delegate(inputs), original); + } + + static Map>> delegate(Set> inputs) { + return Map.copyOf(inputs.stream() + .collect(Collectors.groupingBy(Pair::left, + Collectors.toUnmodifiableMap(Pair::right, + _ -> StableValue.of())))); + } + + @Override + public R apply(T t, U u) { + try { + return Objects.requireNonNull(delegate.get(t).get(u)) + .orElseSet(() -> original.apply(t, u)); + } catch (NullPointerException _) { + throw new IllegalArgumentException(t.toString() + ", " + u.toString()); + } + } + } + //Benchmark Mode Cnt Score Error Units //CustomCachingBiFunctionBenchmark.function avgt 10 8.123 ? 0.113 ns/op diff --git a/test/micro/org/openjdk/bench/java/lang/stable/CustomStableFunctions.java b/test/micro/org/openjdk/bench/java/lang/stable/CustomStableFunctions.java index a80f8cb86e8b2..dfe7dfc1e31aa 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/CustomStableFunctions.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/CustomStableFunctions.java @@ -47,6 +47,14 @@ static Predicate cachingPredicate(Set inputs, return delegate::apply; } + /* + Benchmark Mode Cnt Score Error Units + CustomStableBiFunctionBenchmark.function avgt 10 6.970 ? 0.121 ns/op + CustomStableBiFunctionBenchmark.staticFunction avgt 10 0.339 ? 0.010 ns/op + CustomStableBiFunctionBenchmark.staticStableValue avgt 10 0.337 ? 0.004 ns/op + */ + + static BiFunction cachingBiFunction(Set> inputs, BiFunction original) { diff --git a/test/micro/org/openjdk/bench/java/lang/stable/CustomStablePredicateBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/CustomStablePredicateBenchmark.java index b4daab8c3d589..98ae4d5facc33 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/CustomStablePredicateBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/CustomStablePredicateBenchmark.java @@ -34,6 +34,7 @@ import org.openjdk.jmh.annotations.Threads; import org.openjdk.jmh.annotations.Warmup; +import java.security.spec.ECField; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; @@ -59,7 +60,6 @@ // ,"-XX:PerMethodTrapLimit=0" }) @Threads(Threads.MAX) // Benchmark under contention -//@OperationsPerInvocation(2) public class CustomStablePredicateBenchmark { private static final Set SET = IntStream.range(0, 64).boxed().collect(Collectors.toSet()); @@ -74,6 +74,13 @@ public class CustomStablePredicateBenchmark { private static final Predicate PREDICATE = cachingPredicate(SET, EVEN); private final Predicate predicate = cachingPredicate(SET, EVEN); + private static final Function FUNCTION_PREDICATE = StableValue.function(SET, EVEN::test); + private final Function functionPredicate = StableValue.function(SET, EVEN::test); + + + private static final Predicate WRAPPED_PREDICATE = FUNCTION_PREDICATE::apply; + private final Predicate wrappedPredicate = FUNCTION_PREDICATE::apply; + @Benchmark public boolean predicate() { return predicate.test(VALUE); @@ -84,18 +91,43 @@ public boolean staticPredicate() { return PREDICATE.test(VALUE); } + @Benchmark + public boolean functionPredicate() { + return functionPredicate.apply(VALUE); + } + + @Benchmark + public boolean functionStaticPredicate() { + return FUNCTION_PREDICATE.apply(VALUE); + } - //Benchmark Mode Cnt Score Error Units - //CustomCachingPredicateBenchmark.predicate avgt 10 3.054 ? 0.155 ns/op - //CustomCachingPredicateBenchmark.staticPredicate avgt 10 2.205 ? 0.361 ns/op + @Benchmark + public boolean wrappedPredicate() { + return wrappedPredicate.test(VALUE); + } + + @Benchmark + public boolean wrappedStaticPredicate() { + return WRAPPED_PREDICATE.test(VALUE); + } +/* + Benchmark Mode Cnt Score Error Units + CustomStablePredicateBenchmark.functionPredicate avgt 10 4.223 ? 0.242 ns/op + CustomStablePredicateBenchmark.functionStaticPredicate avgt 10 0.699 ? 0.041 ns/op + CustomStablePredicateBenchmark.predicate avgt 10 5.077 ? 0.330 ns/op + CustomStablePredicateBenchmark.staticPredicate avgt 10 0.715 ? 0.062 ns/op + CustomStablePredicateBenchmark.wrappedPredicate avgt 10 5.229 ? 0.451 ns/op + CustomStablePredicateBenchmark.wrappedStaticPredicate avgt 10 0.776 ? 0.112 ns/op + */ // This is not constant foldable - record CachingPredicate(Map> delegate, - Predicate original) implements Predicate { + record StablePredicate(Map> delegate, + Predicate original) implements Predicate { - public CachingPredicate(Set inputs, Predicate original) { + public StablePredicate(Set inputs, Predicate original) { this(inputs.stream() - .collect(Collectors.toUnmodifiableMap(Function.identity(), _ -> StableValue.of())), + .collect(Collectors.toUnmodifiableMap(Function.identity(), + _ -> StableValue.of())), original ); } @@ -110,7 +142,7 @@ public boolean test(T t) { if (stable.isSet()) { return stable.orElseThrow(); } - synchronized (this) { + synchronized (stable) { if (stable.isSet()) { return stable.orElseThrow(); } From 14ace5dfd917dcec3aa4fdfc9f8eccf2c0687ace Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Fri, 14 Feb 2025 12:14:31 +0100 Subject: [PATCH 185/327] Add a test --- .../java/lang/reflect/AccessibleObject.java | 1 + .../StableValue/TrustedFieldTypeTest.java | 34 +++++++++++++++++-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/java.base/share/classes/java/lang/reflect/AccessibleObject.java b/src/java.base/share/classes/java/lang/reflect/AccessibleObject.java index a045f9c196a0b..723e1562b1c88 100644 --- a/src/java.base/share/classes/java/lang/reflect/AccessibleObject.java +++ b/src/java.base/share/classes/java/lang/reflect/AccessibleObject.java @@ -34,6 +34,7 @@ import jdk.internal.reflect.CallerSensitive; import jdk.internal.reflect.Reflection; import jdk.internal.reflect.ReflectionFactory; +import jdk.internal.vm.annotation.Stable; /** * The {@code AccessibleObject} class is the base class for {@code Field}, diff --git a/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java b/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java index e9c52749eb73b..eea6cb6c352c1 100644 --- a/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java +++ b/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java @@ -27,15 +27,18 @@ * @modules java.base/jdk.internal.lang.stable * @modules java.base/jdk.internal.misc * @compile --enable-preview -source ${jdk.version} TrustedFieldTypeTest.java - * @run junit/othervm --enable-preview TrustedFieldTypeTest + * @run junit/othervm --enable-preview --add-opens java.base/jdk.internal.lang.stable=ALL-UNNAMED -Dopens=true TrustedFieldTypeTest + * @run junit/othervm --enable-preview -Dopens=false TrustedFieldTypeTest */ +import jdk.internal.lang.stable.StableValueImpl; import jdk.internal.misc.Unsafe; import org.junit.jupiter.api.Test; import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; import java.lang.reflect.Field; +import java.lang.reflect.InaccessibleObjectException; import static org.junit.jupiter.api.Assertions.*; @@ -155,7 +158,7 @@ final class ArrayHolder { } @Test - void updateStableValueUnderlyingData() { + void updateStableValueContentVia_j_i_m_Unsafe() { StableValue stableValue = StableValue.of(); stableValue.trySet(42); jdk.internal.misc.Unsafe unsafe = Unsafe.getUnsafe(); @@ -169,4 +172,31 @@ void updateStableValueUnderlyingData() { assertEquals(13, stableValue.orElseThrow()); } + @Test + void updateStableValueContentViaSetAccessible() throws NoSuchFieldException, IllegalAccessException { + + if (Boolean.getBoolean("opens")) { + // Unfortunately, add-opens allows direct access to the `value` field + Field field = StableValueImpl.class.getDeclaredField("value"); + field.setAccessible(true); + + StableValue stableValue = StableValue.of(); + stableValue.trySet(42); + +// assertThrows(IllegalAccessException.class, () -> { + Object oldData = field.get(stableValue); + assertEquals(42, oldData); +// }); + +// assertThrows(IllegalAccessException.class, () -> { + field.set(stableValue, 13); +// }); + assertEquals(13, stableValue.orElseThrow()); + } else { + Field field = StableValueImpl.class.getDeclaredField("value"); + assertThrows(InaccessibleObjectException.class, ()-> field.setAccessible(true)); + } + } + + } From 82076f26f76b047903ee9f0a1230a8e5725ff533 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Fri, 14 Feb 2025 17:25:12 +0100 Subject: [PATCH 186/327] Update docs after CSR review --- .../share/classes/java/lang/StableValue.java | 92 +++++++++++++------ .../share/classes/java/util/Collection.java | 2 +- 2 files changed, 67 insertions(+), 27 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 97de32da23af5..3f6553f58a04a 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -66,27 +66,30 @@ * the content is set: * * {@snippet lang = java: - * class Component { + * public class Component { * * // Creates a new unset stable value with no content * // @link substring="of" target="#of" : * private final StableValue logger = StableValue.of(); * - * Logger getLogger() { + * private Logger getLogger() { * if (!logger.isSet()) { * logger.trySet(Logger.create(Component.class)); * } * return logger.orThrow(); * } * - * void process() { - * logger.get().info("Process started"); + * public void process() { + * getLogger().info("Process started"); * // ... * } * } *} *

    * Note that the holder value can only be set at most once. + * In the example above, the {@code logger} field is declared {@code final} which is + * a prerequisite for being treated as a constant by the JVM. + * *

    * To guarantee that, even under races, only one instance of {@code Logger} is ever * created, the {@linkplain #orElseSet(Supplier) orElseSet()} method can be used @@ -95,18 +98,18 @@ * form of a lambda expression: * * {@snippet lang = java: - * class Component { + * public class Component { * * // Creates a new unset stable value with no content * // @link substring="of" target="#of" : * private final StableValue logger = StableValue.of(); * - * Logger getLogger() { + * private Logger getLogger() { * return logger.orElseSet( () -> Logger.create(Component.class) ); * } * - * void process() { - * logger.get().info("Process started"); + * public void process() { + * getLogger().info("Process started"); * // ... * } * } @@ -133,13 +136,13 @@ * is first accessed: * * {@snippet lang = java: - * class Component { + * public class Component { * * private final Supplier logger = * // @link substring="supplier" target="#supplier(Supplier)" : * StableValue.supplier( () -> Logger.getLogger(Component.class) ); * - * void process() { + * public void process() { * logger.get().info("Process started"); * // ... * } @@ -158,17 +161,19 @@ * effect, the stable int function will act like a cache for the original {@linkplain IntFunction}: * * {@snippet lang = java: - * class SqrtUtil { + * public final class SqrtUtil { * - * private static final IntFunction SQRT = - * // @link substring="intFunction" target="#intFunction(int,IntFunction)" : - * StableValue.intFunction(10, StrictMath::sqrt); + * private SqrtUtil(){} * - * double sqrt9() { - * return SQRT.apply(9); // May eventually constant fold to 3.0 at runtime - * } + * private static final IntFunction SQRT = + * // @link substring="intFunction" target="#intFunction(int,IntFunction)" : + * StableValue.intFunction(10, StrictMath::sqrt); * - * } + * public static double sqrt9() { + * return SQRT.apply(9); // May eventually constant fold to 3.0 at runtime + * } + * + * } *} *

    * A stable function is a function that takes a parameter (of type {@code T}) and @@ -180,13 +185,15 @@ * stable function will act like a cache for the original {@linkplain Function}: * * {@snippet lang = java: - * class SqrtUtil { + * public final class SqrtUtil { + * + * private SqrtUtil(){} * * private static final Function SQRT = * // @link substring="function" target="#function(Set,Function)" : * StableValue.function(Set.of(1, 2, 4, 8, 16, 32), StrictMath::sqrt); * - * double sqrt16() { + * public static double sqrt16() { * return SQRT.apply(16); // May eventually constant fold to 4.0 at runtime * } * @@ -200,13 +207,15 @@ * are computed when they are first accessed, using a provided {@linkplain IntFunction}: * * {@snippet lang = java: - * class SqrtUtil { + * public final class SqrtUtil { + * + * private SqrtUtil(){} * * private static final List SQRT = * // @link substring="list" target="#list(int,IntFunction)" : * StableValue.list(10, StrictMath::sqrt); * - * double sqrt9() { + * public static double sqrt9() { * return SQRT.get(9); // May eventually constant fold to 3.0 at runtime * } * @@ -218,13 +227,15 @@ * using a provided {@linkplain Function}: * * {@snippet lang = java: - * class SqrtUtil { + * public final class SqrtUtil { + * + * private SqrtUtil(){} * * private static final Map SQRT = * // @link substring="map" target="#map(Set,Function)" : * StableValue.map(Set.of(1, 2, 4, 8, 16, 32), StrictMath::sqrt); * - * double sqrt16() { + * public static double sqrt16() { * return SQRT.get(16); // May eventually constant fold to 4.0 at runtime * } * @@ -238,7 +249,9 @@ * instance (that is dependent on the {@code Foo} instance) are lazily created, both of * which are held by stable values: * {@snippet lang = java: - * class Dependency { + * public final class DependencyUtil { + * + * private DependencyUtil(){} * * public static class Foo { * // ... @@ -270,7 +283,9 @@ * Here is another example where a more complex dependency graph is created in which * integers in the Fibonacci delta series are lazily computed: * {@snippet lang = java: - * class Fibonacci { + * public final class Fibonacci { + * + * private Fibonacci() {} * * private static final int MAX_SIZE_INT = 46; * @@ -326,6 +341,13 @@ * {@linkplain java.lang.ref##reachability strongly reachable}. Clients are advised that * {@linkplain java.lang.ref##reachability reachable} stable values will hold their set * content perpetually. + *

    + * A {@linkplain StableValue} that has a type parameter {@code T} that is an array + * type (of arbitrary rank) will only allow the JVM to treat the array reference + * as a stable value but not its components. Clients can instead use + * {@linkplain #list(int, IntFunction) a stable list} of arbitrary depth, which provides + * stable components. More generally, a stable value can hold other stable values of + * arbitrary depth and still provide transitive constantness. * * @implSpec Implementing classes of {@linkplain StableValue} are free to synchronize on * {@code this} and consequently, care should be taken whenever @@ -448,6 +470,8 @@ public sealed interface StableValue * {@return a new unset stable value} *

    * An unset stable value has no content. + *

    + * The returned stable value is not {@link Serializable}. * * @param type of the content */ @@ -457,6 +481,8 @@ static StableValue of() { /** * {@return a new pre-set stable value with the provided {@code content}} + *

    + * The returned stable value is not {@link Serializable}. * * @param content to set * @param type of the content @@ -480,6 +506,8 @@ static StableValue of(T content) { *

    * If the provided {@code original} supplier throws an exception, it is relayed * to the initial caller and no content is recorded. + *

    + * The returned supplier is not {@link Serializable}. * * @param original supplier used to compute a cached value * @param the type of results supplied by the returned supplier @@ -506,6 +534,8 @@ static Supplier supplier(Supplier original) { *

    * If the provided {@code original} int function throws an exception, it is relayed * to the initial caller and no content is recorded. + *

    + * The returned int function is not {@link Serializable}. * * @param size the size of the allowed inputs in {@code [0, size)} * @param original IntFunction used to compute cached values @@ -536,6 +566,8 @@ static IntFunction intFunction(int size, *

    * If the provided {@code original} function throws an exception, it is relayed to * the initial caller and no content is recorded. + *

    + * The returned function is not {@link Serializable}. * * @param inputs the set of allowed input values * @param original Function used to compute cached values @@ -567,6 +599,10 @@ static Function function(Set inputs, *

    * The returned list and its {@link List#subList(int, int) subList} views implement * the {@link RandomAccess} interface. + *

    + * The returned list is not {@link Serializable} and, as it is unmodifiable, does + * not implement the {@linkplain Collection##optional-operation optional operations} + * in the {@linkplain List} interface. * * @param size the size of the returned list * @param mapper to invoke whenever an element is first accessed @@ -597,6 +633,10 @@ static List list(int size, *

    * If the provided {@code mapper} throws an exception, it is relayed to the initial * caller and no value associated with the provided key is recorded. + *

    + * The returned map is not {@link Serializable} and, as it is unmodifiable, does + * not implement the {@linkplain Collection##optional-operations optional operations} + * in the {@linkplain Map} interface. * * @param keys the keys in the returned map * @param mapper to invoke whenever an associated value is first accessed diff --git a/src/java.base/share/classes/java/util/Collection.java b/src/java.base/share/classes/java/util/Collection.java index 0253dbc7e1aa7..43e8db55d7f4c 100644 --- a/src/java.base/share/classes/java/util/Collection.java +++ b/src/java.base/share/classes/java/util/Collection.java @@ -58,7 +58,7 @@ * constructors) but all of the general-purpose {@code Collection} * implementations in the Java platform libraries comply. * - *

    Certain methods are specified to be + *

    Certain methods are specified to be * optional. If a collection implementation doesn't implement a * particular operation, it should define the corresponding method to throw * {@code UnsupportedOperationException}. Such methods are marked "optional From 442b47a956740762b2a99a9f46775c4074802cf0 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Fri, 14 Feb 2025 17:39:39 +0100 Subject: [PATCH 187/327] Revert unintended change --- .../share/classes/java/lang/reflect/AccessibleObject.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/java.base/share/classes/java/lang/reflect/AccessibleObject.java b/src/java.base/share/classes/java/lang/reflect/AccessibleObject.java index 723e1562b1c88..a045f9c196a0b 100644 --- a/src/java.base/share/classes/java/lang/reflect/AccessibleObject.java +++ b/src/java.base/share/classes/java/lang/reflect/AccessibleObject.java @@ -34,7 +34,6 @@ import jdk.internal.reflect.CallerSensitive; import jdk.internal.reflect.Reflection; import jdk.internal.reflect.ReflectionFactory; -import jdk.internal.vm.annotation.Stable; /** * The {@code AccessibleObject} class is the base class for {@code Field}, From 64b620ddc4b47d00b1622a3797b61b8a2b249aa8 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 10 Mar 2025 07:14:53 +0100 Subject: [PATCH 188/327] Simplify link in JavaDoc --- src/java.base/share/classes/java/lang/StableValue.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 3f6553f58a04a..a0364c1e9c6a4 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -318,7 +318,7 @@ *

    * The at-most-once write operation on a stable value that succeeds * (e.g. {@linkplain #trySet(Object) trySet()}) - * happens-before + * {@linkplain java.util.concurrent##MemoryVisibility happens-before} * any subsequent read operation (e.g. {@linkplain #orElseThrow()}). *

    * The method {@linkplain StableValue#orElseSet(Supplier) orElseSet()} guarantees that From b12e9c597b889a4a247e26255868d87c80f9513c Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 10 Mar 2025 19:09:33 +0100 Subject: [PATCH 189/327] Remove unused classes --- .../stable/StableHeterogeneousContainer.java | 176 ------- .../lang/stable/StableValueFactories.java | 4 - test/jdk/java/lang/StableValue/JEP.md | 459 ------------------ test/jdk/java/lang/StableValue/JepTest.java | 423 ---------------- .../StableHeterogeneousContainerTest.java | 176 ------- .../CustomStableBiFunctionBenchmark.java | 327 ------------- .../lang/stable/CustomStableFunctions.java | 104 ---- .../CustomStablePredicateBenchmark.java | 156 ------ 8 files changed, 1825 deletions(-) delete mode 100644 src/java.base/share/classes/jdk/internal/lang/stable/StableHeterogeneousContainer.java delete mode 100644 test/jdk/java/lang/StableValue/JEP.md delete mode 100644 test/jdk/java/lang/StableValue/JepTest.java delete mode 100644 test/jdk/java/lang/StableValue/StableHeterogeneousContainerTest.java delete mode 100644 test/micro/org/openjdk/bench/java/lang/stable/CustomStableBiFunctionBenchmark.java delete mode 100644 test/micro/org/openjdk/bench/java/lang/stable/CustomStableFunctions.java delete mode 100644 test/micro/org/openjdk/bench/java/lang/stable/CustomStablePredicateBenchmark.java diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableHeterogeneousContainer.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableHeterogeneousContainer.java deleted file mode 100644 index a78aa6856e6f4..0000000000000 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableHeterogeneousContainer.java +++ /dev/null @@ -1,176 +0,0 @@ -package jdk.internal.lang.stable; - -import jdk.internal.ValueBased; -import jdk.internal.vm.annotation.DontInline; -import jdk.internal.vm.annotation.ForceInline; -import jdk.internal.vm.annotation.Stable; - -import java.util.Map; -import java.util.NoSuchElementException; -import java.util.Objects; -import java.util.Set; -import java.util.function.Function; -import java.util.function.Supplier; - -/** - * A stable heterogeneous container that can associate types to implementing instances. - *

    - * Attempting to associate a type with an instance that is {@code null} or attempting to - * provide a {@code type} that is {@code null} will result in a - * {@linkplain NullPointerException} for all methods in this class. - * - * @implNote Implementations of this interface are thread-safe and can be a value class - */ -public sealed interface StableHeterogeneousContainer { - - /** - * {@return {@code true} if the provided {@code type} was associated with the provided - * {@code instance}, {@code false} otherwise} - *

    - * When this method returns, the provided {@code type} is always associated with an - * instance. - * - * @param type the class type of the instance to store - * @param instance the instance to store - * @throws ClassCastException if the provided {@code instance} is not - * {@code null} and is not assignable to the type T - * @throws IllegalArgumentException if the provided {@code type} is not a type - * specified at creation - */ - boolean tryPut(Class type, T instance); - - /** - * {@return the instance associated with the provided {@code type}, {@code null} - * otherwise} - * - * @param type used to retrieve an associated instance - * @param type of the associated instance - * @throws ClassCastException if the associated instance is not assignable to - * the type T - * @throws IllegalArgumentException if the provided {@code type} is not a type - * specified at creation - */ - T get(Class type); - - /** - * {@return the instance associated with the provided {@code type}, or throws - * NoSuchElementException} - * - * @param type used to retrieve an associated instance - * @param type of the associated instance - * @throws ClassCastException if the associated instance is not assignable to - * the type T - * @throws IllegalArgumentException if the provided {@code type} is not a type - * specified at creation - * @throws NoSuchElementException if the provided {@code type} is not associated - * with an instance - */ - T getOrElseThrow(Class type); - - /** - * {@return the instance associated with the provided {@code type}, if no association - * is made, first attempts to compute and associate an instance with the - * provided {@code type} using the provided {@code mapper}} - *

    - * The provided {@code mapper} is guaranteed to be invoked at most once if it - * completes without throwing an exception. - *

    - * If the mapper throws an (unchecked) exception, the exception is rethrown, and no - * association is made. - *

    - * When this method returns, the provided {@code type} is always associated with an - * instance. - * - * @param type used to retrieve an associated instance - * @param type of the associated instance - * @param mapper to be used for computing the associated instance - */ - T getOrElseSet(Class type, Function, ? extends T> mapper); - - @ValueBased - final class Impl implements StableHeterogeneousContainer { - - @Stable - private final Map, StableValueImpl> map; - - public Impl(Set> types) { - this.map = StableValueFactories.map(types); - } - - @ForceInline - @Override - public boolean tryPut(Class type, T instance) { - Objects.requireNonNull(type); - Objects.requireNonNull(instance, "The provided instance for '" + type + "' was null"); - return stableOf(type) - .trySet( - type.cast(instance) - ); - } - - @SuppressWarnings("unchecked") - @ForceInline - @Override - public T getOrElseSet(Class type, Function, ? extends T> mapper) { - Objects.requireNonNull(type); - Objects.requireNonNull(mapper); - final StableValue stableValue = stableOf(type); - if (stableValue.isSet()) { - return (T) stableValue.orElseThrow(); - } - return orElseSetSlowPath(type, mapper, stableValue); - } - - @SuppressWarnings("unchecked") - @DontInline - public T orElseSetSlowPath(Class type, - Function, ? extends T> constructor, - StableValue stableValue) { - return (T) stableValue.orElseSet(new Supplier<>() { - @Override - public Object get() { - return type.cast( - Objects.requireNonNull( - constructor.apply(type), - "The constructor for `" + type + "` returned null" - )); - } - }); - } - - @ForceInline - @Override - public T get(Class type) { - Objects.requireNonNull(type); - return type.cast( - stableOf(type) - .orElse(null) - ); - } - - @ForceInline - @Override - public T getOrElseThrow(Class type) { - final T t = get(type); - if (t == null) { - throw new NoSuchElementException("The type `" + type + "` is know but there is no instance associated with it"); - } - return t; - } - - @ForceInline - private StableValue stableOf(Class type) { - final StableValue stableValue = map.get(type); - if (stableValue == null) { - throw new IllegalArgumentException("No such type: " + type); - } - return stableValue; - } - - @Override - public String toString() { - return map.toString(); - } - } - -} diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableValueFactories.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableValueFactories.java index b357231d0beb6..a434ecbdb6ba2 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableValueFactories.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableValueFactories.java @@ -46,10 +46,6 @@ public static Function function(Set inputs, : StableFunction.of(inputs, original); } - public static StableHeterogeneousContainer ofHeterogeneousContainer(Set> types) { - return new StableHeterogeneousContainer.Impl(types); - } - public static List list(int size, IntFunction mapper) { return SharedSecrets.getJavaUtilCollectionAccess().stableList(size, mapper); } diff --git a/test/jdk/java/lang/StableValue/JEP.md b/test/jdk/java/lang/StableValue/JEP.md deleted file mode 100644 index 1e69a0113cf48..0000000000000 --- a/test/jdk/java/lang/StableValue/JEP.md +++ /dev/null @@ -1,459 +0,0 @@ -# Stable Values (Preview) - -## Summary - -Introduce a _Stable Values API_, which provides performant immutable value holders where elements -are initialized _at most once_. Stable Values offer the performance and safety benefits of -final fields, while offering greater flexibility as to the timing of initialization. This is a [preview API](https://openjdk.org/jeps/12). - -## Goals - -- Provide an easy and intuitive API to describe value holders that can change at most once. -- Decouple declaration from initialization without significant footprint or performance penalties. -- Reduce the amount of static initializer and/or field initialization code. -- Uphold integrity and consistency, even in a multithreaded environment. - -## Non-goals - -- It is not a goal to provide additional language support for expressing lazy computation. -This might be the subject of a future JEP. -- It is not a goal to prevent or deprecate existing idioms for expressing lazy initialization. - -## Motivation - -Java allows developers to control whether fields should be mutable or not. Mutable fields can be updated multiple times, and from any arbitrary position in the code and by any thread. As such, mutable fields are often used to model complex objects whose state can be updated several times throughout their lifetimes, such as the contents of a text field in a UI component. Conversely, immutable fields (i.e. `final` fields), must be updated exactly *once*, and only in very specific places: the class initializer (for a static immutable field) or the class constructor(for an instance immutable field). As such, `final` fields are typically used to model values that act as *constants* (albeit shallowly so) throughout the lifetime of a class (in the case of `static` fields) or of an instance (in the case of an instance field). - -Most of the time deciding whether an object should feature mutable or immutable state is straightforward enough. There are however cases where a field is subject to *constrained mutation*. That is, a field's value is neither constant, nor can it be mutated at will. Consider a program that might want to mutate a password field at most three times before it becomes immutable, in order to reflect the three login attempts allowed for a user; further mutation would result in some kind of exception. Expressing this kind of constrained mutation is hard, and cannot be achieved without the help of advanced type-system [calculi](https://en.wikipedia.org/wiki/Dependent_type). - -However, one important and simpler case of constrained mutation is that of a field whose updated *at most once*. As we shall see, the lack of a mechanism to capture this specific kind of constrained mutation in the Java platform incurs a considerable cost of performance and expressiveness. - -#### An example: memoization - -Constrained mutation is essential to reliably cache the result of an expensive method call, so that it can be reused several times throughout the lifetime of an application (this technique is also known as [memoization](https://en.wikipedia.org/wiki/Memoization)). A nearly ubiquitous example of such an expensive method call is that to obtain a logger object through which an application's events can be reported. Obtaining a logger often entails expensive operations, such as reading and parsing configuration data, or preparing the backing storage where logging events will be recorded. Since these operations are expensive, an application will typically want to move them as much *forward in time* as possible: after all, an application might never need to log an event, so why pay the cost for this expensive initialization? Moreover, as some of these operations result in side effects - such as the creation of files and folders - it is crucial that they are executed _at most once_. - -Combining mutable fields and encapsulation is a common way to approximate at-most-once update semantics. Consider the following example, where a logger object is created in the `Application::getLogger` method: - -``` -public class Application { - - private Logger logger; - - public Logger getLogger() { - if (logger == null) { - logger = Logger.create("com.company.Application"); - } - return logger; - } -} -``` - -As the `logger` field is private, the only way for clients to access it is to call the `getLogger` method. This method first tests whether a logger is already available, and if so that logger is returned. Otherwise, it proceeds to the creation of a *new* logger object, which is then stored in the `logger` field. In this way, we guarantee that the logger object is created at most once: the first time the `Application::getLogger` method is invoked. - -Unfortunately, the above solution does not work in a multithreaded environment. For instance, updates to the `logger` field made by one thread may not be immediately visible to other threads. This condition might result in multiple concurrent calls to the `Logger::create` method, thereby violating the "at-most-once" update guarantee. - -##### Thread safety with synchronized - -One possible way to achieve thread safety would be to serialize access to the `Application::getLogger` method - i.e. by marking that method as `synchronized`. - -``` -public class Application { - - private Logger logger; - - public synchronized Logger getLogger() { - if (logger == null) { - logger = Logger.create("com.company.Application"); - } - return logger; - } -} -``` - -However, doing so has a performance cost, as multiple threads cannot concurrently obtain the application's logger object, even *long after* this object has been computed, and safely stored in the `logger` field. In other words, using `synchronized` amounts at applying a *permanent* performance tax on *all* logger accesses, in the rare event that a race occurs during the initial update of the `logger` field. - -A more efficient solution is the so-called [class holder idiom](https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom), which achieves thread safe "at-most-once" update guarantees by leaning on the lazy semantics of class initialization. However, for this approach to work correctly, the memoized data should be `static`, which is not the case here. Instead, for instance variables, it is also possible to resort to the brittle [double-checked idiom](https://en.wikipedia.org/wiki/Double-checked_locking) further described in the Alternative section below. - -##### Problems with synchronized - -Unfortunately, synchronized locking has several inherent design flaws: - -* *lack of expressiveness* - he synchronized idiom leaves a lot to be desired. The "at-most-once" mutation guarantee is not explicitly manifest in the code: after all the `logger` field is just a plain mutable field. This leaves important semantics gaps that is impossible to plug. For example, the `logger` field can be accidentally mutated in another method of the `Application` class. In another example, the field might be reflectively mutated using `setAccessible`. Avoiding these pitfalls is ultimately left to developers. -* *lack of optimizations* - as the `logger` field is updated at most once, one might expect the JVM to optimize access to this field accordingly, e.g. by [constant-folding](https://en.wikipedia.org/wiki/Constant_folding) access to an already-initialized `logger` field. Unfortunately, since `logger` is just a plan mutable field, the JVM cannot trust the field to never be updated again. As such, access to at-most-once fields, when realized with double-checked locking is not as efficient as it could be. -* *limited applicability* - synchronized locking fails to scale to more complex use cases where e.g. the client might need an *array* of values where each element can be updated at most once. Access to one element will block access to _all_ the other elements unless there is a distinct synchronization object for each element. - -##### At-most-once as a first-class concept - -At-most-once semantics is unquestionably critical to implement important use cases such as caches and memoized functions. Unfortunately, existing workarounds, such as double-checked locking, cannot be considered adequate replacements for *first-class* "at-most-once" support. What we are missing is a way to *promise* that a variable will be initialized by the time it is used, with a value that is computed at most once, and *safely* across multiple threads. Such a mechanism would give the Java runtime maximum opportunity to stage and optimize its computation, thus avoiding the penalties that plague the workarounds shown above. Moreover, such a mechanism should gracefully scale to handle *collections* of "at-most-once" variables, while retaining efficient computer resource management. - -When fully realized, first-class support for "at-most-once" semantics would fill an important gap between mutable and immutable fields, as shown in the table below: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Storage kind#UpdatesCore update locationConstant foldingConcurrent updates
    Mutable (non-final)[0, ∞)AnywhereNoYes
    "At-most-once"[0, 1]AnywhereYes, after updateYes, but only one thread "wins"
    Immutable (final)1Constructor or static initializerYesNo
    - -_Table 1: Shows the properties of mutable, at-most-once, and immutable variables._ - -## Description - -The Stable Values API defines an interface so that client code in libraries and applications can - -- Define and use a stable value: - - [`StableValue.newInstance()`](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/java/lang/StableValue.html#newInstance()) -- Define various _caching_ functions: - - [`StableValue.newCachedSupplier()`](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/java/lang/StableValue.html#newCachingSupplier(java.util.function.Supplier,java.util.concurrent.ThreadFactory)) - - [`StableValue.newCachedIntFunction()`](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/java/lang/StableValue.html#newCachingIntFunction(int,java.util.function.IntFunction,java.util.concurrent.ThreadFactory)) - - [`StableValue.newCachedFunction()`](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/java/lang/StableValue.html#newCachingFunction(java.util.Set,java.util.function.Function,java.util.concurrent.ThreadFactory)) -- Define _lazy_ collections: - - [`StableValue.lazyList(int size)`](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/java/lang/StableValue.html#lazyList(int,java.util.function.IntFunction)) - - [`StableValue.lazyMap(Set keys)`](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/java/lang/StableValue.html#lazyMap(java.util.Set,java.util.function.Function)) - -The Stable Values API resides in the [java.lang](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/java/lang/package-summary.html) package of the [java.base](https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/module-summary.html) module. - -### Stable values - -A _stable value_ is a thin, atomic, non-blocking, thread-safe, set-at-most-once, stable value holder -eligible for certain JVM optimizations if set to a value. It is expressed as an object of type -`java.lang.StableValue`, which, like `Future`, is a holder for some computation that may or may not have -occurred yet. Fresh (unset) `StableValue` instances are created via the factory method `StableValue::newInstance`: - -``` -class Foo { - // 1. Declare a Stable field - private static final StableValue LOGGER = StableValue.newInstance(); - - static Logger logger() { - - if (!LOGGER.isSet()) { - // 2. Set the stable value _after_ the field was declared - // If another thread has already set a value, this is a - // no-op and we will continue to 3. and get that value. - LOGGER.trySet(Logger.getLogger("com.company.Foo")); - } - - // 3. Access the stable value with as-declared-final performance - return LOGGER.orElseThrow(); - } - ... - logger().log(Level.DEBUG, ...); -} -``` - -Setting a stable value is an atomic, non-blocking, thread-safe operation, i.e. `StableValue::trySet`, -either results in successfully initializing the `StableValue` to a value, or retaining -an already set value. This is true regardless of whether the stable value is accessed by a single -thread, or concurrently, by multiple threads. - -A stable value may be set to `null` which then will be considered its set value. -Null-averse applications can also use `StableValue>`. - -In addition, stable values are eligible for constant folding optimizations by the JVM. In many -ways, this is similar to the holder-class idiom in the sense it offers the same -performance and constant-folding characteristics but, `StableValue` incurs a lower static -footprint since no additional class is required. - -Looking at the basic example above, it becomes evident, that several threads may invoke the `Logger::getLogger` -method simultaneously if they call the `logger()` method at about the same time. Even though -`StableValue` will guarantee, that only one of these results will ever be exposed to the many -competing threads, there might be applications where it is a requirement, that a supplying method is -called *only once*. This brings us to the introduction of _cached functions_. - -### Caching functions - -So far, we have talked about the fundamental features of StableValue as a securely -wrapped stable value holder. However, it has become apparent, that stable primitives are amenable -to composition with other constructs in order to create more high-level and powerful features. - -[Caching (or Memoized) functions](https://en.wikipedia.org/wiki/Memoization) are functions where the output for a -particular input value is computed only once and is remembered such that remembered outputs can be -reused for subsequent calls with recurring input values. - -In cases where the invoke-at-most-once property of a `Supplier` is important, the -Stable Values API offers a _cached supplier_ which is a caching, thread-safe, stable, -lazily computed `Supplier` that records the value of an _original_ `Supplier` upon being first -accessed via its `Supplier::get` method. In a multithreaded scenario, competing threads will block -until the first thread has computed a cached value. Unsurprisingly, the cached value is -stored internally in a stable value. - -Here is how the code in the previous example can be improved using a caching supplier: - -``` -class Foo { - - // 1. Centrally declare a caching supplier and define how it should be computed - private static final Supplier LOGGER = - StableValue.newCachingSupplier( () -> Logger.getLogger("com.company.Foo")); - - static Logger logger() { - // 2. Access the cached value with as-declared-final performance - // (single evaluation made before the first access) - return LOGGER.get(); - } - ... - logger().log(Level.DEBUG, ...); -} -``` - -In the example above, the original `Supplier` provided is invoked at most once per loading of the containing -class `Foo` (`Foo`, in turn, can be loaded at most once into any given `ClassLoader`) and it is backed -by a lazily computed stable value. - -Analogous to how a `Supplier` can be cached using a backing stable value, a similar pattern -can be used for an `IntFunction` that will record its cached values in a backing array of -stable value elements. Here is an example where we manually map logger numbers -(0 -> "com.company.Foo" , 1 -> "com.company.Bar") to loggers: - -``` -class CachedNum { - - // 1. Centrally declare a caching IntFunction backed by a list of StableValue elements - private static final IntFunction LOGGERS = - StableValue.newCachingIntFunction(2, CachedNum::fromNumber); - - // 2. Define a function that is to be called the first - // time a particular message number is referenced - // The given loggerNumber is manually mapped to loggers - private static Logger fromNumber(int loggerNumber) { - return switch (loggerNumber) { - case 0 -> Logger.getLogger("com.company.Foo"); - case 1 -> Logger.getLogger("com.company.Bar"); - default -> throw new IllegalArgumentException(); - }; - } - - // 3. Access the cached element with as-declared-final performance - // (evaluation made before the first access) - Logger logger = LOGGERS.apply(0); - ... - logger.log(Level.DEBUG, ...); -} -``` - -As can be seen, manually mapping numbers to strings is a bit tedious. This brings us to the most general caching function -variant provided is a caching `Function` which, for example, can make sure `Logger::getLogger` in one of the first examples -above is invoked at most once per input value (provided it executes successfully) in a multithreaded environment. Such a -caching `Function` is almost always faster and more resource efficient than a `ConcurrentHashMap`. - -Here is what a caching `Function` lazily holding two loggers could look like: - -``` -class Cahced { - - // 1. Centrally declare a caching function backed by a map of stable values - private static final Function LOGGERS = - StableValue.newCachingFunction(Set.of("com.company.Foo", "com.company.Bar"), - Logger::getLogger, null); - - private static final String NAME = "com.company.Foo"; - - // 2. Access the cached value via the function with as-declared-final - // performance (evaluation made before the first access) - Logger logger = LOGGERS.apply(NAME); - ... - logger.log(Level.DEBUG, ...); -} -``` - -It should be noted that the enumerated set of valid inputs given at creation time constitutes the only valid -input keys for the caching function. Providing a non-valid input for a caching `Function` (or a caching `IntFunction`) -would incur an `IllegalArgumentException`. - -An advantage with caching functions, compared to working directly with `StableValue` instances, is that the -initialization logic can be centralized and maintained in a single place, usually at the same place where -the caching function is defined. - -Additional caching function types, such a caching `Predicate` (backed by a lazily computed -`Map>`) or a caching `BiFunction` can be custom-made. An astute reader will be able -to write such constructs in a few lines. - -#### Background threads - -The caching-returning factory overloads in the Stable Values API offers an optional -tailing thread factory parameter from which new value-computing background threads will be created: - -``` -// 1. Centrally declare a caching function backed by a map of stable values -// computed in the background by two distinct virtual threads. -private static final Function LOGGERS = - StableValue.newCachingFunction(Set.of("com.company.Foo", "com.company.Bar"), - Logger::getLogger, - // Create cheap virtual threads for background computation - Thread.ofVirtual().factory()); - -... Somewhere else in the code - -private static final String NAME = "com.company.Foo"; - -// 2. Access the cached value via the function with as-declared-final -// performance (evaluation made before the first access) -Logger logger = LOGGERS.apply(NAME); -... -logger.log(Level.DEBUG, ...); -``` - -This can provide a best-of-several-worlds situation where the caching function can be quickly defined (as no -computation is made by the defining thread), the holder value is computed in background threads (thus neither -interfering significantly with the critical startup path nor with future accessing threads), and the threads actually -accessing the holder value can directly access the holder value with as-if-final performance and without having to compute -a holder value. This is true under the assumption, that the background threads can complete computation before accessing -threads requires a holder value. If this is not the case, at least some reduction of blocking time can be enjoyed as -the background threads have had a head start compared to the accessing threads. - -### Stable collections - -The Stable Values API also provides factories that allow the creation of new -collection variants that are lazy, shallowly immutable, and stable: - -- `List` of stable elements -- `Map` of stable values - -Lists of lazily computed stable elements are objects of type `List` where each -element in such a list enjoys the same properties as a `StableValue`. - -Like a `StableValue` object, a lazy `List` of stable value elements is created via a factory -method while additionally providing the `size` of the desired `List` and a mapper of type -`IntFunction` to be used when computing the lazy elements on demand: - -``` -List lazyList = StableValue.lazyList(size, mapper); -``` - -Note how there's only one variable of type `List` to initialize even though every -computation is performed independently of the other element of the list when accessed (i.e. no -blocking will occur across threads computing distinct elements simultaneously). Also, the -`IntSupplier` mapper provided at creation is only invoked at most once for each distinct input -value. The Stable Values API allows modeling this cleanly, while still preserving -good constant-folding guarantees and integrity of updates in the case of multithreaded access. - -It should be noted that even though a lazily computed list of stable elements might mutate its -internal state upon external access, it is _still shallowly immutable_ because _no first-level -change can ever be observed by an external observer_. This is similar to other immutable classes, -such as `String` (which internally caches its `hash` value), where they might rely on mutable -internal states that are carefully kept internally and that never shine through to the outside world. - -Just as a lazy `List` can be created, a `Map` of lazily computed stable values can also be defined -and used similarly: - -``` -// 1. Declare a lazy stable map of loggers with two allowable keys: -// "com.company.Foo" and "com.company.Bar" -static final Map LOGGERS = - StableValue.lazyMap(Set.of("com.company.Foo", "com.company.Bar"), Logger::getLogger); - -// 2. Access the lazy map with as-declared-final performance -// (evaluation made before the first access) -static Logger logger(String name) { - return LOGGERS.get(name); -} -... -logger("com.company.Foo").log(Level.DEBUG, ...); -``` - -In the example above, only two input values were used. However, this concept allows declaring a -large number of stable values that can be easily retrieved using arbitrarily, but pre-specified, -keys in a resource-efficient and performant way. For example, high-performance, non-evicting caches -may now be easily and reliably realized. - -Analog to a lazy list, the lazy map guarantees the function provided at map creation -(used to lazily compute the map values) is invoked at most once per key (absent any Exceptions), -even though used from several threads. - -Even though a caching `IntFunction` may sometimes replace a lazy `List` and a caching `Function` may be -used in place of a lazy `Map`, a lazy `List` or `Map` oftentimes provides better interoperability with -existing libraries. For example, if provided as a method parameter. - -### Preview feature - -The Stable Values is a [preview API](https://openjdk.org/jeps/12), disabled by default. -To use the Stable Value APIs, the JVM flag `--enable-preview` must be passed in, as follows: - -- Compile the program with `javac --release 24 --enable-preview Main.java` and run it with `java --enable-preview Main`; or, - -- When using the source code launcher, run the program with `java --source 24 --enable-preview Main.java`; or, - -- When using `jshell`, start it with `jshell --enable-preview`. - -## Alternatives - -There are other classes in the JDK that support lazy computation including `Map`, `AtomicReference`, `ClassValue`, -and `ThreadLocal` all of which, unfortunately, support arbitrary mutation and thus, hinder the JVM from reasoning -about constantness thereby preventing constant folding and other optimizations. - -Another alternative is to resort to the brittle [double-checked idiom](https://en.wikipedia.org/wiki/Double-checked_locking): - -``` -class Application { - - private volatile Logger logger; - - public Logger logger() { - Logger v = logger; - if (v == null) { - synchronized (this) { - v = logger; - if (v == null) { - logger = v = Logger.create("com.company.Application"); - } - } - } - return v; - } -} -``` - -The basic idea behind double-checked locking is to reduce the chances for callers to enter a `synchronized` block. After all, in the common case, we expect the `logger` field to already contain a logger object, in which case we can just return that object, without any performance hit. In the rare event where `logger` is not set, we must enter a `synchronized` block, and check its value again (as such value might have changed upon entering the block). For the double-checked idiom to work correctly, it is necessary for the `logger` field is marked as `volatile`. This ensures that reading that field across multiple threads can result in one of two outcomes: the field either appears to be uninitialized (its value set to `null`), or initialized (its value set to the final logger object). That is, no *dirty reads* are possible. As with all existing solutions, the double-checked locking idiom does not provide constant folding. - -So, alternatives would be to keep using explicit double-checked locking, maps, holder classes, Atomic classes, -and third-party frameworks. Another alternative would be to add language support for immutable value holders. - -## Risks and assumptions - -Creating an API to provide thread-safe computed constant fields with an on-par performance with holder -classes efficiently is a non-trivial task. It is, however, assumed that the current JIT implementations -will likely suffice to reach the goals of this JEP. - -## Dependencies - -The work described here will likely enable subsequent work to provide pre-evaluated stable value fields at -compile, condensation, and/or runtime. diff --git a/test/jdk/java/lang/StableValue/JepTest.java b/test/jdk/java/lang/StableValue/JepTest.java deleted file mode 100644 index 1db28a638b984..0000000000000 --- a/test/jdk/java/lang/StableValue/JepTest.java +++ /dev/null @@ -1,423 +0,0 @@ -/* - * Copyright (c) 2024, 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 JepTest implementations - * @compile --enable-preview -source ${jdk.version} JepTest.java - * @run junit/othervm --enable-preview JepTest - */ - -import java.io.IOException; -import java.io.UncheckedIOException; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.VarHandle; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.AbstractList; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.function.BiFunction; -import java.util.function.Function; -import java.util.function.IntFunction; -import java.util.function.IntPredicate; -import java.util.function.Predicate; -import java.util.function.Supplier; -import java.util.logging.Logger; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -final class JepTest { - - class Progression0 { - - static final - public class Cache { - - private Logger logger; - - public Logger logger() { - Logger v = logger; - if (v == null) { - logger = v = Logger.getLogger("com.company.Foo"); - } - return v; - } - } - } - - class Progression1 { - - static final - public class Cache { - - private Logger logger; - - public synchronized Logger logger() { - Logger v = logger; - if (v == null) { - logger = v = Logger.getLogger("com.company.Foo"); - } - return v; - } - } - } - - class Progression2 { - - static final - public class Cache { - - private volatile Logger logger; - - public Logger logger() { - Logger v = logger; - if (v == null) { - synchronized (this) { - v = logger; - if (v == null) { - logger = v = Logger.getLogger("com.company.Foo"); - } - } - } - return v; - } - } - } - - class Progression3 { - - static final - public class Cache { - - private static final VarHandle ARRAY_HANDLE = MethodHandles.arrayElementVarHandle(Logger[].class); - private static final int SIZE = 10; - private final Logger[] loggers = new Logger[SIZE]; - - public Logger logger(int i) { - Logger v = (Logger) ARRAY_HANDLE.getVolatile(loggers, i); - if (v == null) { - synchronized (this) { - v = loggers[i]; - if (v == null) { - ARRAY_HANDLE.setVolatile(loggers, i, v = Logger.getLogger("com.company.Foo" + i)); - } - } - } - return v; - } - } - } - - - class Foo { - // 1. Declare a Stable field - private static final StableValue LOGGER = StableValue.of(); - - static Logger logger() { - - if (!LOGGER.isSet()) { - // 2. Set the stable value _after_ the field was declared - LOGGER.trySet(Logger.getLogger("com.company.Foo")); - } - - // 3. Access the stable value with as-declared-final performance - return LOGGER.orElseThrow(); - } - } - - class Foo2 { - - // 1. Centrally declare a caching supplier and define how it should be computed - private static final Supplier LOGGER = - StableValue.supplier( () -> Logger.getLogger("com.company.Foo") ); - - - static Logger logger() { - // 2. Access the cached value with as-declared-final performance - // (single evaluation made before the first access) - return LOGGER.get(); - } - } - - class MapDemo { - - // 1. Declare a lazy stable map of loggers with two allowable keys: - // "com.company.Bar" and "com.company.Baz" - static final Map LOGGERS = - StableValue.map(Set.of("com.company.Foo", "com.company.Bar"), Logger::getLogger); - - // 2. Access the lazy map with as-declared-final performance - // (evaluation made before the first access) - static Logger logger(String name) { - return LOGGERS.get(name); - } - } - - static - class CachedNum { - // 1. Centrally declare a cached IntFunction backed by a list of StableValue elements - private static final IntFunction LOGGERS = - StableValue.intFunction(2, CachedNum::fromNumber); - - // 2. Define a function that is to be called the first - // time a particular message number is referenced - // The given loggerNumber is manually mapped to loggers - private static Logger fromNumber(int loggerNumber) { - return switch (loggerNumber) { - case 0 -> Logger.getLogger("com.company.Foo"); - case 1 -> Logger.getLogger("com.company.Bar"); - default -> throw new IllegalArgumentException(); - }; - } - - // 3. Access the cached element with as-declared-final performance - // (evaluation made before the first access) - Logger logger = LOGGERS.apply(0); - } - - class Cached { - - // 1. Centrally declare a cached function backed by a map of stable values - private static final Function LOGGERS = - StableValue.function(Set.of("com.company.Foo", "com.company.Bar"), - Logger::getLogger); - - private static final String NAME = "com.company.Foo"; - - // 2. Access the cached value via the function with as-declared-final - // performance (evaluation made before the first access) - Logger logger = LOGGERS.apply(NAME); - } - - class A { - - static - class ErrorMessages { - private static final int SIZE = 8; - - // 1. Centrally declare a cached IntFunction backed by a list of StableValue elements - private static final IntFunction ERROR_FUNCTION = - StableValue.intFunction(SIZE, ErrorMessages::readFromFile); - - // 2. Define a function that is to be called the first - // time a particular message number is referenced - private static String readFromFile(int messageNumber) { - try { - return Files.readString(Path.of("message-" + messageNumber + ".html")); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - // 3. Access the cached element with as-declared-final performance - // (evaluation made before the first access) - String msg = ERROR_FUNCTION.apply(2); - } - } - - record CachingPredicate(Map> delegate, - Function original) implements Predicate { - - public CachingPredicate(Set inputs, Predicate original) { - this(inputs.stream() - .collect(Collectors.toMap(Function.identity(), _ -> StableValue.of())), - original::test - ); - } - - @Override - public boolean test(T t) { - final StableValue stable = delegate.get(t); - if (stable == null) { - throw new IllegalArgumentException(t.toString()); - - } - return stable.orElseSet(() -> original.apply(t)); - } - } - - record CachingPredicate2(Map delegate) implements Predicate { - - public CachingPredicate2(Set inputs, Predicate original) { - this(StableValue.map(inputs, original::test)); - } - - @Override - public boolean test(T t) { - return delegate.get(t); - } - } - - public static void main(String[] args) { - Predicate even = i -> i % 2 == 0; - Predicate cachingPredicate = StableValue.map(Set.of(1, 2), even::test)::get; - } - - record Pair(L left, R right){} - - record CachingBiFunction(Map, StableValue> delegate, - Function, R> original) implements BiFunction { - - @Override - public R apply(T t, U u) { - final var key = new Pair<>(t, u); - final StableValue stable = delegate.get(key); - if (stable == null) { - throw new IllegalArgumentException(t + ", " + u); - } - return stable.orElseSet(() -> original.apply(key)); - } - - static CachingBiFunction of(Set> inputs, BiFunction original) { - - Map, StableValue> map = inputs.stream() - .collect(Collectors.toMap(Function.identity(), _ -> StableValue.of())); - - return new CachingBiFunction<>(map, pair -> original.apply(pair.left(), pair.right())); - } - - } - - record CachingBiFunction2(Map, StableValue> delegate, - BiFunction original) implements BiFunction { - - @Override - public R apply(T t, U u) { - final var key = new Pair<>(t, u); - final StableValue stable = delegate.get(key); - if (stable == null) { - throw new IllegalArgumentException(t + ", " + u); - } - return stable.orElseSet(() -> original.apply(key.left(), key.right())); - } - - static BiFunction of(Set> inputs, BiFunction original) { - - Map, StableValue> map = inputs.stream() - .collect(Collectors.toMap(Function.identity(), _ -> StableValue.of())); - - return new CachingBiFunction2<>(map, original); - } - - } - - static - class Application { - private final StableValue LOGGER = StableValue.of(); - - public Logger getLogger() { - return LOGGER.orElseSet(() -> Logger.getLogger("com.company.Foo")); - } - } - - record CachingIntPredicate(List> outputs, - IntPredicate resultFunction) implements IntPredicate { - - CachingIntPredicate(int size, IntPredicate resultFunction) { - this(Stream.generate(StableValue::of).limit(size).toList(), resultFunction); - } - - @Override - public boolean test(int value) { - return outputs.get(value).orElseSet(() -> resultFunction.test(value)); - } - - } - - - static - class FixedStableList extends AbstractList { - private final StableValue[] elements; - - FixedStableList(int size) { - this.elements = Stream.generate(StableValue::of) - .limit(size) - .toArray(StableValue[]::new); - } - - @Override - public E get(int index) { - return elements[index].orElseThrow(); - } - - - - @Override - public E set(int index, E element) { - elements[index].setOrThrow(element); - return null; - } - - - - @Override - public int size() { - return elements.length; - } - } - - static - class Dependency { - - public static class Foo { - // ... - } - - public static class Bar { - public Bar(Foo foo) { - // ... - } - } - - private static final Supplier FOO = StableValue.supplier(Foo::new); - private static final Supplier BAR = StableValue.supplier(() -> new Bar(FOO.get())); - - public static Foo foo() { - return FOO.get(); - } - - public static Bar bar() { - return BAR.get(); - } - - } - - static - class Fibonacci { - - private static final int MAX_SIZE_INT = 46; - - private static final IntFunction FIB = - StableValue.intFunction(MAX_SIZE_INT, Fibonacci::fib); - - public static int fib(int n) { - return n < 2 - ? n - : FIB.apply(n - 1) + FIB.apply(n - 2); - } - - } - - -} diff --git a/test/jdk/java/lang/StableValue/StableHeterogeneousContainerTest.java b/test/jdk/java/lang/StableValue/StableHeterogeneousContainerTest.java deleted file mode 100644 index 3996617ffd3a9..0000000000000 --- a/test/jdk/java/lang/StableValue/StableHeterogeneousContainerTest.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright (c) 2024, 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 Stable Heterogeneous Container methods - * @modules java.base/jdk.internal.lang.stable - * @compile --enable-preview -source ${jdk.version} StableHeterogeneousContainerTest.java - * @run junit/othervm --enable-preview StableHeterogeneousContainerTest - */ - -import jdk.internal.lang.stable.StableValueFactories; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; - -import java.util.Arrays; -import java.util.Comparator; -import java.util.EnumSet; -import java.util.LinkedHashSet; -import java.util.NoSuchElementException; -import java.util.Set; -import java.util.TreeSet; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import static org.junit.jupiter.api.Assertions.*; - -final class StableHeterogeneousContainerTest { - - enum Value { - SHORT((short) Short.BYTES), - INTEGER(Integer.BYTES); - - final Object value; - - Value(Object value) { - this.value = value; - } - - Object value() { - return value; - } - - T valueAs(Class type) { - return type.cast(value); - } - - Class clazz() { - return value.getClass(); - } - - } - - @Test - void factoryInvariants() { - assertThrows(NullPointerException.class, () -> StableValueFactories.ofHeterogeneousContainer(null)); - } - - @ParameterizedTest - @MethodSource("nonEmptySets") - void trySet(Set inputs) { - var container = StableValueFactories.ofHeterogeneousContainer(classes(inputs)); - assertTrue(container.tryPut(Integer.class, Value.INTEGER.valueAs(Integer.class))); - assertFalse(container.tryPut(Integer.class, Value.INTEGER.valueAs(Integer.class))); - assertEquals(Value.INTEGER.value(), container.get(Integer.class)); - var iae = assertThrows(IllegalArgumentException.class, () -> container.tryPut(Long.class, 8L)); - assertEquals("No such type: " + Long.class, iae.getMessage()); - var npe = assertThrows(NullPointerException.class, () -> container.tryPut(Short.class, null)); - assertEquals("The provided instance for '" + Short.class + "' was null", npe.getMessage()); - } - - @ParameterizedTest - @MethodSource("nonEmptySets") - void getOrElseSet(Set inputs) { - var container = StableValueFactories.ofHeterogeneousContainer(classes(inputs)); - assertEquals(Value.INTEGER.value(), container.getOrElseSet(Integer.class, Value.INTEGER::valueAs)); - assertEquals(Value.INTEGER.value(), container.getOrElseSet(Integer.class, Value.INTEGER::valueAs)); - var iae = assertThrows(IllegalArgumentException.class, () -> container.getOrElseSet(Long.class, _ -> 8L)); - assertEquals("No such type: " + Long.class, iae.getMessage()); - var npe = assertThrows(NullPointerException.class, () -> container.getOrElseSet(Short.class, _ -> null)); - assertEquals("The constructor for `" + Short.class + "` returned null", npe.getMessage()); - } - - @ParameterizedTest - @MethodSource("nonEmptySets") - void get(Set inputs) { - var container = StableValueFactories.ofHeterogeneousContainer(classes(inputs)); - assertTrue(container.tryPut(Integer.class, Value.INTEGER.valueAs(Integer.class))); - assertEquals(Value.INTEGER.value(), container.get(Integer.class)); - assertNull(container.get(Value.SHORT.clazz())); - } - - @ParameterizedTest - @MethodSource("nonEmptySets") - void getOrElseThrow(Set inputs) { - var container = StableValueFactories.ofHeterogeneousContainer(classes(inputs)); - assertTrue(container.tryPut(Integer.class, Value.INTEGER.valueAs(Integer.class))); - assertEquals(Value.INTEGER.value(), container.getOrElseThrow(Integer.class)); - var e = assertThrows(NoSuchElementException.class , () -> container.getOrElseThrow(Value.SHORT.clazz())); - assertEquals("The type `" + Short.class + "` is know but there is no instance associated with it", e.getMessage()); - } - - @ParameterizedTest - @MethodSource("nonEmptySets") - void toString(Set inputs) { - var container = StableValueFactories.ofHeterogeneousContainer(classes(inputs)); - assertTrue(container.toString().contains("class java.lang.Integer=StableValue.unset")); - container.tryPut(Integer.class, Value.INTEGER.valueAs(Integer.class)); - assertTrue(container.toString().contains("class java.lang.Integer=StableValue[" + Value.INTEGER.value() + "]"), container.toString()); - } - - private static Set> classes(Set values) { - return values.stream() - .map(Value::clazz) - .collect(Collectors.toUnmodifiableSet()); - } - - private static Stream> nonEmptySets() { - return Stream.of( - Set.of(Value.SHORT, Value.INTEGER), - linkedHashSet(Value.SHORT, Value.INTEGER), - treeSet(Value.SHORT, Value.INTEGER), - EnumSet.of(Value.SHORT, Value.INTEGER) - ); - } - - private static Stream> emptySets() { - return Stream.of( - Set.of(), - linkedHashSet(), - treeSet(), - EnumSet.noneOf(Value.class) - ); - } - - private static Stream> allSets() { - return Stream.concat( - nonEmptySets(), - emptySets() - ); - } - - static Set treeSet(Value... values) { - return populate(new TreeSet<>(Comparator.comparingInt(Value::ordinal).reversed()),values); - } - - static Set linkedHashSet(Value... values) { - return populate(new LinkedHashSet<>(), values); - } - - static Set populate(Set set, Value... values) { - set.addAll(Arrays.asList(values)); - return set; - } - -} diff --git a/test/micro/org/openjdk/bench/java/lang/stable/CustomStableBiFunctionBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/CustomStableBiFunctionBenchmark.java deleted file mode 100644 index 848f873f6ab3a..0000000000000 --- a/test/micro/org/openjdk/bench/java/lang/stable/CustomStableBiFunctionBenchmark.java +++ /dev/null @@ -1,327 +0,0 @@ -/* - * Copyright (c) 2024, 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 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.OperationsPerInvocation; -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.Map; -import java.util.Objects; -import java.util.Set; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.LockSupport; -import java.util.function.BiFunction; -import java.util.function.Function; -import java.util.stream.Collectors; - -import org.openjdk.bench.java.lang.stable.CustomStableFunctions.Pair; - -import static org.openjdk.bench.java.lang.stable.CustomStableFunctions.cachingBiFunction; - -/** - * Benchmark measuring custom stable value types - */ -@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, jvmArgsAppend = { - "--enable-preview" -/* , "-XX:+UnlockDiagnosticVMOptions", - "-XX:+PrintInlining"*/ - // Prevent the use of uncommon traps -/* , "-XX:PerMethodTrapLimit=0"*/ -}) -@Threads(Threads.MAX) // Benchmark under contention -@OperationsPerInvocation(2) -public class CustomStableBiFunctionBenchmark { - - private static final Set> SET = Set.of( - new Pair<>(1, 4), - new Pair<>(1, 6), - new Pair<>(1, 9), - new Pair<>(42, 13), - new Pair<>(13, 42), - new Pair<>(99, 1) - ); - - private static final Integer VALUE = 42; - private static final Integer VALUE2 = 13; - private static final BiFunction ORIGINAL = (l, r) -> { - // Slow down the original - LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1)); - return l * 2 + r; - }; - - private static final BiFunction FUNCTION = new StableBiFunction<>(SET, ORIGINAL); - private static final BiFunction FUNCTION2 = new StableBiFunction<>(SET, ORIGINAL); -// private static final BiFunction FUNCTION = cachingBiFunction(SET, ORIGINAL); -// private static final BiFunction FUNCTION2 = cachingBiFunction(SET, ORIGINAL); - - private final BiFunction function = new StableBiFunction<>(SET, ORIGINAL); //cachingBiFunction(SET, ORIGINAL);; - private final BiFunction function2 = new StableBiFunction<>(SET, ORIGINAL); //cachingBiFunction(SET, ORIGINAL);; - -// private final BiFunction function = cachingBiFunction(SET, ORIGINAL);; -// private final BiFunction function2 = cachingBiFunction(SET, ORIGINAL);; - - private static final StableValue STABLE_VALUE = StableValue.of(); - private static final StableValue STABLE_VALUE2 = StableValue.of(); - - static { - STABLE_VALUE.trySet(ORIGINAL.apply(VALUE, VALUE2)); - STABLE_VALUE2.trySet(ORIGINAL.apply(VALUE2, VALUE)); - } - - @Benchmark - public int function() { - return function.apply(VALUE, VALUE2) + function2.apply(VALUE2, VALUE); - } - - @Benchmark - public int staticFunction() { - return FUNCTION.apply(VALUE, VALUE2) + FUNCTION2.apply(VALUE2, VALUE); - } - - @Benchmark - public int staticStableValue() { - return STABLE_VALUE.orElseThrow() + STABLE_VALUE2.orElseThrow(); - } - - - //Benchmark Mode Cnt Score Error Units - //CustomCachingBiFunctionBenchmark.function avgt 10 572.735 ? 27.583 ns/op - //CustomCachingBiFunctionBenchmark.staticFunction avgt 10 489.379 ? 81.296 ns/op - //CustomCachingBiFunctionBenchmark.staticStableValue avgt 10 0.387 ? 0.062 ns/op - - // Pair seams to not work that well... - static BiFunction cachingBiFunction2(Set> inputs, BiFunction original) { - final Function, R> delegate = StableValue.function(inputs, p -> original.apply(p.left(), p.right())); - return (T t, U u) -> delegate.apply(new Pair<>(t, u)); - } - - //Benchmark Mode Cnt Score Error Units - //CustomCachingBiFunctionBenchmark.function avgt 10 577.307 ? 57.591 ns/op - //CustomCachingBiFunctionBenchmark.staticFunction avgt 10 488.900 ? 126.583 ns/op - //CustomCachingBiFunctionBenchmark.staticStableValue avgt 10 0.343 ? 0.014 ns/op - record CachingBiFunction2(Function, R> delegate) implements BiFunction { - - public CachingBiFunction2(Set> inputs, BiFunction original) { - this(StableValue.function(inputs, (Pair p) -> original.apply(p.left(), p.right()))); - } - - @Override - public R apply(T t, U u) { - return delegate.apply(new Pair<>(t, u)); - } - } - - //Benchmark Mode Cnt Score Error Units - //CustomCachingBiFunctionBenchmark.function avgt 10 471.650 ? 119.590 ns/op - //CustomCachingBiFunctionBenchmark.staticFunction avgt 10 560.667 ? 178.996 ns/op - //CustomCachingBiFunctionBenchmark.staticStableValue avgt 10 0.428 ? 0.070 ns/op - - // Crucially, use Map.copyOf to get an immutable Map - record CachingBiFunction( - Map, StableValue> delegate, - BiFunction original) implements BiFunction { - - public CachingBiFunction(Set> inputs, BiFunction original) { - this(Map.copyOf(inputs.stream() - .collect(Collectors.toMap(Function.identity(), _ -> StableValue.of()))), - original - ); - } - - @Override - public R apply(T t, U u) { - final StableValue stable = delegate.get(new Pair<>(t, u)); - if (stable == null) { - throw new IllegalArgumentException(t.toString()); - - } - if (stable.isSet()) { - return stable.orElseThrow(); - } - synchronized (this) { - if (stable.isSet()) { - return stable.orElseThrow(); - } - final R r = original.apply(t, u); - stable.setOrThrow(r); - return r; - } - } - } - - //Benchmark Mode Cnt Score Error Units - //CustomCachingBiFunctionBenchmark.function avgt 10 8.438 ? 0.683 ns/op - //CustomCachingBiFunctionBenchmark.staticFunction avgt 10 8.312 ? 0.394 ns/op - //CustomCachingBiFunctionBenchmark.staticStableValue avgt 10 0.352 ? 0.021 ns/op - // Map-in-map - record CachingBiFunction3( - Map>> delegate, - BiFunction original) implements BiFunction { - - public CachingBiFunction3(Set> inputs, BiFunction original) { - this(delegate(inputs), original); - } - - static Map>> delegate(Set> inputs) { - Map>> map = inputs.stream() - .collect(Collectors.groupingBy(Pair::left, - Collectors.groupingBy(Pair::right, - Collectors.mapping((Function, ? extends StableValue>) _ -> StableValue.of(), - Collectors.reducing(StableValue.of(), _ -> StableValue.of(), (StableValue a, StableValue b) -> a))))); - - @SuppressWarnings("unchecked") - Map>> copy = Map.ofEntries(map.entrySet().stream() - .map(e -> Map.entry(e.getKey(), Map.copyOf(e.getValue()))) - .toArray(Map.Entry[]::new)); - - return copy; - } - - @Override - public R apply(T t, U u) { - final StableValue stable; - try { - stable = Objects.requireNonNull(delegate.get(t).get(u)); - } catch (NullPointerException _) { - throw new IllegalArgumentException(t.toString() + ", " + u.toString()); - } - if (stable.isSet()) { - return stable.orElseThrow(); - } - synchronized (this) { - if (stable.isSet()) { - return stable.orElseThrow(); - } - final R r = original.apply(t, u); - stable.setOrThrow(r); - return r; - } - } - } - -/* -This seams like a good solution: - -Benchmark Mode Cnt Score Error Units -CustomStableBiFunctionBenchmark.function avgt 10 6.460 ? 0.163 ns/op -CustomStableBiFunctionBenchmark.staticFunction avgt 10 0.361 ? 0.015 ns/op -CustomStableBiFunctionBenchmark.staticStableValue avgt 10 0.348 ? 0.053 ns/op - - */ - - record StableBiFunction( - Map>> delegate, - BiFunction original) implements BiFunction { - - public StableBiFunction(Set> inputs, BiFunction original) { - this(delegate(inputs), original); - } - - static Map>> delegate(Set> inputs) { - return Map.copyOf(inputs.stream() - .collect(Collectors.groupingBy(Pair::left, - Collectors.toUnmodifiableMap(Pair::right, - _ -> StableValue.of())))); - } - - @Override - public R apply(T t, U u) { - try { - return Objects.requireNonNull(delegate.get(t).get(u)) - .orElseSet(() -> original.apply(t, u)); - } catch (NullPointerException _) { - throw new IllegalArgumentException(t.toString() + ", " + u.toString()); - } - } - } - - - //Benchmark Mode Cnt Score Error Units - //CustomCachingBiFunctionBenchmark.function avgt 10 8.123 ? 0.113 ns/op - //CustomCachingBiFunctionBenchmark.staticFunction avgt 10 8.407 ? 0.559 ns/op - //CustomCachingBiFunctionBenchmark.staticStableValue avgt 10 0.354 ? 0.019 ns/op - - // CachingFunction-in-map - record CachingBiFunction4( - Map> delegate) implements BiFunction { - - public CachingBiFunction4(Set> inputs, BiFunction original) { - this(delegate(inputs, original)); - } - - static Map> delegate(Set> inputs, - BiFunction original) { - Map>> map = inputs.stream() - .collect(Collectors.groupingBy(Pair::left, - Collectors.groupingBy(Pair::right, - Collectors.mapping((Function, ? extends StableValue>) _ -> StableValue.of(), - Collectors.reducing(StableValue.of(), _ -> StableValue.of(), (StableValue a, StableValue b) -> b))))); - - @SuppressWarnings("unchecked") - Map> copy = Map.ofEntries(map.entrySet().stream() - .map(e -> Map.entry(e.getKey(), StableValue.function( e.getValue().keySet(), (U u) -> original.apply(e.getKey(), u)))) - .toArray(Map.Entry[]::new)); - - return copy; - } - - @Override - public R apply(T t, U u) { - return delegate.get(t) - .apply(u); - - - // This prevents constant folding -----V - -/* Function function = delegate.get(t); - if (function == null) { - throw new IllegalArgumentException(t.toString() + ", " + u.toString()); - } - return function.apply(u);*/ - -/* - try { - return delegate.get(t) - .apply(u); - } catch (NullPointerException _) { - throw new IllegalArgumentException(t.toString() + ", " + u.toString()); - }*/ - } - } - -} diff --git a/test/micro/org/openjdk/bench/java/lang/stable/CustomStableFunctions.java b/test/micro/org/openjdk/bench/java/lang/stable/CustomStableFunctions.java deleted file mode 100644 index dfe7dfc1e31aa..0000000000000 --- a/test/micro/org/openjdk/bench/java/lang/stable/CustomStableFunctions.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (c) 2024, 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 java.util.Map; -import java.util.Set; -import java.util.function.BiFunction; -import java.util.function.BinaryOperator; -import java.util.function.Function; -import java.util.function.IntSupplier; -import java.util.function.Predicate; -import java.util.function.Supplier; -import java.util.function.UnaryOperator; -import java.util.stream.Collectors; - -final class CustomStableFunctions { - - private CustomStableFunctions() {} - - record Pair(L left, R right){} - - static Predicate cachingPredicate(Set inputs, - Predicate original) { - - final Function delegate = StableValue.function(inputs, original::test); - return delegate::apply; - } - - /* - Benchmark Mode Cnt Score Error Units - CustomStableBiFunctionBenchmark.function avgt 10 6.970 ? 0.121 ns/op - CustomStableBiFunctionBenchmark.staticFunction avgt 10 0.339 ? 0.010 ns/op - CustomStableBiFunctionBenchmark.staticStableValue avgt 10 0.337 ? 0.004 ns/op - */ - - - static BiFunction cachingBiFunction(Set> inputs, - BiFunction original) { - - final Map> tToUs = inputs.stream() - .collect(Collectors.groupingBy(Pair::left, - Collectors.mapping(Pair::right, Collectors.toSet()))); - - // Collectors.toUnmodifiableMap() is crucial! - final Map> map = tToUs.entrySet().stream() - .map(e -> Map.entry(e.getKey(), StableValue.function(e.getValue(), (U u) -> original.apply(e.getKey(), u)))) - .collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue)); - - return (T t, U u) -> { - final Function function = map.get(t); - if (function != null) { - try { - return function.apply(u); - } catch (IllegalArgumentException iae) { - // The original function might throw - throw new IllegalArgumentException(t.toString() + ", " + u.toString(), iae); - } - } - throw new IllegalArgumentException(t.toString() + ", " + u.toString()); - }; - } - - static BinaryOperator cachingBinaryOperator(Set> inputs, - BinaryOperator original) { - - final BiFunction biFunction = cachingBiFunction(inputs, original); - return biFunction::apply; - } - - static UnaryOperator cachingUnaryOperator(Set inputs, - UnaryOperator original) { - - final Function function = StableValue.function(inputs, original); - return function::apply; - - } - - static IntSupplier cachingIntSupplier(IntSupplier original) { - final Supplier delegate = StableValue.supplier(original::getAsInt); - return delegate::get; - } - -} diff --git a/test/micro/org/openjdk/bench/java/lang/stable/CustomStablePredicateBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/CustomStablePredicateBenchmark.java deleted file mode 100644 index 98ae4d5facc33..0000000000000 --- a/test/micro/org/openjdk/bench/java/lang/stable/CustomStablePredicateBenchmark.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright (c) 2024, 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 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.security.spec.ECField; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.LockSupport; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.stream.Collectors; -import java.util.stream.IntStream; - -import static org.openjdk.bench.java.lang.stable.CustomStableFunctions.cachingPredicate; - -/** - * Benchmark measuring custom stable value types - */ -@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, jvmArgsAppend = { - "--enable-preview" - // Prevent the use of uncommon traps -// ,"-XX:PerMethodTrapLimit=0" -}) -@Threads(Threads.MAX) // Benchmark under contention -public class CustomStablePredicateBenchmark { - - private static final Set SET = IntStream.range(0, 64).boxed().collect(Collectors.toSet()); - private static final Predicate EVEN = i -> { - // Slow down the original - LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1)); - return i % 2 == 0; - }; - - private static final Integer VALUE = 42; - - private static final Predicate PREDICATE = cachingPredicate(SET, EVEN); - private final Predicate predicate = cachingPredicate(SET, EVEN); - - private static final Function FUNCTION_PREDICATE = StableValue.function(SET, EVEN::test); - private final Function functionPredicate = StableValue.function(SET, EVEN::test); - - - private static final Predicate WRAPPED_PREDICATE = FUNCTION_PREDICATE::apply; - private final Predicate wrappedPredicate = FUNCTION_PREDICATE::apply; - - @Benchmark - public boolean predicate() { - return predicate.test(VALUE); - } - - @Benchmark - public boolean staticPredicate() { - return PREDICATE.test(VALUE); - } - - @Benchmark - public boolean functionPredicate() { - return functionPredicate.apply(VALUE); - } - - @Benchmark - public boolean functionStaticPredicate() { - return FUNCTION_PREDICATE.apply(VALUE); - } - - @Benchmark - public boolean wrappedPredicate() { - return wrappedPredicate.test(VALUE); - } - - @Benchmark - public boolean wrappedStaticPredicate() { - return WRAPPED_PREDICATE.test(VALUE); - } -/* - Benchmark Mode Cnt Score Error Units - CustomStablePredicateBenchmark.functionPredicate avgt 10 4.223 ? 0.242 ns/op - CustomStablePredicateBenchmark.functionStaticPredicate avgt 10 0.699 ? 0.041 ns/op - CustomStablePredicateBenchmark.predicate avgt 10 5.077 ? 0.330 ns/op - CustomStablePredicateBenchmark.staticPredicate avgt 10 0.715 ? 0.062 ns/op - CustomStablePredicateBenchmark.wrappedPredicate avgt 10 5.229 ? 0.451 ns/op - CustomStablePredicateBenchmark.wrappedStaticPredicate avgt 10 0.776 ? 0.112 ns/op - */ - - // This is not constant foldable - record StablePredicate(Map> delegate, - Predicate original) implements Predicate { - - public StablePredicate(Set inputs, Predicate original) { - this(inputs.stream() - .collect(Collectors.toUnmodifiableMap(Function.identity(), - _ -> StableValue.of())), - original - ); - } - - @Override - public boolean test(T t) { - final StableValue stable = delegate.get(t); - if (stable == null) { - throw new IllegalArgumentException(t.toString()); - - } - if (stable.isSet()) { - return stable.orElseThrow(); - } - synchronized (stable) { - if (stable.isSet()) { - return stable.orElseThrow(); - } - final boolean r = original.test(t); - stable.setOrThrow(r); - return r; - } - } - } - -} From aff88465d74c8e95618c09327a722d51a7d50f74 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 10 Mar 2025 19:14:05 +0100 Subject: [PATCH 190/327] Update JEP number --- .../share/classes/jdk/internal/javac/PreviewFeature.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java b/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java index a4dada8f1c784..ae072c6f6554a 100644 --- a/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java +++ b/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java @@ -81,7 +81,7 @@ public enum Feature { @JEP(number=478, title="Key Derivation Function API", status="Preview") KEY_DERIVATION, LANGUAGE_MODEL, - @JEP(number = 8330465, title = "Stable Values", status = "Preview") + @JEP(number = 502, title = "Stable Values", status = "Preview") STABLE_VALUES, /** * A key for testing. From f92ca110c158ca4867e21d998f15c7ac1896b0fe Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 10 Mar 2025 19:29:19 +0100 Subject: [PATCH 191/327] Fix copyright issues --- .../classes/jdk/internal/access/JavaUtilCollectionAccess.java | 2 +- .../share/classes/jdk/internal/javac/PreviewFeature.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/java.base/share/classes/jdk/internal/access/JavaUtilCollectionAccess.java b/src/java.base/share/classes/jdk/internal/access/JavaUtilCollectionAccess.java index ca0cc2798f4a0..e2d97d067765e 100644 --- a/src/java.base/share/classes/jdk/internal/access/JavaUtilCollectionAccess.java +++ b/src/java.base/share/classes/jdk/internal/access/JavaUtilCollectionAccess.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2024 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, 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 diff --git a/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java b/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java index ae072c6f6554a..e5e8a6a65efcd 100644 --- a/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java +++ b/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 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 From 3e7d7499d45a70f9590b66a2fffe578c1eea999d Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 10 Mar 2025 19:37:35 +0100 Subject: [PATCH 192/327] Revert change --- src/hotspot/share/classfile/vmSymbols.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hotspot/share/classfile/vmSymbols.hpp b/src/hotspot/share/classfile/vmSymbols.hpp index f4215a08ec859..f6ffc0c26a11b 100644 --- a/src/hotspot/share/classfile/vmSymbols.hpp +++ b/src/hotspot/share/classfile/vmSymbols.hpp @@ -739,7 +739,7 @@ class SerializeClosure; template(toFileURL_name, "toFileURL") \ template(toFileURL_signature, "(Ljava/lang/String;)Ljava/net/URL;") \ \ - /* Thread.dump_to_file jcmd */ \ + /* jcmd Thread.dump_to_file */ \ template(jdk_internal_vm_ThreadDumper, "jdk/internal/vm/ThreadDumper") \ template(dumpThreads_name, "dumpThreads") \ template(dumpThreadsToJson_name, "dumpThreadsToJson") \ From 16c50ab6b9c26026a0f3ab16de2a60820fee2cb5 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 11 Mar 2025 09:07:52 +0100 Subject: [PATCH 193/327] Address some comments in the PR --- src/hotspot/share/ci/ciField.cpp | 3 +-- src/hotspot/share/classfile/vmSymbols.hpp | 1 - src/hotspot/share/runtime/fieldDescriptor.cpp | 2 +- src/jdk.unsupported/share/classes/sun/misc/Unsafe.java | 8 ++++---- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/hotspot/share/ci/ciField.cpp b/src/hotspot/share/ci/ciField.cpp index a516832e96b50..2f3ccf349e26a 100644 --- a/src/hotspot/share/ci/ciField.cpp +++ b/src/hotspot/share/ci/ciField.cpp @@ -251,8 +251,7 @@ static bool trust_final_non_static_fields(ciInstanceKlass* holder) { } static bool trust_final_non_static_fields_of_type(Symbol* signature) { - return signature == vmSymbols::java_lang_StableValue_signature() || - signature == vmSymbols::java_lang_StableValue_array_signature(); + return signature == vmSymbols::java_lang_StableValue_signature(); } void ciField::initialize_from(fieldDescriptor* fd) { diff --git a/src/hotspot/share/classfile/vmSymbols.hpp b/src/hotspot/share/classfile/vmSymbols.hpp index f6ffc0c26a11b..270c54bcbf8bc 100644 --- a/src/hotspot/share/classfile/vmSymbols.hpp +++ b/src/hotspot/share/classfile/vmSymbols.hpp @@ -746,7 +746,6 @@ class SerializeClosure; \ /* Stable Values */ \ template(java_lang_StableValue_signature, "Ljava/lang/StableValue;") \ - template(java_lang_StableValue_array_signature, "[Ljava/lang/StableValue;") \ \ /* jcmd Thread.vthread_scheduler and Thread.vthread_pollers */ \ template(jdk_internal_vm_JcmdVThreadCommands, "jdk/internal/vm/JcmdVThreadCommands") \ diff --git a/src/hotspot/share/runtime/fieldDescriptor.cpp b/src/hotspot/share/runtime/fieldDescriptor.cpp index 6ed666588324c..a3796497a257f 100644 --- a/src/hotspot/share/runtime/fieldDescriptor.cpp +++ b/src/hotspot/share/runtime/fieldDescriptor.cpp @@ -44,7 +44,7 @@ Symbol* fieldDescriptor::generic_signature() const { bool fieldDescriptor::is_trusted_final() const { InstanceKlass* ik = field_holder(); return is_final() && (is_static() || ik->is_hidden() || ik->is_record() || - signature() == vmSymbols::java_lang_StableValue_signature() || signature() == vmSymbols::java_lang_StableValue_array_signature()); + signature() == vmSymbols::java_lang_StableValue_signature()); } AnnotationArray* fieldDescriptor::annotations() const { diff --git a/src/jdk.unsupported/share/classes/sun/misc/Unsafe.java b/src/jdk.unsupported/share/classes/sun/misc/Unsafe.java index dd37ba0fab264..b2612d9db1c3f 100644 --- a/src/jdk.unsupported/share/classes/sun/misc/Unsafe.java +++ b/src/jdk.unsupported/share/classes/sun/misc/Unsafe.java @@ -895,7 +895,7 @@ public long objectFieldOffset(Field f) { if (f == null) { throw new NullPointerException(); } - assertNotTrusted(f); + ensureNotTrusted(f); beforeMemoryAccess(); return theInternalUnsafe.objectFieldOffset(f); } @@ -929,7 +929,7 @@ public long staticFieldOffset(Field f) { if (f == null) { throw new NullPointerException(); } - assertNotTrusted(f); + ensureNotTrusted(f); beforeMemoryAccess(); return theInternalUnsafe.staticFieldOffset(f); } @@ -955,7 +955,7 @@ public Object staticFieldBase(Field f) { if (f == null) { throw new NullPointerException(); } - assertNotTrusted(f); + ensureNotTrusted(f); beforeMemoryAccess(); return theInternalUnsafe.staticFieldBase(f); } @@ -980,7 +980,7 @@ public int arrayBaseOffset(Class arrayClass) { } @ForceInline - private static void assertNotTrusted(Field f) { + private static void ensureNotTrusted(Field f) { Class declaringClass = f.getDeclaringClass(); if (declaringClass.isHidden()) { throw new UnsupportedOperationException("can't get base address on a hidden class: " + f); From 44c5219b3404c741fbfaa811047d90973835f511 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 11 Mar 2025 09:42:51 +0100 Subject: [PATCH 194/327] Fix members in StableEnumFunction --- .../lang/stable/StableEnumFunction.java | 42 +++++++++++++------ .../lang/StableValue/StableFunctionTest.java | 20 +++++---- .../StableValue/TrustedFieldTypeTest.java | 6 +-- 3 files changed, 45 insertions(+), 23 deletions(-) diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java index eab53db45ea22..2274d4d3830d4 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java @@ -25,11 +25,14 @@ package jdk.internal.lang.stable; +import jdk.internal.util.ImmutableBitSetPredicate; import jdk.internal.vm.annotation.ForceInline; import jdk.internal.vm.annotation.Stable; +import java.util.BitSet; import java.util.Set; import java.util.function.Function; +import java.util.function.IntPredicate; import java.util.function.Supplier; /** @@ -46,11 +49,15 @@ */ record StableEnumFunction, R>(Class enumType, int firstOrdinal, + IntPredicate member, @Stable StableValueImpl[] delegates, Function original) implements Function { @ForceInline @Override public R apply(E value) { + if (!member.test(value.ordinal())) { + throw new IllegalArgumentException("Input not allowed: " + value); + } final int index = value.ordinal() - firstOrdinal; try { return delegates[index] @@ -80,16 +87,22 @@ private String renderElements() { final StringBuilder sb = new StringBuilder(); sb.append("{"); boolean first = true; - int ordinal = firstOrdinal; final E[] enumElements = enumType.getEnumConstants(); - for (int i = 0; i < delegates.length; i++) { - if (first) { first = false; } else { sb.append(", "); }; - final Object value = delegates[i].wrappedValue(); - sb.append(enumElements[ordinal++]).append('='); - if (value == this) { - sb.append("(this StableEnumFunction)"); - } else { - sb.append(StableValueImpl.renderWrapped(value)); + int ordinal = firstOrdinal; + for (int i = 0; i < delegates.length; i++, ordinal++) { + if (member.test(ordinal)) { + if (first) { + first = false; + } else { + sb.append(", "); + } + final Object value = delegates[i].wrappedValue(); + sb.append(enumElements[ordinal]).append('='); + if (value == this) { + sb.append("(this StableEnumFunction)"); + } else { + sb.append(StableValueImpl.renderWrapped(value)); + } } } sb.append("}"); @@ -99,16 +112,21 @@ private String renderElements() { @SuppressWarnings("unchecked") static , R> Function of(Set inputs, Function original) { + final BitSet bitSet = new BitSet(inputs.size()); // The input set is not empty int min = Integer.MAX_VALUE; int max = Integer.MIN_VALUE; for (T t : inputs) { - min = Math.min(min, ((E) t).ordinal()); - max = Math.max(max, ((E) t).ordinal()); + final int ordinal = ((E) t).ordinal(); + min = Math.min(min, ordinal); + max = Math.max(max, ordinal); + bitSet.set(ordinal); } + final int size = max - min + 1; final Class enumType = (Class)inputs.iterator().next().getClass(); - return (Function) new StableEnumFunction(enumType, min, StableValueFactories.array(size), (Function) original); + final IntPredicate member = ImmutableBitSetPredicate.of(bitSet); + return (Function) new StableEnumFunction(enumType, min, member, StableValueFactories.array(size), (Function) original); } } diff --git a/test/jdk/java/lang/StableValue/StableFunctionTest.java b/test/jdk/java/lang/StableValue/StableFunctionTest.java index d7f68af0952cc..93c2e9caa7789 100644 --- a/test/jdk/java/lang/StableValue/StableFunctionTest.java +++ b/test/jdk/java/lang/StableValue/StableFunctionTest.java @@ -49,11 +49,13 @@ enum Value { // Zero is here so that we have enums with ordinals before the first one // actually used in input sets (i.e. ZERO is not in the input set) ZERO(0), + ILLEGAL_BEFORE(-1), // Valid values THIRTEEN(13), + ILLEGAL_BETWEEN(-2), FORTY_TWO(42), // Illegal values (not in the input set) - ILLEGAL(-1); + ILLEGAL_AFTER(-3); final int intValue; @@ -93,12 +95,16 @@ void basic(Set inputs, Function mapper) { assertTrue(cached.toString().startsWith(cached.getClass().getSimpleName() + "[values={")); // Key order is unspecified assertTrue(cached.toString().contains(Value.THIRTEEN + "=.unset")); - assertTrue(cached.toString().contains(Value.FORTY_TWO + "=[" + mapper.apply(Value.FORTY_TWO) + "]")); + assertTrue(cached.toString().contains(Value.FORTY_TWO + "=[" + mapper.apply(Value.FORTY_TWO) + "]"), cached.toString()); assertTrue(cached.toString().endsWith(", original=" + cif + "]")); // One between the values and one just before "original" - assertEquals(2L, cached.toString().chars().filter(ch -> ch == ',').count()); - var x = assertThrows(IllegalArgumentException.class, () -> cached.apply(Value.ILLEGAL)); - assertTrue(x.getMessage().contains("ILLEGAL")); + assertEquals(2L, cached.toString().chars().filter(ch -> ch == ',').count(), cached.toString()); + var x0 = assertThrows(IllegalArgumentException.class, () -> cached.apply(Value.ILLEGAL_BEFORE)); + assertTrue(x0.getMessage().contains("ILLEGAL")); + var x1 = assertThrows(IllegalArgumentException.class, () -> cached.apply(Value.ILLEGAL_BETWEEN)); + assertTrue(x1.getMessage().contains("ILLEGAL")); + var x2 = assertThrows(IllegalArgumentException.class, () -> cached.apply(Value.ILLEGAL_AFTER)); + assertTrue(x2.getMessage().contains("ILLEGAL")); } @ParameterizedTest @@ -126,7 +132,7 @@ void exception(Set inputs) { assertTrue(cached.toString().startsWith(cached.getClass().getSimpleName() + "[values={")); // Key order is unspecified assertTrue(cached.toString().contains(Value.THIRTEEN + "=.unset")); - assertTrue(cached.toString().contains(Value.FORTY_TWO + "=.unset")); + assertTrue(cached.toString().contains(Value.FORTY_TWO + "=.unset"), cached.toString()); assertTrue(cached.toString().endsWith(", original=" + cif + "]")); } @@ -138,7 +144,7 @@ void circular(Set inputs) { ref.set(cached); cached.apply(Value.FORTY_TWO); String toString = cached.toString(); - assertTrue(toString.contains("(this " + cached.getClass().getSimpleName() + ")")); + assertTrue(toString.contains("(this " + cached.getClass().getSimpleName() + ")"), toString); assertDoesNotThrow(cached::hashCode); assertDoesNotThrow((() -> cached.equals(cached))); } diff --git a/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java b/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java index eea6cb6c352c1..f4dc38ac4a743 100644 --- a/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java +++ b/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java @@ -78,10 +78,8 @@ final class ArrayHolder { ArrayHolder arrayHolder = new ArrayHolder(); // We should be able to read the StableValue array read = arrayField.get(arrayHolder); - // We should NOT be able to write to the StableValue array - assertThrows(IllegalAccessException.class, () -> - arrayField.set(arrayHolder, new StableValue[1]) - ); + // We should be able to write to the StableValue array + assertDoesNotThrow(() -> arrayField.set(arrayHolder, new StableValue[1])); } @SuppressWarnings("removal") From a05717d8da8f804003cfb8d6d25b8a5b6cecd473 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 12 Mar 2025 09:28:49 +0100 Subject: [PATCH 195/327] Fix JavaDoc issues --- src/java.base/share/classes/java/lang/StableValue.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index a0364c1e9c6a4..f3eeb2a315e82 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -76,7 +76,7 @@ * if (!logger.isSet()) { * logger.trySet(Logger.create(Component.class)); * } - * return logger.orThrow(); + * return logger.orElseThrow(); * } * * public void process() { @@ -124,7 +124,7 @@ * Furthermore, {@code orElseSet()} guarantees that the supplier provided is * evaluated only once, even when {@code logger.orElseSet()} is invoked concurrently. * This property is crucial as evaluation of the supplier may have side effects, - * e.g., the call above to {@code Logger.getLogger()} may result in storage resources + * e.g., the call above to {@code Logger.create()} may result in storage resources * being prepared. * *

    Stable Functions

    @@ -420,7 +420,7 @@ public sealed interface StableValue * * {@snippet lang=java: * if (stable.isSet()) { - * return stable.get(); + * return stable.orElseThrow(); * } else { * T newValue = supplier.get(); * stable.setOrThrow(newValue); @@ -540,6 +540,7 @@ static Supplier supplier(Supplier original) { * @param size the size of the allowed inputs in {@code [0, size)} * @param original IntFunction used to compute cached values * @param the type of results delivered by the returned IntFunction + * @throws IllegalArgumentException if the provided {@code size} is negative. */ static IntFunction intFunction(int size, IntFunction original) { @@ -608,6 +609,7 @@ static Function function(Set inputs, * @param mapper to invoke whenever an element is first accessed * (may return {@code null}) * @param the type of elements in the returned list + * @throws IllegalArgumentException if the provided {@code size} is negative. */ static List list(int size, IntFunction mapper) { From 5a59f7dab7d714a5cd1d1b6e4167a8ae09d26bfd Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Thu, 13 Mar 2025 10:57:37 +0100 Subject: [PATCH 196/327] Simplify handling of sentinel, wrap, and unwrap --- .../jdk/internal/lang/stable/StableValueImpl.java | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) 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 850864740c36e..1bfd9d548c580 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 @@ -165,21 +165,15 @@ private boolean wrapAndCas(Object value) { // Wraps `null` values into a sentinel value @ForceInline - private static T wrap(T t) { - return (t == null) ? nullSentinel() : t; + private static Object wrap(Object t) { + return (t == null) ? NULL_SENTINEL : t; } // Unwraps null sentinel values into `null` @SuppressWarnings("unchecked") @ForceInline private static T unwrap(Object t) { - return t != nullSentinel() ? (T) t : null; - } - - @SuppressWarnings("unchecked") - @ForceInline - private static T nullSentinel() { - return (T) NULL_SENTINEL; + return t != NULL_SENTINEL ? (T) t : null; } // Factory From 2633edd03c9a1c3d185df81071934967584a6378 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Thu, 13 Mar 2025 11:00:52 +0100 Subject: [PATCH 197/327] Add missing null check --- .../share/classes/jdk/internal/lang/stable/StableValueImpl.java | 2 ++ 1 file changed, 2 insertions(+) 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 1bfd9d548c580..85874bccf4edc 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 @@ -31,6 +31,7 @@ import jdk.internal.vm.annotation.Stable; import java.util.NoSuchElementException; +import java.util.Objects; import java.util.function.Supplier; /** @@ -115,6 +116,7 @@ public boolean isSet() { @ForceInline @Override public T orElseSet(Supplier supplier) { + Objects.requireNonNull(supplier); final Object t = value; return (t == null) ? computeIfUnsetSlowPath(supplier) : unwrap(t); } From 09ca44e6d00c13748fa979d24ce9d6d0faca8741 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Thu, 13 Mar 2025 12:12:07 +0100 Subject: [PATCH 198/327] Use acquire semantics for reading rather than volatile semantics --- .../lang/stable/StableEnumFunction.java | 2 +- .../internal/lang/stable/StableFunction.java | 2 +- .../lang/stable/StableIntFunction.java | 2 +- .../internal/lang/stable/StableSupplier.java | 2 +- .../internal/lang/stable/StableValueImpl.java | 22 +++++++++---------- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java index 2274d4d3830d4..0a33b94f29e3b 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java @@ -96,7 +96,7 @@ private String renderElements() { } else { sb.append(", "); } - final Object value = delegates[i].wrappedValue(); + final Object value = delegates[i].wrappedValueAcquire(); sb.append(enumElements[ordinal]).append('='); if (value == this) { sb.append("(this StableEnumFunction)"); diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableFunction.java index e96e99a7d25bc..35b5146acec5d 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableFunction.java @@ -80,7 +80,7 @@ private String renderMappings() { boolean first = true; for (var e:values.entrySet()) { if (first) { first = false; } else { sb.append(", "); }; - final Object value = e.getValue().wrappedValue(); + final Object value = e.getValue().wrappedValueAcquire(); sb.append(e.getKey()).append('='); if (value == this) { sb.append("(this StableFunction)"); diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableIntFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableIntFunction.java index 7cb9cdb6359b4..a519d4dca869f 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableIntFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableIntFunction.java @@ -83,7 +83,7 @@ private String renderElements() { boolean first = true; for (int i = 0; i < delegates.length; i++) { if (first) { first = false; } else { sb.append(", "); }; - final Object value = delegates[i].wrappedValue(); + final Object value = delegates[i].wrappedValueAcquire(); if (value == this) { sb.append("(this StableIntFunction)"); } else { diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableSupplier.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableSupplier.java index 74d4992da2dcb..b9cf415f859a8 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableSupplier.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableSupplier.java @@ -57,7 +57,7 @@ public boolean equals(Object obj) { @Override public String toString() { - final Object t = delegate.wrappedValue(); + final Object t = delegate.wrappedValueAcquire(); return "StableSupplier[value=" + (t == this ? "(this StableSupplier)" : StableValueImpl.renderWrapped(t)) + ", original=" + original + "]"; } 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 85874bccf4edc..7f8f4159fdd41 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 @@ -54,7 +54,7 @@ public final class StableValueImpl implements StableValue { // Generally, fields annotated with `@Stable` are accessed by the JVM using special // memory semantics rules (see `parse.hpp` and `parse(1|2|3).cpp`). // - // This field is used directly and via Unsafe using explicit memory semantics. + // This field is used directly and reflectively via Unsafe using explicit memory semantics. // // | Value | Meaning | // | -------------- | ------------ | @@ -63,7 +63,7 @@ public final class StableValueImpl implements StableValue { // | other | Set(other) | // @Stable - private volatile Object value; + private Object value; // Only allow creation via the factory `StableValueImpl::newInstance` private StableValueImpl() {} @@ -71,7 +71,7 @@ private StableValueImpl() {} @ForceInline @Override public boolean trySet(T value) { - if (this.value != null) { + if (wrappedValueAcquire() != null) { return false; } // Mutual exclusion is required here as `computeIfUnset` might also @@ -93,7 +93,7 @@ public void setOrThrow(T value) { @ForceInline @Override public T orElseThrow() { - final Object t = value; + final Object t = wrappedValueAcquire(); if (t == null) { throw new NoSuchElementException("No underlying data set"); } @@ -103,27 +103,27 @@ public T orElseThrow() { @ForceInline @Override public T orElse(T other) { - final Object t = value; + final Object t = wrappedValueAcquire(); return (t == null) ? other : unwrap(t); } @ForceInline @Override public boolean isSet() { - return value != null; + return wrappedValueAcquire() != null; } @ForceInline @Override public T orElseSet(Supplier supplier) { Objects.requireNonNull(supplier); - final Object t = value; + final Object t = wrappedValueAcquire(); return (t == null) ? computeIfUnsetSlowPath(supplier) : unwrap(t); } @DontInline private synchronized T computeIfUnsetSlowPath(Supplier supplier) { - final Object t = value; + final Object t = value; // Plain semantics suffice here if (t == null) { final T newValue = supplier.get(); // The mutex is reentrant so we need to check if the value was actually set. @@ -136,7 +136,7 @@ private synchronized T computeIfUnsetSlowPath(Supplier supplier) { @Override public String toString() { - final Object t = value; + final Object t = wrappedValueAcquire(); return t == this ? "(this StableValue)" : "StableValue" + renderWrapped(t); @@ -145,8 +145,8 @@ public String toString() { // Internal methods shared with other internal classes @ForceInline - public Object wrappedValue() { - return value; + public Object wrappedValueAcquire() { + return UNSAFE.getReferenceAcquire(this, UNDERLYING_DATA_OFFSET); } static String renderWrapped(Object t) { From 1cd1cdb28c347dca6c171a3cfea971d7136771b5 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Thu, 13 Mar 2025 13:39:59 +0100 Subject: [PATCH 199/327] Rework reenterant logic --- .../share/classes/java/lang/StableValue.java | 19 ++++++++++ .../internal/lang/stable/StableValueImpl.java | 36 +++++++++++++------ .../java/lang/StableValue/StableListTest.java | 2 +- .../lang/StableValue/StableValueTest.java | 20 +++++++++++ 4 files changed, 66 insertions(+), 11 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index f3eeb2a315e82..6631ec2b1319b 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -433,6 +433,8 @@ public sealed interface StableValue * {@code supplier} throws an exception. * * @param supplier to be used for computing the content, if not previously set + * @throws IllegalStateException if the provided {@code supplier} recursively + * attempts to set this stable value. */ T orElseSet(Supplier supplier); @@ -508,6 +510,9 @@ static StableValue of(T content) { * to the initial caller and no content is recorded. *

    * The returned supplier is not {@link Serializable}. + *

    + * If the provided {@code original} supplier recursively calls the returned + * supplier, an {@linkplain IllegalStateException} will be thrown. * * @param original supplier used to compute a cached value * @param the type of results supplied by the returned supplier @@ -536,6 +541,10 @@ static Supplier supplier(Supplier original) { * to the initial caller and no content is recorded. *

    * The returned int function is not {@link Serializable}. + *

    + * If the provided {@code original} int function recursively calls the returned + * int function for the same index, an {@linkplain IllegalStateException} will + * be thrown. * * @param size the size of the allowed inputs in {@code [0, size)} * @param original IntFunction used to compute cached values @@ -569,6 +578,10 @@ static IntFunction intFunction(int size, * the initial caller and no content is recorded. *

    * The returned function is not {@link Serializable}. + *

    + * If the provided {@code original} function recursively calls the returned + * function for the same input, an {@linkplain IllegalStateException} will + * be thrown. * * @param inputs the set of allowed input values * @param original Function used to compute cached values @@ -604,6 +617,9 @@ static Function function(Set inputs, * The returned list is not {@link Serializable} and, as it is unmodifiable, does * not implement the {@linkplain Collection##optional-operation optional operations} * in the {@linkplain List} interface. + *

    + * If the provided {@code mapper} recursively calls the returned list for the + * same index, an {@linkplain IllegalStateException} will be thrown. * * @param size the size of the returned list * @param mapper to invoke whenever an element is first accessed @@ -639,6 +655,9 @@ static List list(int size, * The returned map is not {@link Serializable} and, as it is unmodifiable, does * not implement the {@linkplain Collection##optional-operations optional operations} * in the {@linkplain Map} interface. + *

    + * If the provided {@code mapper} recursively calls the returned map for + * the same key, an {@linkplain IllegalStateException} will be thrown. * * @param keys the keys in the returned map * @param mapper to invoke whenever an associated value is first accessed 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 7f8f4159fdd41..b8aed7d013f78 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 @@ -74,10 +74,14 @@ public boolean trySet(T value) { if (wrappedValueAcquire() != null) { return false; } + // Prevent reentry via computeIfUnsetSlowPath + if (Thread.holdsLock(this)) { + throw new IllegalStateException("Recursing supplier detected"); + } // Mutual exclusion is required here as `computeIfUnset` might also // attempt to modify the `wrappedValue` synchronized (this) { - return wrapAndCas(value); + return wrapAndSet(value); } } @@ -122,14 +126,20 @@ public T orElseSet(Supplier supplier) { } @DontInline - private synchronized T computeIfUnsetSlowPath(Supplier supplier) { - final Object t = value; // Plain semantics suffice here - if (t == null) { - final T newValue = supplier.get(); - // The mutex is reentrant so we need to check if the value was actually set. - return wrapAndCas(newValue) ? newValue : orElseThrow(); + private T computeIfUnsetSlowPath(Supplier supplier) { + // Prevent reentry + if (Thread.holdsLock(this)) { + throw new IllegalStateException("Recursing supplier detected: " + supplier); + } + synchronized (this) { + final Object t = value; // Plain semantics suffice here + if (t == null) { + final T newValue = supplier.get(); + // The mutex is reentrant so we need to check if the value was actually set. + return wrapAndSet(newValue) ? newValue : orElseThrow(); + } + return unwrap(t); } - return unwrap(t); } // The methods equals() and hashCode() should be based on identity (defaults from Object) @@ -156,9 +166,15 @@ static String renderWrapped(Object t) { // Private methods @ForceInline - private boolean wrapAndCas(Object value) { + private boolean wrapAndSet(Object newValue) { + assert Thread.holdsLock(this); // This upholds the invariant, a `@Stable` field is written to at most once - return UNSAFE.compareAndSetReference(this, UNDERLYING_DATA_OFFSET, null, wrap(value)); + // We know we hold the monitor here so plain semantic is enough + if (value != null) { + return false; + } + UNSAFE.putReferenceRelease(this, UNDERLYING_DATA_OFFSET, wrap(newValue)); + return true; } // Used to indicate a holder value is `null` (see field `value` below) diff --git a/test/jdk/java/lang/StableValue/StableListTest.java b/test/jdk/java/lang/StableValue/StableListTest.java index 67e7d4c075b92..292bd2628f235 100644 --- a/test/jdk/java/lang/StableValue/StableListTest.java +++ b/test/jdk/java/lang/StableValue/StableListTest.java @@ -215,7 +215,7 @@ void recursiveCall() { AtomicReference> ref = new AtomicReference<>(); var lazy = StableValue.list(SIZE, i -> ref.get().apply(i)); ref.set(lazy::get); - assertThrows(StackOverflowError.class, () -> lazy.get(INDEX)); + assertThrows(IllegalStateException.class, () -> lazy.get(INDEX)); } // Immutability diff --git a/test/jdk/java/lang/StableValue/StableValueTest.java b/test/jdk/java/lang/StableValue/StableValueTest.java index 081a9b7d7497d..d60408ad861af 100644 --- a/test/jdk/java/lang/StableValue/StableValueTest.java +++ b/test/jdk/java/lang/StableValue/StableValueTest.java @@ -34,8 +34,10 @@ import java.util.NoSuchElementException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.LockSupport; import java.util.function.BiPredicate; +import java.util.function.IntFunction; import java.util.stream.IntStream; import static org.junit.jupiter.api.Assertions.*; @@ -168,6 +170,24 @@ void toStringCircular() { assertDoesNotThrow((() -> stable.equals(stable))); } + @Test + void recursiveCall() { + StableValue stable = StableValue.of(); + AtomicReference> ref = new AtomicReference<>(stable); + assertThrows(IllegalStateException.class, () -> + stable.orElseSet(() -> { + ref.get().trySet(1); + return 1; + }) + ); + assertThrows(IllegalStateException.class, () -> + stable.orElseSet(() -> { + ref.get().orElseSet(() -> 1); + return 1; + }) + ); + } + private static final BiPredicate, Integer> TRY_SET = StableValue::trySet; private static final BiPredicate, Integer> SET_OR_THROW = (s, i) -> { try { From 8f6d6bc053e8b63e5c22356c16c108833ec15d06 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Thu, 13 Mar 2025 13:49:03 +0100 Subject: [PATCH 200/327] Rename method and fix comment --- .../jdk/internal/lang/stable/StableValueImpl.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) 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 b8aed7d013f78..7abbf567877e5 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 @@ -74,7 +74,7 @@ public boolean trySet(T value) { if (wrappedValueAcquire() != null) { return false; } - // Prevent reentry via computeIfUnsetSlowPath + // Prevent reentry via orElseSet if (Thread.holdsLock(this)) { throw new IllegalStateException("Recursing supplier detected"); } @@ -122,11 +122,11 @@ public boolean isSet() { public T orElseSet(Supplier supplier) { Objects.requireNonNull(supplier); final Object t = wrappedValueAcquire(); - return (t == null) ? computeIfUnsetSlowPath(supplier) : unwrap(t); + return (t == null) ? orElseSetSlowPath(supplier) : unwrap(t); } @DontInline - private T computeIfUnsetSlowPath(Supplier supplier) { + private T orElseSetSlowPath(Supplier supplier) { // Prevent reentry if (Thread.holdsLock(this)) { throw new IllegalStateException("Recursing supplier detected: " + supplier); @@ -135,8 +135,9 @@ private T computeIfUnsetSlowPath(Supplier supplier) { final Object t = value; // Plain semantics suffice here if (t == null) { final T newValue = supplier.get(); - // The mutex is reentrant so we need to check if the value was actually set. - return wrapAndSet(newValue) ? newValue : orElseThrow(); + // The mutex is not reentrant so we know newValue should be returned + wrapAndSet(newValue); + return newValue; } return unwrap(t); } From c648ea2bcb37d54e16594189455094786c2f3e5d Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Thu, 13 Mar 2025 13:58:12 +0100 Subject: [PATCH 201/327] Rename field --- .../lang/stable/StableEnumFunction.java | 2 +- .../internal/lang/stable/StableFunction.java | 2 +- .../lang/stable/StableIntFunction.java | 2 +- .../internal/lang/stable/StableSupplier.java | 2 +- .../internal/lang/stable/StableValueImpl.java | 30 +++++++++---------- .../StableValue/TrustedFieldTypeTest.java | 7 +++-- 6 files changed, 23 insertions(+), 22 deletions(-) diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java index 0a33b94f29e3b..b82f73ddd62db 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java @@ -96,7 +96,7 @@ private String renderElements() { } else { sb.append(", "); } - final Object value = delegates[i].wrappedValueAcquire(); + final Object value = delegates[i].wrappedContentAcquire(); sb.append(enumElements[ordinal]).append('='); if (value == this) { sb.append("(this StableEnumFunction)"); diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableFunction.java index 35b5146acec5d..725719a848e8e 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableFunction.java @@ -80,7 +80,7 @@ private String renderMappings() { boolean first = true; for (var e:values.entrySet()) { if (first) { first = false; } else { sb.append(", "); }; - final Object value = e.getValue().wrappedValueAcquire(); + final Object value = e.getValue().wrappedContentAcquire(); sb.append(e.getKey()).append('='); if (value == this) { sb.append("(this StableFunction)"); diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableIntFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableIntFunction.java index a519d4dca869f..a35307b2a706e 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableIntFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableIntFunction.java @@ -83,7 +83,7 @@ private String renderElements() { boolean first = true; for (int i = 0; i < delegates.length; i++) { if (first) { first = false; } else { sb.append(", "); }; - final Object value = delegates[i].wrappedValueAcquire(); + final Object value = delegates[i].wrappedContentAcquire(); if (value == this) { sb.append("(this StableIntFunction)"); } else { diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableSupplier.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableSupplier.java index b9cf415f859a8..fd675dec6329f 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableSupplier.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableSupplier.java @@ -57,7 +57,7 @@ public boolean equals(Object obj) { @Override public String toString() { - final Object t = delegate.wrappedValueAcquire(); + final Object t = delegate.wrappedContentAcquire(); return "StableSupplier[value=" + (t == this ? "(this StableSupplier)" : StableValueImpl.renderWrapped(t)) + ", original=" + original + "]"; } 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 7abbf567877e5..3bd2c491c7a1c 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 @@ -40,7 +40,7 @@ * @implNote This implementation can be used early in the boot sequence as it does not * rely on reflection, MethodHandles, Streams etc. * - * @param type of the underlying data + * @param type of the content */ public final class StableValueImpl implements StableValue { @@ -48,8 +48,8 @@ public final class StableValueImpl implements StableValue { static final Unsafe UNSAFE = Unsafe.getUnsafe(); // Unsafe offsets for direct field access - private static final long UNDERLYING_DATA_OFFSET = - UNSAFE.objectFieldOffset(StableValueImpl.class, "value"); + private static final long CONTENT_OFFSET = + UNSAFE.objectFieldOffset(StableValueImpl.class, "content"); // Generally, fields annotated with `@Stable` are accessed by the JVM using special // memory semantics rules (see `parse.hpp` and `parse(1|2|3).cpp`). @@ -63,7 +63,7 @@ public final class StableValueImpl implements StableValue { // | other | Set(other) | // @Stable - private Object value; + private Object content; // Only allow creation via the factory `StableValueImpl::newInstance` private StableValueImpl() {} @@ -71,7 +71,7 @@ private StableValueImpl() {} @ForceInline @Override public boolean trySet(T value) { - if (wrappedValueAcquire() != null) { + if (wrappedContentAcquire() != null) { return false; } // Prevent reentry via orElseSet @@ -97,7 +97,7 @@ public void setOrThrow(T value) { @ForceInline @Override public T orElseThrow() { - final Object t = wrappedValueAcquire(); + final Object t = wrappedContentAcquire(); if (t == null) { throw new NoSuchElementException("No underlying data set"); } @@ -107,21 +107,21 @@ public T orElseThrow() { @ForceInline @Override public T orElse(T other) { - final Object t = wrappedValueAcquire(); + final Object t = wrappedContentAcquire(); return (t == null) ? other : unwrap(t); } @ForceInline @Override public boolean isSet() { - return wrappedValueAcquire() != null; + return wrappedContentAcquire() != null; } @ForceInline @Override public T orElseSet(Supplier supplier) { Objects.requireNonNull(supplier); - final Object t = wrappedValueAcquire(); + final Object t = wrappedContentAcquire(); return (t == null) ? orElseSetSlowPath(supplier) : unwrap(t); } @@ -132,7 +132,7 @@ private T orElseSetSlowPath(Supplier supplier) { throw new IllegalStateException("Recursing supplier detected: " + supplier); } synchronized (this) { - final Object t = value; // Plain semantics suffice here + final Object t = content; // Plain semantics suffice here if (t == null) { final T newValue = supplier.get(); // The mutex is not reentrant so we know newValue should be returned @@ -147,7 +147,7 @@ private T orElseSetSlowPath(Supplier supplier) { @Override public String toString() { - final Object t = wrappedValueAcquire(); + final Object t = wrappedContentAcquire(); return t == this ? "(this StableValue)" : "StableValue" + renderWrapped(t); @@ -156,8 +156,8 @@ public String toString() { // Internal methods shared with other internal classes @ForceInline - public Object wrappedValueAcquire() { - return UNSAFE.getReferenceAcquire(this, UNDERLYING_DATA_OFFSET); + public Object wrappedContentAcquire() { + return UNSAFE.getReferenceAcquire(this, CONTENT_OFFSET); } static String renderWrapped(Object t) { @@ -171,10 +171,10 @@ private boolean wrapAndSet(Object newValue) { assert Thread.holdsLock(this); // This upholds the invariant, a `@Stable` field is written to at most once // We know we hold the monitor here so plain semantic is enough - if (value != null) { + if (content != null) { return false; } - UNSAFE.putReferenceRelease(this, UNDERLYING_DATA_OFFSET, wrap(newValue)); + UNSAFE.putReferenceRelease(this, CONTENT_OFFSET, wrap(newValue)); return true; } diff --git a/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java b/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java index f4dc38ac4a743..925d2ddb9b561 100644 --- a/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java +++ b/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java @@ -53,6 +53,7 @@ final class HolderNonFinal { private StableValue value = StableValue.of(); } final class ArrayHolder { + @SuppressWarnings("unchecked") private final StableValue[] array = (StableValue[]) new StableValue[]{}; } @@ -161,7 +162,7 @@ void updateStableValueContentVia_j_i_m_Unsafe() { stableValue.trySet(42); jdk.internal.misc.Unsafe unsafe = Unsafe.getUnsafe(); - long offset = unsafe.objectFieldOffset(stableValue.getClass(), "value"); + long offset = unsafe.objectFieldOffset(stableValue.getClass(), "content"); assertTrue(offset > 0); // Unfortunately, it is possible to update the underlying data via jdk.internal.misc.Unsafe @@ -175,7 +176,7 @@ void updateStableValueContentViaSetAccessible() throws NoSuchFieldException, Ill if (Boolean.getBoolean("opens")) { // Unfortunately, add-opens allows direct access to the `value` field - Field field = StableValueImpl.class.getDeclaredField("value"); + Field field = StableValueImpl.class.getDeclaredField("content"); field.setAccessible(true); StableValue stableValue = StableValue.of(); @@ -191,7 +192,7 @@ void updateStableValueContentViaSetAccessible() throws NoSuchFieldException, Ill // }); assertEquals(13, stableValue.orElseThrow()); } else { - Field field = StableValueImpl.class.getDeclaredField("value"); + Field field = StableValueImpl.class.getDeclaredField("content"); assertThrows(InaccessibleObjectException.class, ()-> field.setAccessible(true)); } } From 2fe5b0f821f6591ec28d65d65fe086e0ec09233a Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Thu, 13 Mar 2025 14:49:07 +0100 Subject: [PATCH 202/327] Clean up exception messages and fix comments --- .../jdk/internal/lang/stable/StableValueImpl.java | 10 +++++----- test/jdk/java/lang/StableValue/StableValueTest.java | 7 +++++-- 2 files changed, 10 insertions(+), 7 deletions(-) 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 3bd2c491c7a1c..e5d1ec7f7aea0 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 @@ -74,11 +74,11 @@ public boolean trySet(T value) { if (wrappedContentAcquire() != null) { return false; } - // Prevent reentry via orElseSet + // Prevent reentry via an orElseSet(supplier) if (Thread.holdsLock(this)) { throw new IllegalStateException("Recursing supplier detected"); } - // Mutual exclusion is required here as `computeIfUnset` might also + // Mutual exclusion is required here as `orElseSet` might also // attempt to modify the `wrappedValue` synchronized (this) { return wrapAndSet(value); @@ -89,8 +89,8 @@ public boolean trySet(T value) { @Override public void setOrThrow(T value) { if (!trySet(value)) { - throw new IllegalStateException("Cannot set the underlying data to " + value + - " because the underlying data is already set: " + this); + throw new IllegalStateException("Cannot set the content to " + value + + " because the content is already set: " + orElseThrow()); } } @@ -99,7 +99,7 @@ public void setOrThrow(T value) { public T orElseThrow() { final Object t = wrappedContentAcquire(); if (t == null) { - throw new NoSuchElementException("No underlying data set"); + throw new NoSuchElementException("No content set"); } return unwrap(t); } diff --git a/test/jdk/java/lang/StableValue/StableValueTest.java b/test/jdk/java/lang/StableValue/StableValueTest.java index d60408ad861af..22501e57b71c3 100644 --- a/test/jdk/java/lang/StableValue/StableValueTest.java +++ b/test/jdk/java/lang/StableValue/StableValueTest.java @@ -62,7 +62,10 @@ void preSet() { assertEquals(VALUE, stable.orElse(VALUE2)); assertEquals(VALUE, stable.orElseSet(() -> VALUE2)); assertFalse(stable.trySet(VALUE2)); - assertThrows(IllegalStateException.class, () -> stable.setOrThrow(VALUE2)); + var e = assertThrows(IllegalStateException.class, () -> stable.setOrThrow(VALUE2)); + assertEquals( + "Cannot set the content to " + VALUE2 + " because the content is already set: " + VALUE, + e.getMessage()); } void trySet(Integer initial) { @@ -87,7 +90,7 @@ void orElse() { void orElseThrow() { StableValue stable = StableValue.of(); var e = assertThrows(NoSuchElementException.class, stable::orElseThrow); - assertEquals("No underlying data set", e.getMessage()); + assertEquals("No content set", e.getMessage()); stable.trySet(VALUE); assertEquals(VALUE, stable.orElseThrow()); } From 60cf209ce29b762046c7b39b52182b5d9c940bbe Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 19 Mar 2025 15:43:28 -0700 Subject: [PATCH 203/327] Create separate reentry prevention method and add tests --- .../internal/lang/stable/StableValueImpl.java | 15 ++++++++------- .../java/lang/StableValue/StableValueTest.java | 16 ++++++++++++++++ 2 files changed, 24 insertions(+), 7 deletions(-) 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 e5d1ec7f7aea0..6c349aaf51855 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 @@ -75,9 +75,7 @@ public boolean trySet(T value) { return false; } // Prevent reentry via an orElseSet(supplier) - if (Thread.holdsLock(this)) { - throw new IllegalStateException("Recursing supplier detected"); - } + preventReentry(); // Mutual exclusion is required here as `orElseSet` might also // attempt to modify the `wrappedValue` synchronized (this) { @@ -127,10 +125,7 @@ public T orElseSet(Supplier supplier) { @DontInline private T orElseSetSlowPath(Supplier supplier) { - // Prevent reentry - if (Thread.holdsLock(this)) { - throw new IllegalStateException("Recursing supplier detected: " + supplier); - } + preventReentry(); synchronized (this) { final Object t = content; // Plain semantics suffice here if (t == null) { @@ -166,6 +161,12 @@ static String renderWrapped(Object t) { // Private methods + private void preventReentry() { + if (Thread.holdsLock(this)) { + throw new IllegalStateException("Recursing supplier detected"); + } + } + @ForceInline private boolean wrapAndSet(Object newValue) { assert Thread.holdsLock(this); diff --git a/test/jdk/java/lang/StableValue/StableValueTest.java b/test/jdk/java/lang/StableValue/StableValueTest.java index 22501e57b71c3..2db72817ac7cf 100644 --- a/test/jdk/java/lang/StableValue/StableValueTest.java +++ b/test/jdk/java/lang/StableValue/StableValueTest.java @@ -77,6 +77,22 @@ void trySet(Integer initial) { assertEquals(initial, stable.orElseThrow()); } + @Test + void setOrThrowValue() { + StableValue stable = StableValue.of(); + stable.setOrThrow(VALUE); + var e = assertThrows(IllegalStateException.class, () -> stable.setOrThrow(VALUE2)); + assertEquals("Cannot set the content to " + VALUE2 + " because the content is already set: " + VALUE, e.getMessage()); + } + + @Test + void setOrThrowNull() { + StableValue stable = StableValue.of(); + stable.setOrThrow(null); + var e = assertThrows(IllegalStateException.class, () -> stable.setOrThrow(null)); + assertEquals("Cannot set the content to null because the content is already set: null", e.getMessage()); + } + @Test void orElse() { StableValue stable = StableValue.of(); From 4c0dadfba907c44452ff40931977aacbe424516a Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 19 Mar 2025 18:31:32 -0700 Subject: [PATCH 204/327] Fix comments on doc issues --- .../share/classes/java/lang/StableValue.java | 49 ++++++++----------- 1 file changed, 21 insertions(+), 28 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 6631ec2b1319b..cd701b524623f 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -42,7 +42,7 @@ import java.util.function.Supplier; /** - * A stable value is a shallowly immutable holder of deferred content. + * A stable value is a deferred holder of shallowly immutable content. *

    * A {@linkplain StableValue {@code StableValue}} can be created using the factory * method {@linkplain StableValue#of() {@code StableValue.of()}}. When created @@ -327,37 +327,30 @@ * {@linkplain StableValue#orElseSet(Supplier) orElseSet()} they too are * thread safe and guarantee at-most-once-per-input invocation. * - *

    Miscellaneous

    - * Except for a StableValue's content itself, an {@linkplain #orElse(Object) orElse(other)} - * parameter, and an {@linkplain #equals(Object) equals(obj)} parameter; all method - * parameters must be non-null or a {@link NullPointerException} will be thrown. - *

    - * Stable functions and collections are not {@link Serializable} as this would require - * {@linkplain #list(int, IntFunction) mappers} to be {@link Serializable} as well, - * which would introduce security vulnerabilities. - *

    - * As objects can be set via stable values but never removed, this can be a source - * of unintended memory leaks. A stable value's content is - * {@linkplain java.lang.ref##reachability strongly reachable}. Clients are advised that - * {@linkplain java.lang.ref##reachability reachable} stable values will hold their set - * content perpetually. - *

    - * A {@linkplain StableValue} that has a type parameter {@code T} that is an array - * type (of arbitrary rank) will only allow the JVM to treat the array reference - * as a stable value but not its components. Clients can instead use - * {@linkplain #list(int, IntFunction) a stable list} of arbitrary depth, which provides - * stable components. More generally, a stable value can hold other stable values of - * arbitrary depth and still provide transitive constantness. - * * @implSpec Implementing classes of {@linkplain StableValue} are free to synchronize on * {@code this} and consequently, care should be taken whenever * (directly or indirectly) synchronizing on a {@code StableValue}. Failure to * do this may lead to deadlock. Stable functions and collections on the * other hand are guaranteed not to synchronize on {@code this}. + * Except for a {@code StableValue}'s content itself, an {@linkplain #orElse(Object) orElse(other)} + * parameter, and an {@linkplain #equals(Object) equals(obj)} parameter; all + * method parameters must be non-null or a {@link NullPointerException} + * will be thrown. * * @implNote A {@linkplain StableValue} is mainly intended to be a non-public field in * a class and is usually neither exposed directly via accessors nor passed as * a method parameter. + * As objects can be set via stable values but never removed, this can be a source + * of unintended memory leaks. A stable value's content is + * {@linkplain java.lang.ref##reachability strongly reachable}. Clients are + * advised that {@linkplain java.lang.ref##reachability reachable} stable values + * will hold their set content perpetually. + * A {@linkplain StableValue} that has a type parameter {@code T} that is an array + * type (of arbitrary rank) will only allow the JVM to treat the array reference + * as a stable value but not its components. Clients can instead use + * {@linkplain #list(int, IntFunction) a stable list} of arbitrary depth, which + * provides stable components. More generally, a stable value can hold other + * stable values of arbitrary depth and still provide transitive constantness. * * @param type of the content * @@ -494,7 +487,7 @@ static StableValue of(T content) { } /** - * {@return a new unset stable supplier} + * {@return a new stable supplier} *

    * The returned {@linkplain Supplier supplier} is a caching supplier that records * the value of the provided {@code original} supplier upon being first accessed via @@ -523,7 +516,7 @@ static Supplier supplier(Supplier original) { } /** - * {@return a new unset stable int function} + * {@return a new stable int function} *

    * The returned {@link IntFunction int function} is a caching int function that, * for each allowed input, records the values of the provided {@code original} @@ -561,7 +554,7 @@ static IntFunction intFunction(int size, } /** - * {@return a new unset stable function} + * {@return a new stable function} *

    * The returned {@link Function function} is a caching function that, for each allowed * input in the given set of {@code inputs}, records the values of the provided @@ -596,7 +589,7 @@ static Function function(Set inputs, } /** - * {@return a new unset stable list with the provided {@code size}} + * {@return a new stable list with the provided {@code size}} *

    * The returned list is an {@linkplain Collection##unmodifiable unmodifiable} list * whose size is known at construction. The list's elements are computed via the @@ -637,7 +630,7 @@ static List list(int size, } /** - * {@return a new unset stable map with the provided {@code keys}} + * {@return a new stable map with the provided {@code keys}} *

    * The returned map is an {@linkplain Collection##unmodifiable unmodifiable} map whose * keys are known at construction. The map's values are computed via the provided From 42d4dcfa5ed310cb647cb1adbe208065ca5f9cf4 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 25 Mar 2025 08:47:54 -0700 Subject: [PATCH 205/327] Revamp toString() methods --- .../java/util/ImmutableCollections.java | 26 +++++++--- .../lang/stable/EmptyStableFunction.java | 3 +- .../lang/stable/StableEnumFunction.java | 29 +++-------- .../internal/lang/stable/StableFunction.java | 20 +------- .../lang/stable/StableIntFunction.java | 21 +------- .../internal/lang/stable/StableSupplier.java | 2 +- .../jdk/internal/lang/stable/StableUtil.java | 50 +++++++++++++++++++ .../internal/lang/stable/StableValueImpl.java | 6 ++- .../lang/StableValue/StableFunctionTest.java | 16 +++--- .../StableValue/StableIntFunctionTest.java | 8 +-- .../java/lang/StableValue/StableListTest.java | 8 ++- .../java/lang/StableValue/StableMapTest.java | 7 ++- .../lang/StableValue/StableSupplierTest.java | 9 ++-- .../lang/StableValue/StableValueTest.java | 7 +-- 14 files changed, 118 insertions(+), 94 deletions(-) create mode 100644 src/java.base/share/classes/jdk/internal/lang/stable/StableUtil.java diff --git a/src/java.base/share/classes/java/util/ImmutableCollections.java b/src/java.base/share/classes/java/util/ImmutableCollections.java index deec57ef3c5f2..e295bf807925f 100644 --- a/src/java.base/share/classes/java/util/ImmutableCollections.java +++ b/src/java.base/share/classes/java/util/ImmutableCollections.java @@ -42,6 +42,7 @@ import jdk.internal.access.JavaUtilCollectionAccess; import jdk.internal.access.SharedSecrets; +import jdk.internal.lang.stable.StableUtil; import jdk.internal.lang.stable.StableValueImpl; import jdk.internal.lang.stable.StableValueFactories; import jdk.internal.misc.CDS; @@ -774,22 +775,22 @@ static final class StableList extends AbstractImmutableList { @Stable private final IntFunction mapper; @Stable - private final StableValueImpl[] backing; + private final StableValueImpl[] delegates; StableList(int size, IntFunction mapper) { this.mapper = mapper; - this.backing = StableValueFactories.array(size); + this.delegates = StableValueFactories.array(size); } - @Override public boolean isEmpty() { return backing.length == 0;} - @Override public int size() { return backing.length; } + @Override public boolean isEmpty() { return delegates.length == 0;} + @Override public int size() { return delegates.length; } @Override public Object[] toArray() { return copyInto(new Object[size()]); } @ForceInline @Override public E get(int i) { try { - return backing[i] + return delegates[i] .orElseSet(new Supplier() { @Override public E get() { return mapper.apply(i); }}); } catch (ArrayIndexOutOfBoundsException aioobe) { @@ -800,7 +801,7 @@ public E get(int i) { @Override @SuppressWarnings("unchecked") public T[] toArray(T[] a) { - final int size = backing.length; + final int size = delegates.length; if (a.length < size) { // Make a new array of a's runtime type, but my contents: T[] n = (T[])Array.newInstance(a.getClass().getComponentType(), size); @@ -836,13 +837,18 @@ public int lastIndexOf(Object o) { @SuppressWarnings("unchecked") private T[] copyInto(Object[] a) { - final int len = backing.length; + final int len = delegates.length; for (int i = 0; i < len; i++) { a[i] = get(i); } return (T[]) a; } + @Override + public String toString() { + return StableUtil.renderElements(this, "StableList", delegates); + } + } // ---------- Set Implementations ---------- @@ -1537,6 +1543,12 @@ public void accept(Entry> inner) { } } } + + @Override + public String toString() { + return StableUtil.renderMappings(this, "StableMap", delegate.entrySet()); + } + } } diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/EmptyStableFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/EmptyStableFunction.java index 61585541b0e4d..ab2e9ec3c42c1 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/EmptyStableFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/EmptyStableFunction.java @@ -27,6 +27,7 @@ import jdk.internal.vm.annotation.ForceInline; +import java.util.Collections; import java.util.function.Function; /** @@ -59,7 +60,7 @@ public boolean equals(Object obj) { @Override public String toString() { - return "EmptyStableFunction[values={}, original=" + original + "]"; + return StableUtil.renderMappings(this, "StableFunction", Collections.emptyList()); } static Function of(Function original) { diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java index b82f73ddd62db..233869975537a 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java @@ -29,7 +29,12 @@ import jdk.internal.vm.annotation.ForceInline; import jdk.internal.vm.annotation.Stable; +import java.util.AbstractMap; +import java.util.ArrayList; import java.util.BitSet; +import java.util.Collection; +import java.util.List; +import java.util.Map; import java.util.Set; import java.util.function.Function; import java.util.function.IntPredicate; @@ -80,33 +85,15 @@ public boolean equals(Object obj) { @Override public String toString() { - return "StableEnumFunction[values=" + renderElements() + ", original=" + original + "]"; - } - - private String renderElements() { - final StringBuilder sb = new StringBuilder(); - sb.append("{"); - boolean first = true; + final Collection>> entries = new ArrayList<>(); final E[] enumElements = enumType.getEnumConstants(); int ordinal = firstOrdinal; for (int i = 0; i < delegates.length; i++, ordinal++) { if (member.test(ordinal)) { - if (first) { - first = false; - } else { - sb.append(", "); - } - final Object value = delegates[i].wrappedContentAcquire(); - sb.append(enumElements[ordinal]).append('='); - if (value == this) { - sb.append("(this StableEnumFunction)"); - } else { - sb.append(StableValueImpl.renderWrapped(value)); - } + entries.add(new AbstractMap.SimpleImmutableEntry<>(enumElements[ordinal], delegates[i])); } } - sb.append("}"); - return sb.toString(); + return StableUtil.renderMappings(this, "StableFunction", entries); } @SuppressWarnings("unchecked") diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableFunction.java index 725719a848e8e..036eeb5bac29a 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableFunction.java @@ -71,25 +71,7 @@ public boolean equals(Object obj) { @Override public String toString() { - return "StableFunction[values=" + renderMappings() + ", original=" + original + "]"; - } - - private String renderMappings() { - final StringBuilder sb = new StringBuilder(); - sb.append("{"); - boolean first = true; - for (var e:values.entrySet()) { - if (first) { first = false; } else { sb.append(", "); }; - final Object value = e.getValue().wrappedContentAcquire(); - sb.append(e.getKey()).append('='); - if (value == this) { - sb.append("(this StableFunction)"); - } else { - sb.append(StableValueImpl.renderWrapped(value)); - } - } - sb.append("}"); - return sb.toString(); + return StableUtil.renderMappings(this, "StableFunction", values.entrySet()); } static StableFunction of(Set inputs, diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableIntFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableIntFunction.java index a35307b2a706e..6fa7cd87892b6 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableIntFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableIntFunction.java @@ -72,26 +72,7 @@ public boolean equals(Object obj) { @Override public String toString() { - return "StableIntFunction[values=" + - renderElements() + - ", original=" + original + ']'; - } - - private String renderElements() { - final StringBuilder sb = new StringBuilder(); - sb.append("["); - boolean first = true; - for (int i = 0; i < delegates.length; i++) { - if (first) { first = false; } else { sb.append(", "); }; - final Object value = delegates[i].wrappedContentAcquire(); - if (value == this) { - sb.append("(this StableIntFunction)"); - } else { - sb.append(StableValueImpl.renderWrapped(value)); - } - } - sb.append("]"); - return sb.toString(); + return StableUtil.renderElements(this, "StableIntFunction", delegates); } static StableIntFunction of(int size, IntFunction original) { diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableSupplier.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableSupplier.java index fd675dec6329f..03e854f1b4f62 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableSupplier.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableSupplier.java @@ -58,7 +58,7 @@ public boolean equals(Object obj) { @Override public String toString() { final Object t = delegate.wrappedContentAcquire(); - return "StableSupplier[value=" + (t == this ? "(this StableSupplier)" : StableValueImpl.renderWrapped(t)) + ", original=" + original + "]"; + return t == this ? "(this StableSupplier)" : StableValueImpl.renderWrapped(t); } static StableSupplier of(Supplier original) { 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 new file mode 100644 index 0000000000000..2aa315731d7d9 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableUtil.java @@ -0,0 +1,50 @@ +package jdk.internal.lang.stable; + +import java.util.Collection; +import java.util.Map; + +public final class StableUtil { + + private StableUtil() {} + + public static String renderElements(Object self, + String selfName, + StableValueImpl[] delegates) { + final StringBuilder sb = new StringBuilder(); + sb.append("["); + boolean first = true; + for (int i = 0; i < delegates.length; i++) { + if (first) { first = false; } else { sb.append(", "); } + final Object value = delegates[i].wrappedContentAcquire(); + if (value == self) { + sb.append("(this ").append(selfName).append(")"); + } else { + sb.append(StableValueImpl.renderWrapped(value)); + } + } + sb.append("]"); + return sb.toString(); + } + + public static String renderMappings(Object self, + String selfName, + Iterable>> delegates) { + final StringBuilder sb = new StringBuilder(); + sb.append("{"); + boolean first = true; + for (var e : delegates) { + if (first) { first = false; } else { sb.append(", "); } + final Object value = e.getValue().wrappedContentAcquire(); + sb.append(e.getKey()).append('='); + if (value == self) { + sb.append("(this ").append(selfName).append(")"); + } else { + sb.append(StableValueImpl.renderWrapped(value)); + } + } + sb.append("}"); + return sb.toString(); + } + + +} 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 6c349aaf51855..e2c995a64b993 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 @@ -44,6 +44,8 @@ */ public final class StableValueImpl implements StableValue { + static final String UNSET_LABEL = ".unset"; + // Unsafe allows StableValue to be used early in the boot sequence static final Unsafe UNSAFE = Unsafe.getUnsafe(); @@ -145,7 +147,7 @@ public String toString() { final Object t = wrappedContentAcquire(); return t == this ? "(this StableValue)" - : "StableValue" + renderWrapped(t); + : renderWrapped(t); } // Internal methods shared with other internal classes @@ -156,7 +158,7 @@ public Object wrappedContentAcquire() { } static String renderWrapped(Object t) { - return (t == null) ? ".unset" : "[" + unwrap(t) + "]"; + return (t == null) ? UNSET_LABEL : Objects.toString(unwrap(t)); } // Private methods diff --git a/test/jdk/java/lang/StableValue/StableFunctionTest.java b/test/jdk/java/lang/StableValue/StableFunctionTest.java index 93c2e9caa7789..b02261190915a 100644 --- a/test/jdk/java/lang/StableValue/StableFunctionTest.java +++ b/test/jdk/java/lang/StableValue/StableFunctionTest.java @@ -92,13 +92,13 @@ void basic(Set inputs, Function mapper) { assertEquals(1, cif.cnt()); assertEquals(mapper.apply(Value.FORTY_TWO), cached.apply(Value.FORTY_TWO)); assertEquals(1, cif.cnt()); - assertTrue(cached.toString().startsWith(cached.getClass().getSimpleName() + "[values={")); + assertTrue(cached.toString().startsWith("{"), cached.toString()); // Key order is unspecified assertTrue(cached.toString().contains(Value.THIRTEEN + "=.unset")); - assertTrue(cached.toString().contains(Value.FORTY_TWO + "=[" + mapper.apply(Value.FORTY_TWO) + "]"), cached.toString()); - assertTrue(cached.toString().endsWith(", original=" + cif + "]")); - // One between the values and one just before "original" - assertEquals(2L, cached.toString().chars().filter(ch -> ch == ',').count(), cached.toString()); + assertTrue(cached.toString().contains(Value.FORTY_TWO + "=" + mapper.apply(Value.FORTY_TWO)), cached.toString()); + assertTrue(cached.toString().endsWith("}")); + // One between the values + assertEquals(1L, cached.toString().chars().filter(ch -> ch == ',').count(), cached.toString()); var x0 = assertThrows(IllegalArgumentException.class, () -> cached.apply(Value.ILLEGAL_BEFORE)); assertTrue(x0.getMessage().contains("ILLEGAL")); var x1 = assertThrows(IllegalArgumentException.class, () -> cached.apply(Value.ILLEGAL_BETWEEN)); @@ -129,11 +129,11 @@ void exception(Set inputs) { assertEquals(1, cif.cnt()); assertThrows(UnsupportedOperationException.class, () -> cached.apply(Value.FORTY_TWO)); assertEquals(2, cif.cnt()); - assertTrue(cached.toString().startsWith(cached.getClass().getSimpleName() + "[values={")); + assertTrue(cached.toString().startsWith("{")); // Key order is unspecified assertTrue(cached.toString().contains(Value.THIRTEEN + "=.unset")); assertTrue(cached.toString().contains(Value.FORTY_TWO + "=.unset"), cached.toString()); - assertTrue(cached.toString().endsWith(", original=" + cif + "]")); + assertTrue(cached.toString().endsWith("}")); } @ParameterizedTest @@ -144,7 +144,7 @@ void circular(Set inputs) { ref.set(cached); cached.apply(Value.FORTY_TWO); String toString = cached.toString(); - assertTrue(toString.contains("(this " + cached.getClass().getSimpleName() + ")"), toString); + assertTrue(toString.contains("(this StableFunction)"), toString); assertDoesNotThrow(cached::hashCode); assertDoesNotThrow((() -> cached.equals(cached))); } diff --git a/test/jdk/java/lang/StableValue/StableIntFunctionTest.java b/test/jdk/java/lang/StableValue/StableIntFunctionTest.java index a7c73b767979e..8ceb7adea3a94 100644 --- a/test/jdk/java/lang/StableValue/StableIntFunctionTest.java +++ b/test/jdk/java/lang/StableValue/StableIntFunctionTest.java @@ -54,12 +54,12 @@ void basic() { void basic(IntFunction mapper) { StableTestUtil.CountingIntFunction cif = new StableTestUtil.CountingIntFunction<>(mapper); var cached = StableValue.intFunction(SIZE, cif); - assertEquals("StableIntFunction[values=[.unset, .unset], original=" + cif + "]", cached.toString()); + assertEquals("[.unset, .unset]", cached.toString()); assertEquals(mapper.apply(1), cached.apply(1)); assertEquals(1, cif.cnt()); assertEquals(mapper.apply(1), cached.apply(1)); assertEquals(1, cif.cnt()); - assertEquals("StableIntFunction[values=[.unset, [" + mapper.apply(1) + "]], original=" + cif + "]", cached.toString()); + assertEquals("[.unset, " + mapper.apply(1) + "]", cached.toString()); assertThrows(IllegalArgumentException.class, () -> cached.apply(SIZE)); assertThrows(IllegalArgumentException.class, () -> cached.apply(-1)); assertThrows(IllegalArgumentException.class, () -> cached.apply(1_000_000)); @@ -75,7 +75,7 @@ void exception() { assertEquals(1, cif.cnt()); assertThrows(UnsupportedOperationException.class, () -> cached.apply(1)); assertEquals(2, cif.cnt()); - assertEquals("StableIntFunction[values=[.unset, .unset], original=" + cif + "]", cached.toString()); + assertEquals("[.unset, .unset]", cached.toString()); } @Test @@ -85,7 +85,7 @@ void circular() { ref.set(cached); cached.apply(0); String toString = cached.toString(); - assertTrue(toString.startsWith("StableIntFunction[values=[(this StableIntFunction), .unset], original=")); + assertEquals("[(this StableIntFunction), .unset]", toString); assertDoesNotThrow(cached::hashCode); assertDoesNotThrow((() -> cached.equals(cached))); } diff --git a/test/jdk/java/lang/StableValue/StableListTest.java b/test/jdk/java/lang/StableValue/StableListTest.java index 292bd2628f235..a0f8ab1e66cc6 100644 --- a/test/jdk/java/lang/StableValue/StableListTest.java +++ b/test/jdk/java/lang/StableValue/StableListTest.java @@ -151,8 +151,12 @@ void lastIndex() { @Test void toStringTest() { assertEquals("[]", newEmptyList().toString()); - assertEquals("[0, 1]", StableValue.list(2, IDENTITY).toString()); - assertEquals(newRegularList().toString(), newList().toString()); + var list = StableValue.list(2, IDENTITY); + assertEquals("[.unset, .unset]", list.toString()); + list.get(0); + assertEquals("[0, .unset]", list.toString()); + list.get(1); + assertEquals("[0, 1]", list.toString()); } @Test diff --git a/test/jdk/java/lang/StableValue/StableMapTest.java b/test/jdk/java/lang/StableValue/StableMapTest.java index c60dbaf987e4c..c72bccf5b0e3b 100644 --- a/test/jdk/java/lang/StableValue/StableMapTest.java +++ b/test/jdk/java/lang/StableValue/StableMapTest.java @@ -131,11 +131,14 @@ void forEach() { @Test void toStringTest() { assertEquals("{}", newEmptyMap().toString()); - assertEquals("{" + KEY + "=" + KEY + "}", StableValue.map(Set.of(KEY), IDENTITY).toString()); + var map = StableValue.map(Set.of(KEY), IDENTITY); + assertEquals("{" + KEY + "=.unset}", map.toString()); + map.get(KEY); + assertEquals("{" + KEY + "=" + KEY + "}", map.toString()); String actual = newMap().toString(); assertTrue(actual.startsWith("{")); for (int key:KEYS) { - assertTrue(actual.contains(key + "=" + key)); + assertTrue(actual.contains(key + "=.unset")); } assertTrue(actual.endsWith("}")); } diff --git a/test/jdk/java/lang/StableValue/StableSupplierTest.java b/test/jdk/java/lang/StableValue/StableSupplierTest.java index d6ed324f8bcd4..8f7a961120183 100644 --- a/test/jdk/java/lang/StableValue/StableSupplierTest.java +++ b/test/jdk/java/lang/StableValue/StableSupplierTest.java @@ -29,6 +29,7 @@ import org.junit.jupiter.api.Test; +import java.util.Objects; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; @@ -52,12 +53,12 @@ void basic() { void basic(Supplier supplier) { StableTestUtil.CountingSupplier cs = new StableTestUtil.CountingSupplier<>(supplier); var cached = StableValue.supplier(cs); - assertEquals("StableSupplier[value=.unset, original=" + cs + "]", cached.toString()); + assertEquals(".unset", cached.toString()); assertEquals(supplier.get(), cached.get()); assertEquals(1, cs.cnt()); assertEquals(supplier.get(), cached.get()); assertEquals(1, cs.cnt()); - assertEquals("StableSupplier[value=[" + supplier.get() + "], original=" + cs + "]", cached.toString()); + assertEquals(Objects.toString(supplier.get()), cached.toString()); } @Test @@ -70,7 +71,7 @@ void exception() { assertEquals(1, cs.cnt()); assertThrows(UnsupportedOperationException.class, cached::get); assertEquals(2, cs.cnt()); - assertEquals("StableSupplier[value=.unset, original=" + cs + "]", cached.toString()); + assertEquals(".unset", cached.toString()); } @Test @@ -80,7 +81,7 @@ void circular() { ref.set(cached); cached.get(); String toString = cached.toString(); - assertTrue(toString.startsWith("StableSupplier[value=(this StableSupplier), original=")); + assertTrue(toString.startsWith("(this StableSupplier)")); assertDoesNotThrow(cached::hashCode); } diff --git a/test/jdk/java/lang/StableValue/StableValueTest.java b/test/jdk/java/lang/StableValue/StableValueTest.java index 2db72817ac7cf..3b2a213f250f9 100644 --- a/test/jdk/java/lang/StableValue/StableValueTest.java +++ b/test/jdk/java/lang/StableValue/StableValueTest.java @@ -32,6 +32,7 @@ import java.util.BitSet; import java.util.List; import java.util.NoSuchElementException; +import java.util.Objects; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; @@ -162,21 +163,21 @@ void testEquals() { @Test void toStringUnset() { StableValue stable = StableValue.of(); - assertEquals("StableValue.unset", stable.toString()); + assertEquals(".unset", stable.toString()); } @Test void toStringNull() { StableValue stable = StableValue.of(); assertTrue(stable.trySet(null)); - assertEquals("StableValue[null]", stable.toString()); + assertEquals("null", stable.toString()); } @Test void toStringNonNull() { StableValue stable = StableValue.of(); assertTrue(stable.trySet(VALUE)); - assertEquals("StableValue[" + VALUE + "]", stable.toString()); + assertEquals(Objects.toString(VALUE), stable.toString()); } @Test From 69688848d235de53b431d9892a893ded334ce228 Mon Sep 17 00:00:00 2001 From: Per-Ake Minborg Date: Wed, 26 Mar 2025 08:56:43 -0700 Subject: [PATCH 206/327] Update src/java.base/share/classes/java/lang/StableValue.java Co-authored-by: Paul Sandoz --- src/java.base/share/classes/java/lang/StableValue.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index cd701b524623f..8c3380118ac11 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -76,7 +76,7 @@ * if (!logger.isSet()) { * logger.trySet(Logger.create(Component.class)); * } - * return logger.orElseThrow(); + * return logger.orElseThrow(); * } * * public void process() { From 3581485b20f0717c7d37530b47ca20a7f4a01d48 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 26 Mar 2025 09:04:19 -0700 Subject: [PATCH 207/327] Add partial equality test --- test/jdk/java/lang/StableValue/StableListTest.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/jdk/java/lang/StableValue/StableListTest.java b/test/jdk/java/lang/StableValue/StableListTest.java index a0f8ab1e66cc6..ec9c73ca44c87 100644 --- a/test/jdk/java/lang/StableValue/StableListTest.java +++ b/test/jdk/java/lang/StableValue/StableListTest.java @@ -174,6 +174,15 @@ void equalsTest() { assertFalse(newList().equals("A")); } + @Test + void equalsPartialEvaluationTest() { + var list = StableValue.list(2, IDENTITY); + assertFalse(list.equals(List.of(0))); + assertEquals("[0, .unset]", list.toString()); + assertTrue(list.equals(List.of(0, 1))); + assertEquals("[0, 1]", list.toString()); + } + @Test void iteratorTotal() { var iterator = newList().iterator(); From 8b4113f6313003f33c9ec9ac51fc0dd55d3b73de Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 26 Mar 2025 09:08:05 -0700 Subject: [PATCH 208/327] Remove snippet for orElseSet --- .../share/classes/java/lang/StableValue.java | 21 ++++--------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 8c3380118ac11..303ad75c6157b 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -407,23 +407,10 @@ public sealed interface StableValue * } *

    * When this method returns successfully, the content is always set. - * - * @implSpec The implementation logic is equivalent to the following steps for this - * {@code stable}: - * - * {@snippet lang=java: - * if (stable.isSet()) { - * return stable.orElseThrow(); - * } else { - * T newValue = supplier.get(); - * stable.setOrThrow(newValue); - * return newValue; - * } - * } - * Except it is thread-safe and will only return the same witness value - * regardless if invoked by several threads. Also, the provided {@code supplier} - * will only be invoked once even if invoked from several threads unless the - * {@code supplier} throws an exception. + *

    + * This method will always return the same witness value regardless if invoked by + * several threads. Also, the provided {@code supplier} will only be invoked once even + * if invoked from several threads unless the {@code supplier} throws an exception. * * @param supplier to be used for computing the content, if not previously set * @throws IllegalStateException if the provided {@code supplier} recursively From f90557b87791b6567308dbe4326ea3359459d87e Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 26 Mar 2025 10:23:34 -0700 Subject: [PATCH 209/327] Remove empty instances --- .../lang/stable/EmptyStableFunction.java | 70 ------------------- .../internal/lang/stable/StableFunction.java | 3 + .../lang/stable/StableValueFactories.java | 5 +- .../lang/StableValue/StableFunctionTest.java | 4 +- 4 files changed, 6 insertions(+), 76 deletions(-) delete mode 100644 src/java.base/share/classes/jdk/internal/lang/stable/EmptyStableFunction.java diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/EmptyStableFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/EmptyStableFunction.java deleted file mode 100644 index ab2e9ec3c42c1..0000000000000 --- a/src/java.base/share/classes/jdk/internal/lang/stable/EmptyStableFunction.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (c) 2024, 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.vm.annotation.ForceInline; - -import java.util.Collections; -import java.util.function.Function; - -/** - * An empty stable function with no allowed inputs - * - * @implNote This implementation can be used early in the boot sequence as it does not - * rely on reflection, MethodHandles, Streams etc. - * - * @param original the original Function - * @param the type of the input to the function - * @param the type of the result of the function - */ -record EmptyStableFunction(Function original) implements Function { - - @ForceInline - @Override - public R apply(T value) { - throw new IllegalArgumentException("Input not allowed: " + value); - } - - @Override - public int hashCode() { - return System.identityHashCode(this); - } - - @Override - public boolean equals(Object obj) { - return obj == this; - } - - @Override - public String toString() { - return StableUtil.renderMappings(this, "StableFunction", Collections.emptyList()); - } - - static Function of(Function original) { - return new EmptyStableFunction<>(original); - } - -} diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableFunction.java index 036eeb5bac29a..64730afe09d62 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableFunction.java @@ -27,6 +27,8 @@ import jdk.internal.vm.annotation.ForceInline; +import java.util.Collection; +import java.util.Collections; import java.util.Map; import java.util.Set; import java.util.function.Function; @@ -48,6 +50,7 @@ */ record StableFunction(Map> values, Function original) implements Function { + @ForceInline @Override public R apply(T value) { diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableValueFactories.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableValueFactories.java index a434ecbdb6ba2..ec4e9cc8a22dc 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableValueFactories.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableValueFactories.java @@ -38,10 +38,7 @@ public static IntFunction intFunction(int size, public static Function function(Set inputs, Function original) { - if (inputs.isEmpty()) { - return EmptyStableFunction.of(original); - } - return inputs instanceof EnumSet + return inputs instanceof EnumSet && !inputs.isEmpty() ? StableEnumFunction.of(inputs, original) : StableFunction.of(inputs, original); } diff --git a/test/jdk/java/lang/StableValue/StableFunctionTest.java b/test/jdk/java/lang/StableValue/StableFunctionTest.java index b02261190915a..d3bfa198da1b7 100644 --- a/test/jdk/java/lang/StableValue/StableFunctionTest.java +++ b/test/jdk/java/lang/StableValue/StableFunctionTest.java @@ -113,7 +113,7 @@ void empty(Set inputs) { Function f0 = StableValue.function(inputs, Value::asInt); Function f1 = StableValue.function(inputs, Value::asInt); assertTrue(f0.toString().contains("{}")); - assertThrows(IllegalArgumentException.class, () -> f0.apply(null)); + assertThrows(NullPointerException.class, () -> f0.apply(null)); assertNotEquals(f0, f1); assertNotEquals(null, f0); } @@ -175,7 +175,7 @@ void usesOptimizedVersion() { Function enumFunction = StableValue.function(EnumSet.of(Value.FORTY_TWO), Value::asInt); assertEquals("jdk.internal.lang.stable.StableEnumFunction", enumFunction.getClass().getName()); Function emptyFunction = StableValue.function(Set.of(), Value::asInt); - assertEquals("jdk.internal.lang.stable.EmptyStableFunction", emptyFunction.getClass().getName()); + assertEquals("jdk.internal.lang.stable.StableFunction", emptyFunction.getClass().getName()); } private static Stream> nonEmptySets() { From 168622c6dae122391bfff1f2f018b47546abad9b Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 31 Mar 2025 08:34:53 +0200 Subject: [PATCH 210/327] Improve exception checking --- test/jdk/java/lang/StableValue/StableValueTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/jdk/java/lang/StableValue/StableValueTest.java b/test/jdk/java/lang/StableValue/StableValueTest.java index 3b2a213f250f9..33ac9e79bb923 100644 --- a/test/jdk/java/lang/StableValue/StableValueTest.java +++ b/test/jdk/java/lang/StableValue/StableValueTest.java @@ -184,7 +184,7 @@ void toStringNonNull() { void toStringCircular() { StableValue> stable = StableValue.of(); stable.trySet(stable); - String toString = stable.toString(); + String toString = assertDoesNotThrow(stable::toString); assertEquals("(this StableValue)", toString); assertDoesNotThrow(stable::hashCode); assertDoesNotThrow((() -> stable.equals(stable))); From 7fe2c363e229b22d6771239c22824307988d6ee2 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 31 Mar 2025 09:55:54 +0200 Subject: [PATCH 211/327] Remove link --- src/java.base/share/classes/java/lang/StableValue.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 303ad75c6157b..e8d23186ec33d 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -44,9 +44,9 @@ /** * A stable value is a deferred holder of shallowly immutable content. *

    - * A {@linkplain StableValue {@code StableValue}} can be created using the factory - * method {@linkplain StableValue#of() {@code StableValue.of()}}. When created - * this way, the stable value is unset, which means it holds no content. + * A {@code StableValue} can be created using the factory method + * {@linkplain StableValue#of() {@code StableValue.of()}}. When created this way, + * the stable value is unset, which means it holds no content. * Its content, of type {@code T}, can be set by calling * {@linkplain #trySet(Object) trySet()}, {@linkplain #setOrThrow(Object) setOrThrow()}, * or {@linkplain #orElseSet(Supplier) orElseSet()}. Once set, the content From 2d5bc50057caa8e71ee5feebab4650e31cf46d1e Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 31 Mar 2025 11:05:01 +0200 Subject: [PATCH 212/327] Improve StableMapEntrySet::toString --- .../java/util/ImmutableCollections.java | 5 +++ .../java/lang/StableValue/StableListTest.java | 20 ++++++++++ .../java/lang/StableValue/StableMapTest.java | 40 +++++++++++++++++++ 3 files changed, 65 insertions(+) diff --git a/src/java.base/share/classes/java/util/ImmutableCollections.java b/src/java.base/share/classes/java/util/ImmutableCollections.java index e295bf807925f..bf96a8bc401aa 100644 --- a/src/java.base/share/classes/java/util/ImmutableCollections.java +++ b/src/java.base/share/classes/java/util/ImmutableCollections.java @@ -1508,6 +1508,11 @@ final class StableMapEntrySet extends AbstractImmutableSet> { @Override public int size() { return delegateEntrySet.size(); } @Override public int hashCode() { return StableMap.this.hashCode(); } + @Override + public String toString() { + return StableUtil.renderMappings(this, "StableSet", delegateEntrySet); + } + @jdk.internal.ValueBased final class LazyMapIterator implements Iterator> { diff --git a/test/jdk/java/lang/StableValue/StableListTest.java b/test/jdk/java/lang/StableValue/StableListTest.java index ec9c73ca44c87..7469b30e46ec3 100644 --- a/test/jdk/java/lang/StableValue/StableListTest.java +++ b/test/jdk/java/lang/StableValue/StableListTest.java @@ -223,6 +223,26 @@ void subList() { assertEquals(regularSubList, lazySubList); } + @Test + void subList2() { + var lazy = newList(); + var lazySubList = lazy.subList(1, SIZE); + lazySubList.get(0); + var eq = newList(); + eq.get(1); + assertEquals(eq.toString(), lazy.toString()); + } + + @Test + void subListToString() { + var lazy = newList(); + var lazySubList = lazy.subList(1, SIZE); + var regularList = newRegularList(); + var regularSubList = regularList.subList(1, SIZE); + // There is no requirement that the lazy sub list's toString method should be lazy + assertEquals(regularSubList.toString(), lazySubList.toString()); + } + @Test void recursiveCall() { AtomicReference> ref = new AtomicReference<>(); diff --git a/test/jdk/java/lang/StableValue/StableMapTest.java b/test/jdk/java/lang/StableValue/StableMapTest.java index c72bccf5b0e3b..6dd4e06736ba2 100644 --- a/test/jdk/java/lang/StableValue/StableMapTest.java +++ b/test/jdk/java/lang/StableValue/StableMapTest.java @@ -167,6 +167,46 @@ void entrySet() { assertTrue(regular.equals(actual)); } + @Test + void entrySetToString() { + var map = newMap(); + var entrySet = map.entrySet(); + for (var key : KEYS) { + assertTrue(entrySet.toString().contains(key + "=.unset")); + } + map.get(KEY); + for (var key : KEYS) { + if (key.equals(KEY)) { + continue; + } + assertTrue(entrySet.toString().contains(key + "=.unset")); + } + assertTrue(entrySet.toString().contains(KEY + "=" + KEY)); + } + + @Test + void values() { + var map = newMap(); + var values = map.values(); + // Look at one of the elements + var val = values.stream().iterator().next(); + for (var key : KEYS) { + if (key.equals(val)) { + assertTrue(map.toString().contains(key + "=" + key)); + } else { + assertTrue(map.toString().contains(key + "=.unset")); + } + } + + // Mod ops + assertThrows(UnsupportedOperationException.class, () -> values.remove(KEY)); + assertThrows(UnsupportedOperationException.class, () -> values.add(KEY)); + assertThrows(UnsupportedOperationException.class, values::clear); + assertThrows(UnsupportedOperationException.class, () -> values.addAll(Set.of(1))); + assertThrows(UnsupportedOperationException.class, () -> values.removeIf(i -> true)); + assertThrows(UnsupportedOperationException.class, () -> values.retainAll(Set.of(KEY))); + } + @Test void iteratorNext() { Set encountered = new HashSet<>(); From 5bdb55844be52c273720fad1a121d443992b98ed Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 31 Mar 2025 11:28:04 +0200 Subject: [PATCH 213/327] Fix issue with StableMap and null values --- .../classes/java/util/ImmutableCollections.java | 13 ++++++++++++- test/jdk/java/lang/StableValue/StableMapTest.java | 8 ++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/java.base/share/classes/java/util/ImmutableCollections.java b/src/java.base/share/classes/java/util/ImmutableCollections.java index bf96a8bc401aa..6d3718ac0233b 100644 --- a/src/java.base/share/classes/java/util/ImmutableCollections.java +++ b/src/java.base/share/classes/java/util/ImmutableCollections.java @@ -1484,9 +1484,20 @@ static final class StableMap @ForceInline @Override public V get(Object key) { + return getOrDefault0(key, null); + } + + @ForceInline + @Override + public V getOrDefault(Object key, V defaultValue) { + return getOrDefault0(key, defaultValue); + } + + @ForceInline + private V getOrDefault0(Object key, V defaultValue) { final StableValueImpl stable = delegate.get(key); if (stable == null) { - return null; + return defaultValue; } @SuppressWarnings("unchecked") final K k = (K) key; diff --git a/test/jdk/java/lang/StableValue/StableMapTest.java b/test/jdk/java/lang/StableValue/StableMapTest.java index 6dd4e06736ba2..f5dcc0d256928 100644 --- a/test/jdk/java/lang/StableValue/StableMapTest.java +++ b/test/jdk/java/lang/StableValue/StableMapTest.java @@ -278,6 +278,14 @@ void distinct() { assertEquals(3, idMap.size()); } + @Test + void nullResult() { + var map = StableValue.map(Set.of(0), _ -> null); + assertNull(map.getOrDefault(0, 1));; + assertTrue(map.containsKey(0)); + assertNull(map.get(0)); + } + // Support constructs record Operation(String name, From 8c0ea1abe24173362015c540b8b7f7d117b86146 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 31 Mar 2025 11:42:10 +0200 Subject: [PATCH 214/327] Add test and comments about null keys --- src/java.base/share/classes/java/lang/StableValue.java | 4 ++-- test/jdk/java/lang/StableValue/StableMapTest.java | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index e8d23186ec33d..74d1c0f102807 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -563,7 +563,7 @@ static IntFunction intFunction(int size, * function for the same input, an {@linkplain IllegalStateException} will * be thrown. * - * @param inputs the set of allowed input values + * @param inputs the set of (non-null) allowed input values * @param original Function used to compute cached values * @param the type of the input to the returned Function * @param the type of results delivered by the returned Function @@ -639,7 +639,7 @@ static List list(int size, * If the provided {@code mapper} recursively calls the returned map for * the same key, an {@linkplain IllegalStateException} will be thrown. * - * @param keys the keys in the returned map + * @param keys the (non-null) keys in the returned map * @param mapper to invoke whenever an associated value is first accessed * (may return {@code null}) * @param the type of keys maintained by the returned map diff --git a/test/jdk/java/lang/StableValue/StableMapTest.java b/test/jdk/java/lang/StableValue/StableMapTest.java index f5dcc0d256928..7f468600a0933 100644 --- a/test/jdk/java/lang/StableValue/StableMapTest.java +++ b/test/jdk/java/lang/StableValue/StableMapTest.java @@ -286,6 +286,14 @@ void nullResult() { assertNull(map.get(0)); } + @Test + void nullKeys() { + Set inputs = new HashSet<>(); + inputs.add(0); + inputs.add(null); + assertThrows(NullPointerException.class, () -> StableValue.map(inputs, IDENTITY)); + } + // Support constructs record Operation(String name, From 94b835f229128e99f87688d95a79d6cb0642a3e5 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 31 Mar 2025 11:50:18 +0200 Subject: [PATCH 215/327] Fix issue with wrapped exception --- .../share/classes/java/util/ImmutableCollections.java | 7 ++++--- .../jdk/internal/lang/stable/StableEnumFunction.java | 8 +++++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/java.base/share/classes/java/util/ImmutableCollections.java b/src/java.base/share/classes/java/util/ImmutableCollections.java index 6d3718ac0233b..43a98c42d2f88 100644 --- a/src/java.base/share/classes/java/util/ImmutableCollections.java +++ b/src/java.base/share/classes/java/util/ImmutableCollections.java @@ -789,13 +789,14 @@ static final class StableList extends AbstractImmutableList { @ForceInline @Override public E get(int i) { + final StableValueImpl delegate; try { - return delegates[i] - .orElseSet(new Supplier() { - @Override public E get() { return mapper.apply(i); }}); + delegate = delegates[i]; } catch (ArrayIndexOutOfBoundsException aioobe) { throw new IndexOutOfBoundsException(i); } + return delegate.orElseSet(new Supplier() { + @Override public E get() { return mapper.apply(i); }}); } @Override diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java index 233869975537a..b0ff924f665e1 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java @@ -64,13 +64,15 @@ public R apply(E value) { throw new IllegalArgumentException("Input not allowed: " + value); } final int index = value.ordinal() - firstOrdinal; + final StableValueImpl delegate; try { - return delegates[index] - .orElseSet(new Supplier() { - @Override public R get() { return original.apply(value); }}); + delegate = delegates[index]; } catch (ArrayIndexOutOfBoundsException ioob) { throw new IllegalArgumentException("Input not allowed: " + value, ioob); } + return delegate.orElseSet(new Supplier() { + @Override public R get() { return original.apply(value); }}); + } @Override From fe021b5cc367719319eefe2b934f068a7071c8f8 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 31 Mar 2025 11:54:23 +0200 Subject: [PATCH 216/327] Fix issue in StableIntFunction related to wrapped exceptions --- .../jdk/internal/lang/stable/StableIntFunction.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableIntFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableIntFunction.java index 6fa7cd87892b6..ff1ca99842bdb 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableIntFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableIntFunction.java @@ -51,13 +51,14 @@ record StableIntFunction(@Stable StableValueImpl[] delegates, @ForceInline @Override public R apply(int index) { + final StableValueImpl delegate; try { - return delegates[index] - .orElseSet(new Supplier() { - @Override public R get() { return original.apply(index); }}); + delegate = delegates[index]; } catch (ArrayIndexOutOfBoundsException ioob) { throw new IllegalArgumentException("Input not allowed: " + index, ioob); } + return delegate.orElseSet(new Supplier() { + @Override public R get() { return original.apply(index); }}); } @Override From 766d907ff9b59aaa494a8412a69f27241c784f80 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 31 Mar 2025 13:14:39 +0200 Subject: [PATCH 217/327] Rename factory method --- .../classes/jdk/internal/lang/stable/StableSupplier.java | 2 +- .../jdk/internal/lang/stable/StableValueFactories.java | 6 +++--- .../classes/jdk/internal/lang/stable/StableValueImpl.java | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableSupplier.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableSupplier.java index 03e854f1b4f62..0321a3dd23705 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableSupplier.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableSupplier.java @@ -62,7 +62,7 @@ public String toString() { } static StableSupplier of(Supplier original) { - return new StableSupplier<>(StableValueImpl.newInstance(), original); + return new StableSupplier<>(StableValueImpl.of(), original); } } diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableValueFactories.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableValueFactories.java index ec4e9cc8a22dc..cbb80329a3850 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableValueFactories.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableValueFactories.java @@ -18,7 +18,7 @@ private StableValueFactories() {} // Factories public static StableValueImpl of() { - return StableValueImpl.newInstance(); + return StableValueImpl.of(); } public static StableValueImpl of(T value) { @@ -60,7 +60,7 @@ public static StableValueImpl[] array(int size) { @SuppressWarnings("unchecked") final var stableValues = (StableValueImpl[]) new StableValueImpl[size]; for (int i = 0; i < size; i++) { - stableValues[i] = StableValueImpl.newInstance(); + stableValues[i] = StableValueImpl.of(); } return stableValues; } @@ -71,7 +71,7 @@ public static Map> map(Set keys) { final var entries = (Map.Entry>[]) new Map.Entry[keys.size()]; int i = 0; for (K key : keys) { - entries[i++] = Map.entry(key, StableValueImpl.newInstance()); + entries[i++] = Map.entry(key, StableValueImpl.of()); } return Map.ofEntries(entries); } 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 e2c995a64b993..3d43d2c06696c 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 @@ -200,7 +200,7 @@ private static T unwrap(Object t) { // Factory - static StableValueImpl newInstance() { + static StableValueImpl of() { return new StableValueImpl<>(); } From 09122d41aff39d69c6c0b8d7e6edad1b3dc2bb48 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 31 Mar 2025 13:42:58 +0200 Subject: [PATCH 218/327] Remove VM optimizations for StableValue fields --- src/hotspot/share/ci/ciField.cpp | 8 +- src/hotspot/share/classfile/vmSymbols.hpp | 3 - src/hotspot/share/runtime/fieldDescriptor.cpp | 3 +- .../share/classes/sun/misc/Unsafe.java | 34 +++++---- .../StableValue/TrustedFieldTypeTest.java | 73 ------------------- 5 files changed, 23 insertions(+), 98 deletions(-) diff --git a/src/hotspot/share/ci/ciField.cpp b/src/hotspot/share/ci/ciField.cpp index 2f3ccf349e26a..cbe0cadbc9304 100644 --- a/src/hotspot/share/ci/ciField.cpp +++ b/src/hotspot/share/ci/ciField.cpp @@ -250,10 +250,6 @@ static bool trust_final_non_static_fields(ciInstanceKlass* holder) { return TrustFinalNonStaticFields; } -static bool trust_final_non_static_fields_of_type(Symbol* signature) { - return signature == vmSymbols::java_lang_StableValue_signature(); -} - void ciField::initialize_from(fieldDescriptor* fd) { // Get the flags, offset, and canonical holder of the field. _flags = ciFlags(fd->access_flags(), fd->field_flags().is_stable(), fd->field_status().is_initialized_final_update()); @@ -286,9 +282,7 @@ void ciField::initialize_from(fieldDescriptor* fd) { // An instance field can be constant if it's a final static field or if // it's a final non-static field of a trusted class (classes in // java.lang.invoke and sun.invoke packages and subpackages). - _is_constant = is_stable_field || - trust_final_non_static_fields(_holder) || - trust_final_non_static_fields_of_type(fd->signature()); + _is_constant = is_stable_field || trust_final_non_static_fields(_holder); } } else { // For CallSite objects treat the target field as a compile time constant. diff --git a/src/hotspot/share/classfile/vmSymbols.hpp b/src/hotspot/share/classfile/vmSymbols.hpp index 270c54bcbf8bc..e66066738ef38 100644 --- a/src/hotspot/share/classfile/vmSymbols.hpp +++ b/src/hotspot/share/classfile/vmSymbols.hpp @@ -744,9 +744,6 @@ class SerializeClosure; template(dumpThreads_name, "dumpThreads") \ template(dumpThreadsToJson_name, "dumpThreadsToJson") \ \ - /* Stable Values */ \ - template(java_lang_StableValue_signature, "Ljava/lang/StableValue;") \ - \ /* jcmd Thread.vthread_scheduler and Thread.vthread_pollers */ \ template(jdk_internal_vm_JcmdVThreadCommands, "jdk/internal/vm/JcmdVThreadCommands") \ template(printScheduler_name, "printScheduler") \ diff --git a/src/hotspot/share/runtime/fieldDescriptor.cpp b/src/hotspot/share/runtime/fieldDescriptor.cpp index a3796497a257f..a9d4f572e4f7e 100644 --- a/src/hotspot/share/runtime/fieldDescriptor.cpp +++ b/src/hotspot/share/runtime/fieldDescriptor.cpp @@ -43,8 +43,7 @@ Symbol* fieldDescriptor::generic_signature() const { bool fieldDescriptor::is_trusted_final() const { InstanceKlass* ik = field_holder(); - return is_final() && (is_static() || ik->is_hidden() || ik->is_record() || - signature() == vmSymbols::java_lang_StableValue_signature()); + return is_final() && (is_static() || ik->is_hidden() || ik->is_record()); } AnnotationArray* fieldDescriptor::annotations() const { diff --git a/src/jdk.unsupported/share/classes/sun/misc/Unsafe.java b/src/jdk.unsupported/share/classes/sun/misc/Unsafe.java index b2612d9db1c3f..612f196d46255 100644 --- a/src/jdk.unsupported/share/classes/sun/misc/Unsafe.java +++ b/src/jdk.unsupported/share/classes/sun/misc/Unsafe.java @@ -895,7 +895,13 @@ public long objectFieldOffset(Field f) { if (f == null) { throw new NullPointerException(); } - ensureNotTrusted(f); + Class declaringClass = f.getDeclaringClass(); + if (declaringClass.isHidden()) { + throw new UnsupportedOperationException("can't get field offset on a hidden class: " + f); + } + if (declaringClass.isRecord()) { + throw new UnsupportedOperationException("can't get field offset on a record class: " + f); + } beforeMemoryAccess(); return theInternalUnsafe.objectFieldOffset(f); } @@ -929,7 +935,13 @@ public long staticFieldOffset(Field f) { if (f == null) { throw new NullPointerException(); } - ensureNotTrusted(f); + Class declaringClass = f.getDeclaringClass(); + if (declaringClass.isHidden()) { + throw new UnsupportedOperationException("can't get field offset on a hidden class: " + f); + } + if (declaringClass.isRecord()) { + throw new UnsupportedOperationException("can't get field offset on a record class: " + f); + } beforeMemoryAccess(); return theInternalUnsafe.staticFieldOffset(f); } @@ -955,7 +967,13 @@ public Object staticFieldBase(Field f) { if (f == null) { throw new NullPointerException(); } - ensureNotTrusted(f); + Class declaringClass = f.getDeclaringClass(); + if (declaringClass.isHidden()) { + throw new UnsupportedOperationException("can't get field offset on a hidden class: " + f); + } + if (declaringClass.isRecord()) { + throw new UnsupportedOperationException("can't get field offset on a record class: " + f); + } beforeMemoryAccess(); return theInternalUnsafe.staticFieldBase(f); } @@ -988,16 +1006,6 @@ private static void ensureNotTrusted(Field f) { if (declaringClass.isRecord()) { throw new UnsupportedOperationException("can't get base address on a record class: " + f); } - Class fieldType = f.getType(); - // Todo: Change to "java.lang.StableValue.class.isAssignableFrom(fieldType)" etc. after StableValue exits preview - if (fieldType.getName().equals("java.lang.StableValue") || (fieldType.isArray() && deepComponent(fieldType).getName().equals("java.lang.StableValue"))) { - throw new UnsupportedOperationException("can't get field offset for a field of type " + fieldType.getName() + ": " + f); - } - } - - @ForceInline - private static Class deepComponent(Class clazz) { - return clazz.isArray() ? deepComponent(clazz.getComponentType()) : clazz; } /** The value of {@code arrayBaseOffset(boolean[].class)}. diff --git a/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java b/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java index 925d2ddb9b561..1074db55d2c31 100644 --- a/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java +++ b/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java @@ -44,78 +44,6 @@ final class TrustedFieldTypeTest { - @Test - void reflection() throws NoSuchFieldException, IllegalAccessException { - final class Holder { - private final StableValue value = StableValue.of(); - } - final class HolderNonFinal { - private StableValue value = StableValue.of(); - } - final class ArrayHolder { - @SuppressWarnings("unchecked") - private final StableValue[] array = (StableValue[]) new StableValue[]{}; - } - - Field valueField = Holder.class.getDeclaredField("value"); - valueField.setAccessible(true); - Holder holder = new Holder(); - // We should be able to read the StableValue field - Object read = valueField.get(holder); - // We should NOT be able to write to the StableValue field - assertThrows(IllegalAccessException.class, () -> - valueField.set(holder, StableValue.of()) - ); - - Field valueNonFinal = HolderNonFinal.class.getDeclaredField("value"); - valueNonFinal.setAccessible(true); - HolderNonFinal holderNonFinal = new HolderNonFinal(); - // As the field is not final, both read and write should be ok (not trusted) - Object readNonFinal = valueNonFinal.get(holderNonFinal); - valueNonFinal.set(holderNonFinal, StableValue.of()); - - Field arrayField = ArrayHolder.class.getDeclaredField("array"); - arrayField.setAccessible(true); - ArrayHolder arrayHolder = new ArrayHolder(); - // We should be able to read the StableValue array - read = arrayField.get(arrayHolder); - // We should be able to write to the StableValue array - assertDoesNotThrow(() -> arrayField.set(arrayHolder, new StableValue[1])); - } - - @SuppressWarnings("removal") - @Test - void sunMiscUnsafe() throws NoSuchFieldException, IllegalAccessException { - Field unsafeField = sun.misc.Unsafe.class.getDeclaredField("theUnsafe"); - assertTrue(unsafeField.trySetAccessible()); - sun.misc.Unsafe unsafe = (sun.misc.Unsafe)unsafeField.get(null); - - final class Holder { - private final StableValue value = StableValue.of(); - } - final class ArrayHolder { - @SuppressWarnings("unchecked") - private final StableValue[] array = (StableValue[]) new StableValue[]{}; - } - - Field valueField = Holder.class.getDeclaredField("value"); - assertThrows(UnsupportedOperationException.class, () -> - unsafe.objectFieldOffset(valueField) - ); - - Field arrayField = ArrayHolder.class.getDeclaredField("array"); - - assertThrows(UnsupportedOperationException.class, () -> - unsafe.objectFieldOffset(arrayField) - ); - - // Test direct access - StableValue stableValue = StableValue.of(); - Class clazz = stableValue.getClass(); - System.out.println("clazz = " + clazz); - assertThrows(NoSuchFieldException.class, () -> clazz.getField("value")); - } - @Test void varHandle() throws NoSuchFieldException, IllegalAccessException { MethodHandles.Lookup lookup = MethodHandles.lookup(); @@ -197,5 +125,4 @@ void updateStableValueContentViaSetAccessible() throws NoSuchFieldException, Ill } } - } From 6fd565335e4568aa442a0b20b40119a4839d1595 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 31 Mar 2025 14:02:22 +0200 Subject: [PATCH 219/327] Revert changes in s.m.Unsafe --- .../share/classes/sun/misc/Unsafe.java | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/jdk.unsupported/share/classes/sun/misc/Unsafe.java b/src/jdk.unsupported/share/classes/sun/misc/Unsafe.java index 612f196d46255..b0a27d368ff7c 100644 --- a/src/jdk.unsupported/share/classes/sun/misc/Unsafe.java +++ b/src/jdk.unsupported/share/classes/sun/misc/Unsafe.java @@ -969,10 +969,10 @@ public Object staticFieldBase(Field f) { } Class declaringClass = f.getDeclaringClass(); if (declaringClass.isHidden()) { - throw new UnsupportedOperationException("can't get field offset on a hidden class: " + f); + throw new UnsupportedOperationException("can't get base address on a hidden class: " + f); } if (declaringClass.isRecord()) { - throw new UnsupportedOperationException("can't get field offset on a record class: " + f); + throw new UnsupportedOperationException("can't get base address on a record class: " + f); } beforeMemoryAccess(); return theInternalUnsafe.staticFieldBase(f); @@ -997,17 +997,6 @@ public int arrayBaseOffset(Class arrayClass) { return (int) theInternalUnsafe.arrayBaseOffset(arrayClass); } - @ForceInline - private static void ensureNotTrusted(Field f) { - Class declaringClass = f.getDeclaringClass(); - if (declaringClass.isHidden()) { - throw new UnsupportedOperationException("can't get base address on a hidden class: " + f); - } - if (declaringClass.isRecord()) { - throw new UnsupportedOperationException("can't get base address on a record class: " + f); - } - } - /** The value of {@code arrayBaseOffset(boolean[].class)}. * * @deprecated Not needed when using {@link VarHandle} or {@link java.lang.foreign}. From f9521793daf46ad4b03e0ccb4d645fd00b3dac1d Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 31 Mar 2025 14:14:02 +0200 Subject: [PATCH 220/327] Remove StableValueFactories --- .../share/classes/java/lang/StableValue.java | 25 ++++-- .../java/util/ImmutableCollections.java | 5 +- .../lang/stable/StableEnumFunction.java | 17 ++-- .../internal/lang/stable/StableFunction.java | 12 ++- .../lang/stable/StableIntFunction.java | 8 +- .../internal/lang/stable/StableSupplier.java | 5 +- .../jdk/internal/lang/stable/StableUtil.java | 24 ++++++ .../lang/stable/StableValueFactories.java | 79 ------------------- .../internal/lang/stable/StableValueImpl.java | 2 +- .../java/lang/StableValue/StableListTest.java | 4 +- .../java/lang/StableValue/StableMapTest.java | 4 +- .../StableValue/StableValueFactoriesTest.java | 4 +- 12 files changed, 70 insertions(+), 119 deletions(-) delete mode 100644 src/java.base/share/classes/jdk/internal/lang/stable/StableValueFactories.java diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 74d1c0f102807..02fa88c7fccc8 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -25,12 +25,17 @@ package java.lang; +import jdk.internal.access.SharedSecrets; import jdk.internal.javac.PreviewFeature; +import jdk.internal.lang.stable.StableEnumFunction; +import jdk.internal.lang.stable.StableFunction; +import jdk.internal.lang.stable.StableIntFunction; +import jdk.internal.lang.stable.StableSupplier; import jdk.internal.lang.stable.StableValueImpl; -import jdk.internal.lang.stable.StableValueFactories; import java.io.Serializable; import java.util.Collection; +import java.util.EnumSet; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; @@ -458,7 +463,7 @@ public sealed interface StableValue * @param type of the content */ static StableValue of() { - return StableValueFactories.of(); + return StableValueImpl.of(); } /** @@ -470,7 +475,9 @@ static StableValue of() { * @param type of the content */ static StableValue of(T content) { - return StableValueFactories.of(content); + final StableValue stableValue = StableValue.of(); + stableValue.trySet(content); + return stableValue; } /** @@ -499,7 +506,7 @@ static StableValue of(T content) { */ static Supplier supplier(Supplier original) { Objects.requireNonNull(original); - return StableValueFactories.supplier(original); + return StableSupplier.of(original); } /** @@ -537,7 +544,7 @@ static IntFunction intFunction(int size, throw new IllegalArgumentException(); } Objects.requireNonNull(original); - return StableValueFactories.intFunction(size, original); + return StableIntFunction.of(size, original); } /** @@ -572,7 +579,9 @@ static Function function(Set inputs, Function original) { Objects.requireNonNull(inputs); Objects.requireNonNull(original); - return StableValueFactories.function(inputs, original); + return inputs instanceof EnumSet && !inputs.isEmpty() + ? StableEnumFunction.of(inputs, original) + : StableFunction.of(inputs, original); } /** @@ -613,7 +622,7 @@ static List list(int size, throw new IllegalArgumentException(); } Objects.requireNonNull(mapper); - return StableValueFactories.list(size, mapper); + return SharedSecrets.getJavaUtilCollectionAccess().stableList(size, mapper); } /** @@ -649,7 +658,7 @@ static Map map(Set keys, Function mapper) { Objects.requireNonNull(keys); Objects.requireNonNull(mapper); - return StableValueFactories.map(keys, mapper); + return SharedSecrets.getJavaUtilCollectionAccess().stableMap(keys, mapper); } } diff --git a/src/java.base/share/classes/java/util/ImmutableCollections.java b/src/java.base/share/classes/java/util/ImmutableCollections.java index 43a98c42d2f88..f060910778128 100644 --- a/src/java.base/share/classes/java/util/ImmutableCollections.java +++ b/src/java.base/share/classes/java/util/ImmutableCollections.java @@ -44,7 +44,6 @@ import jdk.internal.access.SharedSecrets; import jdk.internal.lang.stable.StableUtil; import jdk.internal.lang.stable.StableValueImpl; -import jdk.internal.lang.stable.StableValueFactories; import jdk.internal.misc.CDS; import jdk.internal.util.NullableKeyValueHolder; import jdk.internal.vm.annotation.ForceInline; @@ -779,7 +778,7 @@ static final class StableList extends AbstractImmutableList { StableList(int size, IntFunction mapper) { this.mapper = mapper; - this.delegates = StableValueFactories.array(size); + this.delegates = StableUtil.array(size); } @Override public boolean isEmpty() { return delegates.length == 0;} @@ -1475,7 +1474,7 @@ static final class StableMap StableMap(Set keys, Function mapper) { this.mapper = mapper; - this.delegate = StableValueFactories.map(keys); + this.delegate = StableUtil.map(keys); } @Override public boolean containsKey(Object o) { return delegate.containsKey(o); } diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java index b0ff924f665e1..92a06396489d1 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java @@ -33,7 +33,6 @@ import java.util.ArrayList; import java.util.BitSet; import java.util.Collection; -import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Function; @@ -52,11 +51,11 @@ * @param the type of the input to the function * @param the type of the result of the function */ -record StableEnumFunction, R>(Class enumType, - int firstOrdinal, - IntPredicate member, - @Stable StableValueImpl[] delegates, - Function original) implements Function { +public record StableEnumFunction, R>(Class enumType, + int firstOrdinal, + IntPredicate member, + @Stable StableValueImpl[] delegates, + Function original) implements Function { @ForceInline @Override public R apply(E value) { @@ -99,8 +98,8 @@ public String toString() { } @SuppressWarnings("unchecked") - static , R> Function of(Set inputs, - Function original) { + public static , R> Function of(Set inputs, + Function original) { final BitSet bitSet = new BitSet(inputs.size()); // The input set is not empty int min = Integer.MAX_VALUE; @@ -115,7 +114,7 @@ static , R> Function of(Set inputs, final int size = max - min + 1; final Class enumType = (Class)inputs.iterator().next().getClass(); final IntPredicate member = ImmutableBitSetPredicate.of(bitSet); - return (Function) new StableEnumFunction(enumType, min, member, StableValueFactories.array(size), (Function) original); + return (Function) new StableEnumFunction(enumType, min, member, StableUtil.array(size), (Function) original); } } diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableFunction.java index 64730afe09d62..8cda536fafdea 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableFunction.java @@ -27,8 +27,6 @@ import jdk.internal.vm.annotation.ForceInline; -import java.util.Collection; -import java.util.Collections; import java.util.Map; import java.util.Set; import java.util.function.Function; @@ -48,8 +46,8 @@ * @param the type of the input to the function * @param the type of the result of the function */ -record StableFunction(Map> values, - Function original) implements Function { +public record StableFunction(Map> values, + Function original) implements Function { @ForceInline @Override @@ -77,9 +75,9 @@ public String toString() { return StableUtil.renderMappings(this, "StableFunction", values.entrySet()); } - static StableFunction of(Set inputs, - Function original) { - return new StableFunction<>(StableValueFactories.map(inputs), original); + public static StableFunction of(Set inputs, + Function original) { + return new StableFunction<>(StableUtil.map(inputs), original); } } diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableIntFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableIntFunction.java index ff1ca99842bdb..7a4b9de5c701c 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableIntFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableIntFunction.java @@ -45,8 +45,8 @@ * * @param the return type */ -record StableIntFunction(@Stable StableValueImpl[] delegates, - IntFunction original) implements IntFunction { +public record StableIntFunction(@Stable StableValueImpl[] delegates, + IntFunction original) implements IntFunction { @ForceInline @Override @@ -76,8 +76,8 @@ public String toString() { return StableUtil.renderElements(this, "StableIntFunction", delegates); } - static StableIntFunction of(int size, IntFunction original) { - return new StableIntFunction<>(StableValueFactories.array(size), original); + public static StableIntFunction of(int size, IntFunction original) { + return new StableIntFunction<>(StableUtil.array(size), original); } } diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableSupplier.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableSupplier.java index 0321a3dd23705..01125664190e5 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableSupplier.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableSupplier.java @@ -37,7 +37,8 @@ * * @param the return type */ -record StableSupplier(StableValueImpl delegate, Supplier original) implements Supplier { +public record StableSupplier(StableValueImpl delegate, + Supplier original) implements Supplier { @ForceInline @Override @@ -61,7 +62,7 @@ public String toString() { return t == this ? "(this StableSupplier)" : StableValueImpl.renderWrapped(t); } - static StableSupplier of(Supplier original) { + public static StableSupplier of(Supplier original) { return new StableSupplier<>(StableValueImpl.of(), original); } 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 2aa315731d7d9..17419cbb61143 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 @@ -2,6 +2,8 @@ import java.util.Collection; import java.util.Map; +import java.util.Objects; +import java.util.Set; public final class StableUtil { @@ -47,4 +49,26 @@ public static String renderMappings(Object self, } + public static StableValueImpl[] array(int size) { + if (size < 0) { + throw new IllegalArgumentException(); + } + @SuppressWarnings("unchecked") + final var stableValues = (StableValueImpl[]) new StableValueImpl[size]; + for (int i = 0; i < size; i++) { + stableValues[i] = StableValueImpl.of(); + } + return stableValues; + } + + public static Map> map(Set keys) { + Objects.requireNonNull(keys); + @SuppressWarnings("unchecked") + final var entries = (Map.Entry>[]) new Map.Entry[keys.size()]; + int i = 0; + for (K key : keys) { + entries[i++] = Map.entry(key, StableValueImpl.of()); + } + return Map.ofEntries(entries); + } } diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableValueFactories.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableValueFactories.java deleted file mode 100644 index cbb80329a3850..0000000000000 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableValueFactories.java +++ /dev/null @@ -1,79 +0,0 @@ -package jdk.internal.lang.stable; - -import jdk.internal.access.SharedSecrets; - -import java.util.EnumSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.function.Function; -import java.util.function.IntFunction; -import java.util.function.Supplier; - -public final class StableValueFactories { - - private StableValueFactories() {} - - // Factories - - public static StableValueImpl of() { - return StableValueImpl.of(); - } - - public static StableValueImpl of(T value) { - final StableValueImpl stableValue = of(); - stableValue.trySet(value); - return stableValue; - } - - public static Supplier supplier(Supplier original) { - return StableSupplier.of(original); - } - - public static IntFunction intFunction(int size, - IntFunction original) { - return StableIntFunction.of(size, original); - } - - public static Function function(Set inputs, - Function original) { - return inputs instanceof EnumSet && !inputs.isEmpty() - ? StableEnumFunction.of(inputs, original) - : StableFunction.of(inputs, original); - } - - public static List list(int size, IntFunction mapper) { - return SharedSecrets.getJavaUtilCollectionAccess().stableList(size, mapper); - } - - public static Map map(Set keys, Function mapper) { - return SharedSecrets.getJavaUtilCollectionAccess().stableMap(keys, mapper); - } - - // Supporting methods - - public static StableValueImpl[] array(int size) { - if (size < 0) { - throw new IllegalArgumentException(); - } - @SuppressWarnings("unchecked") - final var stableValues = (StableValueImpl[]) new StableValueImpl[size]; - for (int i = 0; i < size; i++) { - stableValues[i] = StableValueImpl.of(); - } - return stableValues; - } - - public static Map> map(Set keys) { - Objects.requireNonNull(keys); - @SuppressWarnings("unchecked") - final var entries = (Map.Entry>[]) new Map.Entry[keys.size()]; - int i = 0; - for (K key : keys) { - entries[i++] = Map.entry(key, StableValueImpl.of()); - } - return Map.ofEntries(entries); - } - -} 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 3d43d2c06696c..43bed39872857 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 @@ -200,7 +200,7 @@ private static T unwrap(Object t) { // Factory - static StableValueImpl of() { + public static StableValueImpl of() { return new StableValueImpl<>(); } diff --git a/test/jdk/java/lang/StableValue/StableListTest.java b/test/jdk/java/lang/StableValue/StableListTest.java index 7469b30e46ec3..ad714858e627e 100644 --- a/test/jdk/java/lang/StableValue/StableListTest.java +++ b/test/jdk/java/lang/StableValue/StableListTest.java @@ -28,8 +28,8 @@ * @run junit/othervm --enable-preview StableListTest */ +import jdk.internal.lang.stable.StableUtil; import jdk.internal.lang.stable.StableValueImpl; -import jdk.internal.lang.stable.StableValueFactories; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -309,7 +309,7 @@ void randomAccess() { @Test void distinct() { - StableValueImpl[] array = StableValueFactories.array(SIZE); + StableValueImpl[] array = StableUtil.array(SIZE); assertEquals(SIZE, array.length); // Check, every StableValue is distinct Map, Boolean> idMap = new IdentityHashMap<>(); diff --git a/test/jdk/java/lang/StableValue/StableMapTest.java b/test/jdk/java/lang/StableValue/StableMapTest.java index 7f468600a0933..814c25cfd1353 100644 --- a/test/jdk/java/lang/StableValue/StableMapTest.java +++ b/test/jdk/java/lang/StableValue/StableMapTest.java @@ -28,8 +28,8 @@ * @run junit/othervm --enable-preview StableMapTest */ +import jdk.internal.lang.stable.StableUtil; import jdk.internal.lang.stable.StableValueImpl; -import jdk.internal.lang.stable.StableValueFactories; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -270,7 +270,7 @@ void serializable(Map map) { @Test void distinct() { - Map> map = StableValueFactories.map(Set.of(1, 2, 3)); + Map> map = StableUtil.map(Set.of(1, 2, 3)); assertEquals(3, map.size()); // Check, every StableValue is distinct Map, Boolean> idMap = new IdentityHashMap<>(); diff --git a/test/jdk/java/lang/StableValue/StableValueFactoriesTest.java b/test/jdk/java/lang/StableValue/StableValueFactoriesTest.java index e02dcabc16ab3..7510f0012313d 100644 --- a/test/jdk/java/lang/StableValue/StableValueFactoriesTest.java +++ b/test/jdk/java/lang/StableValue/StableValueFactoriesTest.java @@ -28,7 +28,7 @@ * @run junit/othervm --enable-preview StableValueFactoriesTest */ -import jdk.internal.lang.stable.StableValueFactories; +import jdk.internal.lang.stable.StableUtil; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; @@ -37,7 +37,7 @@ final class StableValueFactoriesTest { @Test void array() { - assertThrows(IllegalArgumentException.class, () -> StableValueFactories.array(-1)); + assertThrows(IllegalArgumentException.class, () -> StableUtil.array(-1)); } } From 7fb8cb417e962153ac87a6eff9935df4e28e86ce Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 31 Mar 2025 15:29:25 +0200 Subject: [PATCH 221/327] Finish and clean up benchmarks --- .../lang/stable/StableFunctionBenchmark.java | 12 +-- .../stable/StableFunctionSingleBenchmark.java | 88 +++++++++++++++++++ .../stable/StableIntFunctionBenchmark.java | 13 ++- .../StableIntFunctionSingleBenchmark.java | 84 ++++++++++++++++++ .../lang/stable/StableSupplierBenchmark.java | 2 - .../lang/stable/StableValueBenchmark.java | 36 +++++--- 6 files changed, 206 insertions(+), 29 deletions(-) create mode 100644 test/micro/org/openjdk/bench/java/lang/stable/StableFunctionSingleBenchmark.java create mode 100644 test/micro/org/openjdk/bench/java/lang/stable/StableIntFunctionSingleBenchmark.java diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableFunctionBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableFunctionBenchmark.java index 62767d0644181..381780fd9ae38 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/StableFunctionBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableFunctionBenchmark.java @@ -60,17 +60,17 @@ public class StableFunctionBenchmark { private static final int SIZE = 100; private static final Set SET = IntStream.range(0, SIZE).boxed().collect(Collectors.toSet()); - private static final Map STABLE = StableValue.map(SET, Function.identity()); + private static final Map MAP = StableValue.map(SET, Function.identity()); private static final Function FUNCTION = StableValue.function(SET, Function.identity()); - private final Map stable = StableValue.map(SET, Function.identity()); + private final Map map = StableValue.map(SET, Function.identity()); private final Function function = StableValue.function(SET, Function.identity()); @Benchmark - public int stable() { + public int map() { int sum = 0; for (int i = 0; i < SIZE; i++) { - sum += stable.get(i); + sum += map.get(i); } return sum; } @@ -85,10 +85,10 @@ public int function() { } @Benchmark - public int staticStable() { + public int staticSMap() { int sum = 0; for (int i = 0; i < SIZE; i++) { - sum += STABLE.get(i); + sum += MAP.get(i); } return sum; } diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableFunctionSingleBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableFunctionSingleBenchmark.java new file mode 100644 index 0000000000000..6c6371d1e3cbf --- /dev/null +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableFunctionSingleBenchmark.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2024, 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 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.OperationsPerInvocation; +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.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +/** + * 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, jvmArgsAppend = { + "--enable-preview" +}) +@Threads(Threads.MAX) // Benchmark under contention +public class StableFunctionSingleBenchmark { + + private static final int SIZE = 100; + private static final Set SET = IntStream.range(0, SIZE).boxed().collect(Collectors.toSet()); + + private static final Map MAP = StableValue.map(SET, Function.identity()); + private static final Function FUNCTION = StableValue.function(SET, Function.identity()); + + private final Map map = StableValue.map(SET, Function.identity()); + private final Function function = StableValue.function(SET, Function.identity()); + + @Benchmark + public int map() { + return map.get(1); + } + + @Benchmark + public int function() { + return function.apply(1); + } + + @Benchmark + public int staticSMap() { + return MAP.get(1); + } + + @Benchmark + public int staticIntFunction() { + return FUNCTION.apply(1); + } + +} diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableIntFunctionBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableIntFunctionBenchmark.java index c28363cd94748..5c858c07b77a6 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/StableIntFunctionBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableIntFunctionBenchmark.java @@ -57,22 +57,21 @@ public class StableIntFunctionBenchmark { private static final int SIZE = 100; private static final IntFunction IDENTITY = i -> i; - private static final List STABLE = StableValue.list(SIZE, IDENTITY); + private static final List LIST = StableValue.list(SIZE, IDENTITY); private static final IntFunction INT_FUNCTION = StableValue.intFunction(SIZE, IDENTITY); - private final List stable = StableValue.list(SIZE, IDENTITY); + private final List list = StableValue.list(SIZE, IDENTITY); private final IntFunction intFunction = StableValue.intFunction(SIZE, IDENTITY); @Benchmark - public int stable() { + public int list() { int sum = 0; for (int i = 0; i < SIZE; i++) { - sum += stable.get(i); + sum += list.get(i); } return sum; } - @Benchmark public int intFunction() { int sum = 0; @@ -83,10 +82,10 @@ public int intFunction() { } @Benchmark - public int staticStable() { + public int staticList() { int sum = 0; for (int i = 0; i < SIZE; i++) { - sum += STABLE.get(i); + sum += LIST.get(i); } return sum; } diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableIntFunctionSingleBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableIntFunctionSingleBenchmark.java new file mode 100644 index 0000000000000..6e8c6357227e4 --- /dev/null +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableIntFunctionSingleBenchmark.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2024, 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 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.List; +import java.util.concurrent.TimeUnit; +import java.util.function.IntFunction; + +/** + * 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, jvmArgsAppend = { + "--enable-preview" +}) +@Threads(Threads.MAX) // Benchmark under contention +public class StableIntFunctionSingleBenchmark { + + private static final int SIZE = 100; + private static final IntFunction IDENTITY = i -> i; + + private static final List STABLE = StableValue.list(SIZE, IDENTITY); + private static final IntFunction INT_FUNCTION = StableValue.intFunction(SIZE, IDENTITY); + + private final List stable = StableValue.list(SIZE, IDENTITY); + private final IntFunction intFunction = StableValue.intFunction(SIZE, IDENTITY); + + @Benchmark + public int list() { + return stable.get(1); + } + + @Benchmark + public int intFunction() { + return intFunction.apply(1); + } + + @Benchmark + public int staticList() { + return STABLE.get(1); + } + + @Benchmark + public int staticIntFunction() { + return INT_FUNCTION.apply(1); + } + +} diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableSupplierBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableSupplierBenchmark.java index d587b7877c13e..584703243fc87 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/StableSupplierBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableSupplierBenchmark.java @@ -76,7 +76,6 @@ public int supplier() { return supplier.get() + supplier2.get(); } -/* @Benchmark public int staticStable() { return STABLE.orElseThrow() + STABLE2.orElseThrow(); @@ -86,7 +85,6 @@ public int staticStable() { public int staticSupplier() { return SUPPLIER.get() + SUPPLIER2.get(); } -*/ private static StableValue init(StableValue m, Integer value) { m.trySet(value); diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java index d8eec559e4e2c..c5e480d0a833f 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java @@ -55,6 +55,8 @@ public class StableValueBenchmark { private static final AtomicReference ATOMIC2 = new AtomicReference<>(VALUE2); private static final Holder HOLDER = new Holder(VALUE); private static final Holder HOLDER2 = new Holder(VALUE2); + private static final RecordHolder RECORD_HOLDER = new RecordHolder(VALUE); + private static final RecordHolder RECORD_HOLDER2 = new RecordHolder(VALUE2); private final StableValue stable = init(StableValue.of(), VALUE); private final StableValue stable2 = init(StableValue.of(), VALUE2); @@ -71,18 +73,7 @@ public class StableValueBenchmark { @Setup public void setup() { stableNull.trySet(null); - stableNull2.trySet(VALUE2); - // Create pollution - int sum = 0; - for (int i = 0; i < 500_000; i++) { - final int v = i; - Dcl dclX = new Dcl<>(() -> v); - sum += dclX.get(); - StableValue stableX = StableValue.of(); - stableX.trySet(i); - sum += stableX.orElseThrow(); - } - System.out.println("sum = " + sum); + stableNull2.trySet(null); } @Benchmark @@ -126,6 +117,11 @@ public int staticHolder() { return HOLDER.get() + HOLDER2.get(); } + @Benchmark + public int staticRecordHolder() { + return RECORD_HOLDER.get() + RECORD_HOLDER2.get(); + } + @Benchmark public int staticStable() { return STABLE.orElseThrow() + STABLE2.orElseThrow(); @@ -137,8 +133,6 @@ private static StableValue init(StableValue m, Integer value) return m; } - // The VM should be able to constant-fold the value given in the constructor - // because StableValue fields have a special meaning. private static final class Holder { private final StableValue delegate = StableValue.of(); @@ -153,6 +147,20 @@ int get() { } + private record RecordHolder(StableValue delegate) { + + RecordHolder(int value) { + this(StableValue.of()); + delegate.setOrThrow(value); + } + + int get() { + return delegate.orElseThrow(); + } + + } + + // Handles null values private static class Dcl implements Supplier { From df4ef35ce1e1fd9dc011c3217ff7da8fa93959b1 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 31 Mar 2025 18:02:44 +0200 Subject: [PATCH 222/327] Add MethodHandle benchmark --- .../stable/StableMethodHandleBenchmark.java | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 test/micro/org/openjdk/bench/java/lang/stable/StableMethodHandleBenchmark.java diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableMethodHandleBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableMethodHandleBenchmark.java new file mode 100644 index 0000000000000..032631f16eef5 --- /dev/null +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableMethodHandleBenchmark.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2024, 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 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.OperationsPerInvocation; +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.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.function.IntFunction; + +/** + * 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, jvmArgsAppend = { + "--enable-preview" +}) +@Threads(Threads.MAX) // Benchmark under contention +public class StableMethodHandleBenchmark { + + private static final MethodHandle FINAL_MH = identityHandle(); + private static final StableValue STABLE_MH; + private static MethodHandle mh = identityHandle(); + + static { + STABLE_MH = StableValue.of(); + STABLE_MH.setOrThrow(identityHandle()); + } + + @Benchmark + public int finalMh() throws Throwable { + return (int) FINAL_MH.invokeExact(1); + } + + @Benchmark + public int stableMh() throws Throwable { + return (int)STABLE_MH.orElseThrow().invokeExact(1); + } + + @Benchmark + public int mh() throws Throwable { + return (int)mh.invokeExact(1); + } + + static MethodHandle identityHandle() { + var lookup = MethodHandles.lookup(); + try { + return lookup.findStatic(StableMethodHandleBenchmark.class, "identity", MethodType.methodType(int.class, int.class)); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + + private static int identity(int value) { + return value; + } + +} From f7f10fa104fa16ea0c5bbba1db78a4533b2feffb Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 1 Apr 2025 11:32:46 +0200 Subject: [PATCH 223/327] Add benchmarks and update copyright years --- .../lang/stable/StableFunctionBenchmark.java | 2 +- .../stable/StableFunctionSingleBenchmark.java | 2 +- .../stable/StableIntFunctionBenchmark.java | 2 +- .../StableIntFunctionSingleBenchmark.java | 2 +- .../stable/StableMethodHandleBenchmark.java | 27 +++++++++++++------ .../lang/stable/StableSupplierBenchmark.java | 2 +- .../lang/stable/StableValueBenchmark.java | 4 +-- 7 files changed, 26 insertions(+), 15 deletions(-) diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableFunctionBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableFunctionBenchmark.java index 381780fd9ae38..b59532c8cf743 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/StableFunctionBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableFunctionBenchmark.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 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 diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableFunctionSingleBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableFunctionSingleBenchmark.java index 6c6371d1e3cbf..d0a621f91fc00 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/StableFunctionSingleBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableFunctionSingleBenchmark.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 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 diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableIntFunctionBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableIntFunctionBenchmark.java index 5c858c07b77a6..7eee8f903112b 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/StableIntFunctionBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableIntFunctionBenchmark.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 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 diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableIntFunctionSingleBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableIntFunctionSingleBenchmark.java index 6e8c6357227e4..ca29805468155 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/StableIntFunctionSingleBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableIntFunctionSingleBenchmark.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 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 diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableMethodHandleBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableMethodHandleBenchmark.java index 032631f16eef5..9016a20d8241e 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/StableMethodHandleBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableMethodHandleBenchmark.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * 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 @@ -23,12 +23,12 @@ package org.openjdk.bench.java.lang.stable; +import org.openjdk.bench.java.lang.stable.StableValueBenchmark.Dcl; 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.OperationsPerInvocation; import org.openjdk.jmh.annotations.OutputTimeUnit; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.State; @@ -38,9 +38,8 @@ import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; -import java.util.List; import java.util.concurrent.TimeUnit; -import java.util.function.IntFunction; +import java.util.concurrent.atomic.AtomicReference; /** * Benchmark measuring StableValue performance @@ -59,25 +58,37 @@ public class StableMethodHandleBenchmark { private static final MethodHandle FINAL_MH = identityHandle(); private static final StableValue STABLE_MH; private static MethodHandle mh = identityHandle(); + private static final Dcl DCL = new Dcl<>(StableMethodHandleBenchmark::identityHandle); + private static final AtomicReference ATOMIC_REFERENCE = new AtomicReference<>(identityHandle()); static { STABLE_MH = StableValue.of(); STABLE_MH.setOrThrow(identityHandle()); } + @Benchmark + public int atomic() throws Throwable { + return (int) ATOMIC_REFERENCE.get().invokeExact(1); + } + + @Benchmark + public int dcl() throws Throwable { + return (int) DCL.get().invokeExact(1); + } + @Benchmark public int finalMh() throws Throwable { return (int) FINAL_MH.invokeExact(1); } @Benchmark - public int stableMh() throws Throwable { - return (int)STABLE_MH.orElseThrow().invokeExact(1); + public int nonFinalMh() throws Throwable { + return (int) mh.invokeExact(1); } @Benchmark - public int mh() throws Throwable { - return (int)mh.invokeExact(1); + public int stableMh() throws Throwable { + return (int) STABLE_MH.orElseThrow().invokeExact(1); } static MethodHandle identityHandle() { diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableSupplierBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableSupplierBenchmark.java index 584703243fc87..3777a6bb491b7 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/StableSupplierBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableSupplierBenchmark.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 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 diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java index c5e480d0a833f..d58d2727e2424 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 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 @@ -162,7 +162,7 @@ int get() { // Handles null values - private static class Dcl implements Supplier { + public static class Dcl implements Supplier { private final Supplier supplier; From dfb940be33f88cbd3aeba2f59faed02e3ae7ea4a Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 1 Apr 2025 11:58:54 +0200 Subject: [PATCH 224/327] Add additional benchmarks with maps holding method handles --- .../lang/stable/StableMethodHandleBenchmark.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableMethodHandleBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableMethodHandleBenchmark.java index 9016a20d8241e..92678b89e4ffa 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/StableMethodHandleBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableMethodHandleBenchmark.java @@ -38,6 +38,9 @@ import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; @@ -60,10 +63,13 @@ public class StableMethodHandleBenchmark { private static MethodHandle mh = identityHandle(); private static final Dcl DCL = new Dcl<>(StableMethodHandleBenchmark::identityHandle); private static final AtomicReference ATOMIC_REFERENCE = new AtomicReference<>(identityHandle()); + private static final Map MAP = new ConcurrentHashMap<>(); + private static final Map STABLE_MAP = StableValue.map(Set.of("identityHandle"), _ -> identityHandle()); static { STABLE_MH = StableValue.of(); STABLE_MH.setOrThrow(identityHandle()); + MAP.put("identityHandle", identityHandle()); } @Benchmark @@ -81,11 +87,21 @@ public int finalMh() throws Throwable { return (int) FINAL_MH.invokeExact(1); } + @Benchmark + public int map() throws Throwable { + return (int) MAP.get("identityHandle").invokeExact(1); + } + @Benchmark public int nonFinalMh() throws Throwable { return (int) mh.invokeExact(1); } + @Benchmark + public int stableMap() throws Throwable { + return (int) STABLE_MAP.get("identityHandle").invokeExact(1); + } + @Benchmark public int stableMh() throws Throwable { return (int) STABLE_MH.orElseThrow().invokeExact(1); From 1cc203267cb09be2c4dd20ce1eb857ed925871c4 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 1 Apr 2025 14:30:01 +0200 Subject: [PATCH 225/327] Make toString for reversed and sublist lazy --- .../java/util/ImmutableCollections.java | 11 +++- .../java/util/ReverseOrderListView.java | 33 +++++++----- .../jdk/internal/lang/stable/StableUtil.java | 12 ++++- .../java/lang/StableValue/StableListTest.java | 50 ++++++++++++++++--- 4 files changed, 85 insertions(+), 21 deletions(-) diff --git a/src/java.base/share/classes/java/util/ImmutableCollections.java b/src/java.base/share/classes/java/util/ImmutableCollections.java index f060910778128..bc9fa014bc818 100644 --- a/src/java.base/share/classes/java/util/ImmutableCollections.java +++ b/src/java.base/share/classes/java/util/ImmutableCollections.java @@ -569,6 +569,15 @@ public T[] toArray(T[] a) { } return array; } + + @Override + public String toString() { + if (root instanceof StableList stableList) { + return StableUtil.renderElements(root, "StableList", stableList.delegates, offset, size); + } else { + return super.toString(); + } + } } @jdk.internal.ValueBased @@ -774,7 +783,7 @@ static final class StableList extends AbstractImmutableList { @Stable private final IntFunction mapper; @Stable - private final StableValueImpl[] delegates; + final StableValueImpl[] delegates; StableList(int size, IntFunction mapper) { this.mapper = mapper; diff --git a/src/java.base/share/classes/java/util/ReverseOrderListView.java b/src/java.base/share/classes/java/util/ReverseOrderListView.java index f48b41920c930..9f4195d606314 100644 --- a/src/java.base/share/classes/java/util/ReverseOrderListView.java +++ b/src/java.base/share/classes/java/util/ReverseOrderListView.java @@ -32,6 +32,9 @@ import java.util.function.UnaryOperator; import java.util.stream.Stream; import java.util.stream.StreamSupport; + +import jdk.internal.lang.stable.StableUtil; +import jdk.internal.lang.stable.StableValueImpl; import jdk.internal.util.ArraysSupport; /** @@ -296,18 +299,24 @@ public T[] toArray(IntFunction generator) { // copied from AbstractCollection public String toString() { - Iterator it = iterator(); - if (! it.hasNext()) - return "[]"; - - StringBuilder sb = new StringBuilder(); - sb.append('['); - for (;;) { - E e = it.next(); - sb.append(e == this ? "(this Collection)" : e); - if (! it.hasNext()) - return sb.append(']').toString(); - sb.append(',').append(' '); + if (base instanceof ImmutableCollections.StableList stableList) { + final StableValueImpl[] reversed = ArraysSupport.reverse( + Arrays.copyOf(stableList.delegates, stableList.delegates.length)); + return StableUtil.renderElements(base, "Collection", reversed); + } else { + Iterator it = iterator(); + if (!it.hasNext()) + return "[]"; + + StringBuilder sb = new StringBuilder(); + sb.append('['); + for (; ; ) { + E e = it.next(); + sb.append(e == this ? "(this Collection)" : e); + if (!it.hasNext()) + return sb.append(']').toString(); + sb.append(',').append(' '); + } } } 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 17419cbb61143..ae66267181995 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 @@ -12,12 +12,20 @@ private StableUtil() {} public static String renderElements(Object self, String selfName, StableValueImpl[] delegates) { + return renderElements(self, selfName, delegates, 0, delegates.length); + } + + public static String renderElements(Object self, + String selfName, + StableValueImpl[] delegates, + int offset, + int length) { final StringBuilder sb = new StringBuilder(); sb.append("["); boolean first = true; - for (int i = 0; i < delegates.length; i++) { + for (int i = 0; i < length; i++) { if (first) { first = false; } else { sb.append(", "); } - final Object value = delegates[i].wrappedContentAcquire(); + final Object value = delegates[i + offset].wrappedContentAcquire(); if (value == self) { sb.append("(this ").append(selfName).append(")"); } else { diff --git a/test/jdk/java/lang/StableValue/StableListTest.java b/test/jdk/java/lang/StableValue/StableListTest.java index ad714858e627e..15792e043907f 100644 --- a/test/jdk/java/lang/StableValue/StableListTest.java +++ b/test/jdk/java/lang/StableValue/StableListTest.java @@ -46,6 +46,7 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import java.util.function.IntFunction; +import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; @@ -218,6 +219,7 @@ void iteratorPartial() { void subList() { var lazy = newList(); var lazySubList = lazy.subList(1, SIZE); + assertInstanceOf(RandomAccess.class, lazySubList); var regularList = newRegularList(); var regularSubList = regularList.subList(1, SIZE); assertEquals(regularSubList, lazySubList); @@ -235,12 +237,42 @@ void subList2() { @Test void subListToString() { - var lazy = newList(); - var lazySubList = lazy.subList(1, SIZE); - var regularList = newRegularList(); - var regularSubList = regularList.subList(1, SIZE); - // There is no requirement that the lazy sub list's toString method should be lazy - assertEquals(regularSubList.toString(), lazySubList.toString()); + subListToString0(newList()); + subListToString0(newList().subList(1, SIZE)); + subListToString0(newList().subList(1, SIZE).subList(0, SIZE - 2)); + } + + void subListToString0(List subList) { + assertEquals(asString(".unset", subList), subList.toString()); + + var first = subList.getFirst(); + assertEquals(asString(first.toString(), subList), subList.toString()); + } + + @Test + void reversed() { + var reversed = newList().reversed(); + assertInstanceOf(RandomAccess.class, reversed); + assertEquals(SIZE - 1, reversed.getFirst()); + assertEquals(0, reversed.getLast()); + + var reversed2 = reversed.reversed(); + assertEquals(0, reversed2.getFirst()); + assertEquals(SIZE - 1, reversed2.getLast()); + } + + @Test + void reversedToString() { + var reversed = newList().reversed(); + subListToString0(reversed); + } + + @Test + void subListReversedToString() { + var list = newList().subList(1, SIZE - 1).reversed(); + // This combination is not lazy. There has to be a limit somewhere. + var regularList = newRegularList().subList(1, SIZE - 1).reversed(); + assertEquals(regularList.toString(), list.toString()); } @Test @@ -384,4 +416,10 @@ static List newRegularList() { return IntStream.range(0, SIZE).boxed().toList(); } + static String asString(String first, List list) { + return "[" + first + ", " + Stream.generate(() -> ".unset") + .limit(list.size() - 1) + .collect(Collectors.joining(", ")) + "]"; + } + } From be84045a5af5f16297750411b98a1fb3301e9009 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 1 Apr 2025 15:22:16 +0200 Subject: [PATCH 226/327] Add lazy toSting for StableMap::values --- .../java/util/ImmutableCollections.java | 26 +++++++++++++++++++ .../java/lang/StableValue/StableMapTest.java | 11 ++++++++ 2 files changed, 37 insertions(+) diff --git a/src/java.base/share/classes/java/util/ImmutableCollections.java b/src/java.base/share/classes/java/util/ImmutableCollections.java index bc9fa014bc818..302d372167fb8 100644 --- a/src/java.base/share/classes/java/util/ImmutableCollections.java +++ b/src/java.base/share/classes/java/util/ImmutableCollections.java @@ -1569,6 +1569,32 @@ public void accept(Entry> inner) { } } + @Override + public Collection values() { + return new StableMapValues(); + } + + final class StableMapValues extends AbstractCollection { + @Override public Iterator iterator() { return new ValueIterator(); } + @Override public int size() { return StableMap.this.size(); } + @Override public boolean isEmpty() { return StableMap.this.isEmpty();} + @Override public void clear() { StableMap.this.clear(); } + @Override public boolean contains(Object v) { return StableMap.this.containsValue(v); } + + @Override + public String toString() { + final StableValueImpl[] values = delegate.values().toArray(new IntFunction[]>() { + @Override + public StableValueImpl[] apply(int len) { + @SuppressWarnings("unchecked") + var array = (StableValueImpl[]) Array.newInstance(StableValueImpl.class, len); + return array; + } + }); + return StableUtil.renderElements(StableMap.this, "StableMap", values); + } + } + @Override public String toString() { return StableUtil.renderMappings(this, "StableMap", delegate.entrySet()); diff --git a/test/jdk/java/lang/StableValue/StableMapTest.java b/test/jdk/java/lang/StableValue/StableMapTest.java index 814c25cfd1353..e8c22d79ef5ee 100644 --- a/test/jdk/java/lang/StableValue/StableMapTest.java +++ b/test/jdk/java/lang/StableValue/StableMapTest.java @@ -39,6 +39,7 @@ import java.util.HashSet; import java.util.IdentityHashMap; import java.util.Map; +import java.util.RandomAccess; import java.util.Set; import java.util.function.Consumer; import java.util.function.Function; @@ -207,6 +208,16 @@ void values() { assertThrows(UnsupportedOperationException.class, () -> values.retainAll(Set.of(KEY))); } + @Test + void valuesToString() { + var map = newMap(); + var values = map.values(); + assertEquals("[.unset, .unset, .unset]", values.toString()); + map.get(KEY); + var afterGet = values.toString(); + assertTrue(afterGet.contains(Integer.toString(KEY)), afterGet); + } + @Test void iteratorNext() { Set encountered = new HashSet<>(); From 34fd718d348d2029ef4f84902f222d9ace16c324 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 2 Apr 2025 10:59:10 +0200 Subject: [PATCH 227/327] Add stable collections to MOAT tests --- .../java/util/ImmutableCollections.java | 4 ++-- .../lang/stable/StableEnumFunction.java | 2 +- .../internal/lang/stable/StableFunction.java | 2 +- .../jdk/internal/lang/stable/StableUtil.java | 7 ++++--- .../java/lang/StableValue/StableMapTest.java | 3 +++ test/jdk/java/util/Collection/MOAT.java | 21 ++++++++++++++++--- 6 files changed, 29 insertions(+), 10 deletions(-) diff --git a/src/java.base/share/classes/java/util/ImmutableCollections.java b/src/java.base/share/classes/java/util/ImmutableCollections.java index 302d372167fb8..2cc802c42dd2e 100644 --- a/src/java.base/share/classes/java/util/ImmutableCollections.java +++ b/src/java.base/share/classes/java/util/ImmutableCollections.java @@ -1530,7 +1530,7 @@ final class StableMapEntrySet extends AbstractImmutableSet> { @Override public String toString() { - return StableUtil.renderMappings(this, "StableSet", delegateEntrySet); + return StableUtil.renderMappings(this, "StableSet", delegateEntrySet, false); } @jdk.internal.ValueBased @@ -1597,7 +1597,7 @@ public StableValueImpl[] apply(int len) { @Override public String toString() { - return StableUtil.renderMappings(this, "StableMap", delegate.entrySet()); + return StableUtil.renderMappings(this, "StableMap", delegate.entrySet(), true); } } diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java index 92a06396489d1..8d03a7c773a74 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java @@ -94,7 +94,7 @@ public String toString() { entries.add(new AbstractMap.SimpleImmutableEntry<>(enumElements[ordinal], delegates[i])); } } - return StableUtil.renderMappings(this, "StableFunction", entries); + return StableUtil.renderMappings(this, "StableFunction", entries, true); } @SuppressWarnings("unchecked") diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableFunction.java index 8cda536fafdea..c03949c1a728b 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableFunction.java @@ -72,7 +72,7 @@ public boolean equals(Object obj) { @Override public String toString() { - return StableUtil.renderMappings(this, "StableFunction", values.entrySet()); + return StableUtil.renderMappings(this, "StableFunction", values.entrySet(), true); } public static StableFunction of(Set inputs, 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 ae66267181995..a4fe74791b647 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 @@ -38,9 +38,10 @@ public static String renderElements(Object self, public static String renderMappings(Object self, String selfName, - Iterable>> delegates) { + Iterable>> delegates, + boolean curly) { final StringBuilder sb = new StringBuilder(); - sb.append("{"); + sb.append(curly ? "{" : "["); boolean first = true; for (var e : delegates) { if (first) { first = false; } else { sb.append(", "); } @@ -52,7 +53,7 @@ public static String renderMappings(Object self, sb.append(StableValueImpl.renderWrapped(value)); } } - sb.append("}"); + sb.append(curly ? "}" : "]"); return sb.toString(); } diff --git a/test/jdk/java/lang/StableValue/StableMapTest.java b/test/jdk/java/lang/StableValue/StableMapTest.java index e8c22d79ef5ee..9dc1c81e06cf3 100644 --- a/test/jdk/java/lang/StableValue/StableMapTest.java +++ b/test/jdk/java/lang/StableValue/StableMapTest.java @@ -175,6 +175,9 @@ void entrySetToString() { for (var key : KEYS) { assertTrue(entrySet.toString().contains(key + "=.unset")); } + assertTrue(entrySet.toString().startsWith("[")); + assertTrue(entrySet.toString().endsWith("]")); + map.get(KEY); for (var key : KEYS) { if (key.equals(KEY)) { diff --git a/test/jdk/java/util/Collection/MOAT.java b/test/jdk/java/util/Collection/MOAT.java index d281e5db125f8..baadb2d10c556 100644 --- a/test/jdk/java/util/Collection/MOAT.java +++ b/test/jdk/java/util/Collection/MOAT.java @@ -30,7 +30,8 @@ * @summary Run many tests on many Collection and Map implementations * @author Martin Buchholz * @modules java.base/java.util:open - * @run main MOAT + * @compile --enable-preview -source ${jdk.version} MOAT.java + * @run main/othervm --enable-preview MOAT * @key randomness */ @@ -217,10 +218,15 @@ public static void realMain(String[] args) { // Immutable List testEmptyList(List.of()); testEmptyList(List.of().subList(0,0)); + testEmptyList(StableValue.list(0, i -> i)); + testEmptyList(StableValue.list(3, i -> i).subList(0, 0)); testListMutatorsAlwaysThrow(List.of()); testListMutatorsAlwaysThrow(List.of().subList(0,0)); + testListMutatorsAlwaysThrow(StableValue.list(0, i -> i)); testEmptyListMutatorsAlwaysThrow(List.of()); testEmptyListMutatorsAlwaysThrow(List.of().subList(0,0)); + testEmptyListMutatorsAlwaysThrow(StableValue.list(0, i -> i)); + testEmptyListMutatorsAlwaysThrow(StableValue.list(3, i -> i).subList(0, 0)); for (List list : Arrays.asList( List.of(), List.of(1), @@ -242,7 +248,10 @@ public static void realMain(String[] args) { Stream.of((Integer)null).toList(), Stream.of(1, null).toList(), Stream.of(1, null, 3).toList(), - Stream.of(1, null, 3, 4).toList())) { + Stream.of(1, null, 3, 4).toList(), + StableValue.list(0, i -> i), + StableValue.list(3, i -> i), + StableValue.list(10, i -> i))) { testCollection(list); testImmutableList(list); testListMutatorsAlwaysThrow(list); @@ -354,6 +363,9 @@ public static void realMain(String[] args) { testEmptyMap(Map.of()); testMapMutatorsAlwaysThrow(Map.of()); testEmptyMapMutatorsAlwaysThrow(Map.of()); + testEmptyMap(StableValue.map(Set.of(), k -> k)); + testMapMutatorsAlwaysThrow(StableValue.map(Set.of(), k -> k)); + testEmptyMapMutatorsAlwaysThrow(StableValue.map(Set.of(), k -> k)); for (Map map : Arrays.asList( Map.of(), Map.of(1, 101), @@ -366,7 +378,10 @@ public static void realMain(String[] args) { Map.of(1, 101, 2, 202, 3, 303, 4, 404, 5, 505, 6, 606, 7, 707, 8, 808), Map.of(1, 101, 2, 202, 3, 303, 4, 404, 5, 505, 6, 606, 7, 707, 8, 808, 9, 909), Map.of(1, 101, 2, 202, 3, 303, 4, 404, 5, 505, 6, 606, 7, 707, 8, 808, 9, 909, 10, 1010), - Map.ofEntries(ea))) { + Map.ofEntries(ea), + StableValue.map(Set.of(), k -> k), + StableValue.map(Set.of(1), k -> k), + StableValue.map(Set.of(1, 2, 3), k -> k))) { testMap(map); testImmutableMap(map); testMapMutatorsAlwaysThrow(map); From 62cbc5904c97a83758140d3aac3894df52411beb Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 2 Apr 2025 11:08:26 +0200 Subject: [PATCH 228/327] Update StableValue main description --- src/java.base/share/classes/java/lang/StableValue.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 02fa88c7fccc8..081be14e287f5 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -47,7 +47,7 @@ import java.util.function.Supplier; /** - * A stable value is a deferred holder of shallowly immutable content. + * A stable value is a holder of shallowly immutable content that can be lazily computed. *

    * A {@code StableValue} can be created using the factory method * {@linkplain StableValue#of() {@code StableValue.of()}}. When created this way, From 0e4ebba969b73497a6622931f1fb38e62e86132a Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 2 Apr 2025 11:22:44 +0200 Subject: [PATCH 229/327] Use static final SV in the snippets --- src/java.base/share/classes/java/lang/StableValue.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 081be14e287f5..e78f708bfb58f 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -75,7 +75,7 @@ * * // Creates a new unset stable value with no content * // @link substring="of" target="#of" : - * private final StableValue logger = StableValue.of(); + * private static final StableValue logger = StableValue.of(); * * private Logger getLogger() { * if (!logger.isSet()) { @@ -92,8 +92,8 @@ *} *

    * Note that the holder value can only be set at most once. - * In the example above, the {@code logger} field is declared {@code final} which is - * a prerequisite for being treated as a constant by the JVM. + * In the example above, the {@code logger} field is declared {@code static final} which + * is a prerequisite for being treated as a constant by the JVM. * *

    * To guarantee that, even under races, only one instance of {@code Logger} is ever @@ -107,7 +107,7 @@ * * // Creates a new unset stable value with no content * // @link substring="of" target="#of" : - * private final StableValue logger = StableValue.of(); + * private static final StableValue logger = StableValue.of(); * * private Logger getLogger() { * return logger.orElseSet( () -> Logger.create(Component.class) ); @@ -143,7 +143,7 @@ * {@snippet lang = java: * public class Component { * - * private final Supplier logger = + * private static final Supplier logger = * // @link substring="supplier" target="#supplier(Supplier)" : * StableValue.supplier( () -> Logger.getLogger(Component.class) ); * From ab842789aa4d5bb0bca42cb6b4785d92d3274af0 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 2 Apr 2025 13:32:32 +0200 Subject: [PATCH 230/327] Improve examples --- .../share/classes/java/lang/StableValue.java | 77 +++++++++++++++---- 1 file changed, 61 insertions(+), 16 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index e78f708bfb58f..f64b66386edd1 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -49,7 +49,7 @@ /** * A stable value is a holder of shallowly immutable content that can be lazily computed. *

    - * A {@code StableValue} can be created using the factory method + * A {@code StableValue} is typically created using the factory method * {@linkplain StableValue#of() {@code StableValue.of()}}. When created this way, * the stable value is unset, which means it holds no content. * Its content, of type {@code T}, can be set by calling @@ -161,21 +161,33 @@ * uses it to compute a result that is then cached by the backing stable value storage * for that parameter value. A stable int function is created via the * {@linkplain StableValue#intFunction(int, IntFunction) StableValue.intFunction()} - * factory. Upon creation, the input range (i.e. [0, size)) is specified together with - * an original {@linkplain IntFunction} which is invoked at most once per input value. In - * effect, the stable int function will act like a cache for the original {@linkplain IntFunction}: + * factory. Upon creation, the input range (i.e. {@code [0, size)}) is specified together + * with an original {@linkplain IntFunction} which is invoked at most once per input value. + * In effect, the stable int function will act like a cache for the original + * {@linkplain IntFunction}: * * {@snippet lang = java: * public final class SqrtUtil { * * private SqrtUtil(){} * + * private static final int CACHED_SIZE = 10; + * * private static final IntFunction SQRT = * // @link substring="intFunction" target="#intFunction(int,IntFunction)" : - * StableValue.intFunction(10, StrictMath::sqrt); + * StableValue.intFunction(CACHED_SIZE, StrictMath::sqrt); + * + * public static double sqrt(int a) { + * if (a < CACHED_SIZE) { + * return SQRT.apply(a); + * } else { + * return StrictMath.sqrt(a); + * } + * } * - * public static double sqrt9() { - * return SQRT.apply(9); // May eventually constant fold to 3.0 at runtime + * public static void computeSomeValues() { + * double sqrt9 = sqrt(9); // May eventually constant fold to 3.0 at runtime + * double sqrt81 = sqrt(81); // Will not constant fold * } * * } @@ -194,12 +206,23 @@ * * private SqrtUtil(){} * + * private static final Set CACHED_KEYS = Set.of(1, 2, 4, 8, 16, 32); + * * private static final Function SQRT = * // @link substring="function" target="#function(Set,Function)" : - * StableValue.function(Set.of(1, 2, 4, 8, 16, 32), StrictMath::sqrt); + * StableValue.function(CACHED_KEYS, StrictMath::sqrt); + * + * public static double sqrt(int a) { + * if (CACHED_KEYS.contains(a)) { + * return SQRT.apply(a); + * } else { + * return StrictMath.sqrt(a); + * } + * } * - * public static double sqrt16() { - * return SQRT.apply(16); // May eventually constant fold to 4.0 at runtime + * public static double computeSomeValues() { + * double sqrt16 = sqrt(16); // May eventually constant fold to 4.0 at runtime + * double sqrt81 = sqrt(81); // Will not constant fold * } * * } @@ -214,15 +237,26 @@ * {@snippet lang = java: * public final class SqrtUtil { * + * private static final int CACHED_SIZE = 10; + * * private SqrtUtil(){} * * private static final List SQRT = * // @link substring="list" target="#list(int,IntFunction)" : - * StableValue.list(10, StrictMath::sqrt); + * StableValue.list(CACHED_SIZE, StrictMath::sqrt); + * + * public static double sqrt(int a) { + * if (a < CACHED_SIZE) { + * return SQRT.get(a); + * } else { + * return StrictMath.sqrt(a); + * } + * } * - * public static double sqrt9() { - * return SQRT.get(9); // May eventually constant fold to 3.0 at runtime - * } + * public static void computeSomeValues() { + * double sqrt9 = sqrt(9); // May eventually constant fold to 3.0 at runtime + * double sqrt81 = sqrt(81); // Will not constant fold + * } * * } *} @@ -236,12 +270,23 @@ * * private SqrtUtil(){} * + * private static final Set CACHED_KEYS = Set.of(1, 2, 4, 8, 16, 32); + * * private static final Map SQRT = * // @link substring="map" target="#map(Set,Function)" : * StableValue.map(Set.of(1, 2, 4, 8, 16, 32), StrictMath::sqrt); * - * public static double sqrt16() { - * return SQRT.get(16); // May eventually constant fold to 4.0 at runtime + * public static double sqrt(int a) { + * if (CACHED_KEYS.contains(a)) { + * return SQRT.get(a); + * } else { + * return StrictMath.sqrt(a); + * } + * } + * + * public static double computeSomeValues() { + * double sqrt16 = sqrt(16); // May eventually constant fold to 4.0 at runtime + * double sqrt81 = sqrt(81); // Will not constant fold * } * * } From a1214f05e1aeb19686361895dff68e320c32ee8b Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 2 Apr 2025 13:40:57 +0200 Subject: [PATCH 231/327] Use @enablePreview in MOAT --- test/jdk/java/util/Collection/MOAT.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/jdk/java/util/Collection/MOAT.java b/test/jdk/java/util/Collection/MOAT.java index baadb2d10c556..a1bdc17b1a4bd 100644 --- a/test/jdk/java/util/Collection/MOAT.java +++ b/test/jdk/java/util/Collection/MOAT.java @@ -30,8 +30,8 @@ * @summary Run many tests on many Collection and Map implementations * @author Martin Buchholz * @modules java.base/java.util:open - * @compile --enable-preview -source ${jdk.version} MOAT.java - * @run main/othervm --enable-preview MOAT + * @enablePreview + * @run main MOAT * @key randomness */ From 769eda3e4d02cc7d009664b8cd2b4f9565510cc2 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 2 Apr 2025 13:42:15 +0200 Subject: [PATCH 232/327] Wip on condy --- .../lang/stable/StableMethodHandleBenchmark.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableMethodHandleBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableMethodHandleBenchmark.java index 92678b89e4ffa..5017fb4e11a47 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/StableMethodHandleBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableMethodHandleBenchmark.java @@ -35,15 +35,20 @@ import org.openjdk.jmh.annotations.Threads; import org.openjdk.jmh.annotations.Warmup; +import java.lang.classfile.CodeBuilder; +import java.lang.classfile.constantpool.ConstantPoolBuilder; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; +import static java.lang.constant.ConstantDescs.*; + /** * Benchmark measuring StableValue performance */ @@ -107,6 +112,14 @@ public int stableMh() throws Throwable { return (int) STABLE_MH.orElseThrow().invokeExact(1); } + Object cp() { + CodeBuilder cob = null; + ConstantPoolBuilder cp = ConstantPoolBuilder.of(); + cob.ldc(cp.constantDynamicEntry(cp.bsmEntry(cp.methodHandleEntry(BSM_CLASS_DATA), List.of()), + cp.nameAndTypeEntry(DEFAULT_NAME, CD_MethodHandle))); + return null; + } + static MethodHandle identityHandle() { var lookup = MethodHandles.lookup(); try { From de3d5616997d206b073916b871f992267c480463 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 2 Apr 2025 13:58:33 +0200 Subject: [PATCH 233/327] Add a dependency layout image snippet --- .../share/classes/java/lang/StableValue.java | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index f64b66386edd1..2984f20138161 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -357,9 +357,21 @@ * constant-fold expressions like {@code Fibonacci.fib(10)}. *

    * The fibonacci example above is a dependency graph with no circular dependencies (i.e., - * it is a dependency tree). If there are circular dependencies in a dependency graph, - * a stable value will eventually throw a {@linkplain StackOverflowError} upon referencing - * elements in a circularity. + * it is a dependency tree): + *{@snippet lang=text : + * + * ___________fib(5)____________ + * / \ + * ____fib(4)____ ____fib(3)____ + * / \ / \ + * fib(3) fib(2) fib(2) fib(1) + * / \ / \ / \ + * fib(2) fib(1) fib(1) fib(0) fib(1) fib(0) + *} + * + * If there are circular dependencies in a dependency graph, a stable value will + * eventually throw a {@linkplain StackOverflowError} upon referencing elements in + * a circularity. * *

    Thread Safety

    * The content of a stable value is guaranteed to be set at most once. If competing From d69d7c35e9116eeec90e68d11fd1cb25fc884f50 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 2 Apr 2025 14:35:49 +0200 Subject: [PATCH 234/327] Specify what is a successful read/write operation --- .../share/classes/java/lang/StableValue.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 2984f20138161..2cbc80fddb2c1 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -381,7 +381,19 @@ * The at-most-once write operation on a stable value that succeeds * (e.g. {@linkplain #trySet(Object) trySet()}) * {@linkplain java.util.concurrent##MemoryVisibility happens-before} - * any subsequent read operation (e.g. {@linkplain #orElseThrow()}). + * any subsequent read operation (e.g. {@linkplain #orElseThrow()}) that is successful. + * A successful write operation can be either: + *
      + *
    • a {@link #trySet(Object)} that returns {@code true},
    • + *
    • a {@link #setOrThrow(Object)} that does not throw, or
    • + *
    • an {@link #orElseSet(Supplier)} that successfully runs the supplier
    • + *
    + * A successful read operation can be either: + *
      + *
    • a {@link #orElseThrow()} that does not throw,
    • + *
    • a {@link #orElse(Object) orElse(other)} that does not return the {@code other} value, or
    • + *
    • an {@link #orElseSet(Supplier)} that does not {@code throw}
    • + *
    *

    * The method {@linkplain StableValue#orElseSet(Supplier) orElseSet()} guarantees that * the provided {@linkplain Supplier} is invoked successfully at most once even under From 55f2c90ee861c318428dc1a09e7fb13ae547ee75 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 2 Apr 2025 14:46:36 +0200 Subject: [PATCH 235/327] Rename variable and clarify get operations --- .../share/classes/java/lang/StableValue.java | 21 ++++++++++--------- .../internal/lang/stable/StableValueImpl.java | 10 ++++----- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 2cbc80fddb2c1..ca3056eac6ae5 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -91,7 +91,7 @@ * } *} *

    - * Note that the holder value can only be set at most once. + * Note that the content can only be set at most once. * In the example above, the {@code logger} field is declared {@code static final} which * is a prerequisite for being treated as a constant by the JVM. * @@ -437,14 +437,15 @@ public sealed interface StableValue // Principal methods /** - * {@return {@code true} if the content was set to the provided {@code value}, - * {@code false} otherwise} + * Tries to set the content of this StableValue to the provided {@code content}. *

    - * When this method returns, the content is always set. + * When this method returns, the content of this StableValue is always set. * - * @param value to set + * @return {@code true} if the content of this StableValue was set to the + * provided {@code content}, {@code false} otherwise + * @param content to set */ - boolean trySet(T value); + boolean trySet(T content); /** * {@return the content if set, otherwise, returns the provided {@code other} value} @@ -495,15 +496,15 @@ public sealed interface StableValue // Convenience methods /** - * Sets the content to the provided {@code value}, or, if already set, - * throws {@code IllegalStateException}. + * Sets the content of this StableValue to the provided {@code content}, or, if + * already set, throws {@code IllegalStateException}. *

    * When this method returns (or throws an exception), the content is always set. * - * @param value to set + * @param content to set * @throws IllegalStateException if the content was already set */ - void setOrThrow(T value); + void setOrThrow(T content); // Object methods 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 43bed39872857..6cd357f15dd1c 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 @@ -72,7 +72,7 @@ private StableValueImpl() {} @ForceInline @Override - public boolean trySet(T value) { + public boolean trySet(T content) { if (wrappedContentAcquire() != null) { return false; } @@ -81,15 +81,15 @@ public boolean trySet(T value) { // Mutual exclusion is required here as `orElseSet` might also // attempt to modify the `wrappedValue` synchronized (this) { - return wrapAndSet(value); + return wrapAndSet(content); } } @ForceInline @Override - public void setOrThrow(T value) { - if (!trySet(value)) { - throw new IllegalStateException("Cannot set the content to " + value + + public void setOrThrow(T content) { + if (!trySet(content)) { + throw new IllegalStateException("Cannot set the content to " + content + " because the content is already set: " + orElseThrow()); } } From 260086db0917c15d6670ee1e68cd570d06f2e43f Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 2 Apr 2025 15:02:47 +0200 Subject: [PATCH 236/327] Address various comments --- .../share/classes/java/lang/StableValue.java | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index ca3056eac6ae5..42aba5d88508c 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -444,6 +444,8 @@ public sealed interface StableValue * @return {@code true} if the content of this StableValue was set to the * provided {@code content}, {@code false} otherwise * @param content to set + * @throws IllegalStateException if this method is invoked directly by a supplier + * provided to the {@link #orElseSet(Supplier)} method. */ boolean trySet(T content); @@ -483,9 +485,8 @@ public sealed interface StableValue *

    * When this method returns successfully, the content is always set. *

    - * This method will always return the same witness value regardless if invoked by - * several threads. Also, the provided {@code supplier} will only be invoked once even - * if invoked from several threads unless the {@code supplier} throws an exception. + * The provided {@code supplier} will only be invoked once even if invoked from + * several threads unless the {@code supplier} throws an exception. * * @param supplier to be used for computing the content, if not previously set * @throws IllegalStateException if the provided {@code supplier} recursively @@ -658,7 +659,7 @@ static Function function(Set inputs, * {@return a new stable list with the provided {@code size}} *

    * The returned list is an {@linkplain Collection##unmodifiable unmodifiable} list - * whose size is known at construction. The list's elements are computed via the + * with the provided {@code size}. The list's elements are computed via the * provided {@code mapper} when they are first accessed * (e.g. via {@linkplain List#get(int) List::get}). *

    @@ -670,8 +671,11 @@ static Function function(Set inputs, * If the provided {@code mapper} throws an exception, it is relayed to the initial * caller and no value for the element is recorded. *

    - * The returned list and its {@link List#subList(int, int) subList} views implement - * the {@link RandomAccess} interface. + * Any direct {@link List#subList(int, int) subList} or {@link List#reversed()} views + * of the returned list are also stable. + *

    + * The returned list and its {@link List#subList(int, int) subList} or + * {@link List#reversed()} views implement the {@link RandomAccess} interface. *

    * The returned list is not {@link Serializable} and, as it is unmodifiable, does * not implement the {@linkplain Collection##optional-operation optional operations} From b8d52436cf2bb2234b0b54fb32e2c19c83d6cf85 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 2 Apr 2025 15:11:12 +0200 Subject: [PATCH 237/327] Mention the impact of toString and equals --- src/java.base/share/classes/java/lang/StableValue.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 42aba5d88508c..3b8725f6f91a1 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -414,6 +414,11 @@ * @implNote A {@linkplain StableValue} is mainly intended to be a non-public field in * a class and is usually neither exposed directly via accessors nor passed as * a method parameter. + * Stable functions and collections make all reasonable efforts to provide + * {@link Object#toString()} operations that do not trigger evaluation + * of the internal stable values when called. + * Stable collections have {@link Object#equals(Object)} operations that tries + * to minimize evaluation of the internal stable values when called. * As objects can be set via stable values but never removed, this can be a source * of unintended memory leaks. A stable value's content is * {@linkplain java.lang.ref##reachability strongly reachable}. Clients are From 5dbcd4d0e172fa8e298259d76e90c29e6e6f0e86 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 2 Apr 2025 15:19:23 +0200 Subject: [PATCH 238/327] Add info that Map#values and Map#entrySet are stable --- src/java.base/share/classes/java/lang/StableValue.java | 3 +++ test/jdk/java/lang/StableValue/StableMapTest.java | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 3b8725f6f91a1..4c5a7cd2e2462 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -720,6 +720,9 @@ static List list(int size, * If the provided {@code mapper} throws an exception, it is relayed to the initial * caller and no value associated with the provided key is recorded. *

    + * Any direct {@link Map#values()} or {@link Map#entrySet()} views + * of the returned map are also stable. + *

    * The returned map is not {@link Serializable} and, as it is unmodifiable, does * not implement the {@linkplain Collection##optional-operations optional operations} * in the {@linkplain Map} interface. diff --git a/test/jdk/java/lang/StableValue/StableMapTest.java b/test/jdk/java/lang/StableValue/StableMapTest.java index 9dc1c81e06cf3..f68b87d22990d 100644 --- a/test/jdk/java/lang/StableValue/StableMapTest.java +++ b/test/jdk/java/lang/StableValue/StableMapTest.java @@ -39,7 +39,6 @@ import java.util.HashSet; import java.util.IdentityHashMap; import java.util.Map; -import java.util.RandomAccess; import java.util.Set; import java.util.function.Consumer; import java.util.function.Function; From 56d9b3c017b38e66798bc5717eab59d76849e04a Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Thu, 3 Apr 2025 10:15:46 +0200 Subject: [PATCH 239/327] Address doc comments --- .../share/classes/java/lang/StableValue.java | 12 ++++++++++-- .../java/lang/StableValue/StableFunctionTest.java | 9 +++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 4c5a7cd2e2462..71668190e71f3 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -442,7 +442,9 @@ public sealed interface StableValue // Principal methods /** - * Tries to set the content of this StableValue to the provided {@code content}. + * Tries to set the content of this StableValue to the provided {@code content}. The + * content of this StableValue can only be set once, implying this method only returns + * {@code true} once. *

    * When this method returns, the content of this StableValue is always set. * @@ -478,7 +480,9 @@ public sealed interface StableValue * content using the provided {@code supplier}} *

    * The provided {@code supplier} is guaranteed to be invoked at most once if it - * completes without throwing an exception. + * completes without throwing an exception. If this method is invoked several times + * with different suppliers, only one of them will be invoked provided it completes + * without throwing an exception. *

    * If the supplier throws an (unchecked) exception, the exception is rethrown, and no * content is set. The most common usage is to construct a new object serving @@ -650,6 +654,8 @@ static IntFunction intFunction(int size, * @param original Function used to compute cached values * @param the type of the input to the returned Function * @param the type of results delivered by the returned Function + * @throws NullPointerException if the provided set of {@code inputs} contains a + * {@code null} element. */ static Function function(Set inputs, Function original) { @@ -735,6 +741,8 @@ static List list(int size, * (may return {@code null}) * @param the type of keys maintained by the returned map * @param the type of mapped values in the returned map + * @throws NullPointerException if the provided set of {@code inputs} contains a + * {@code null} element. */ static Map map(Set keys, Function mapper) { diff --git a/test/jdk/java/lang/StableValue/StableFunctionTest.java b/test/jdk/java/lang/StableValue/StableFunctionTest.java index d3bfa198da1b7..1a9fead60bbf7 100644 --- a/test/jdk/java/lang/StableValue/StableFunctionTest.java +++ b/test/jdk/java/lang/StableValue/StableFunctionTest.java @@ -34,6 +34,7 @@ import java.util.Arrays; import java.util.Comparator; import java.util.EnumSet; +import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Set; import java.util.TreeSet; @@ -170,6 +171,14 @@ void hashCodeStable(Set inputs) { } } + @Test + void nullKeys() { + Set inputs = new HashSet<>(); + inputs.add(Value.FORTY_TWO); + inputs.add(null); + assertThrows(NullPointerException.class, () -> StableValue.function(inputs, MAPPER)); + } + @Test void usesOptimizedVersion() { Function enumFunction = StableValue.function(EnumSet.of(Value.FORTY_TWO), Value::asInt); From 96a90d0f8b7623ae9a2ad9559b838388b8216b6d Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Thu, 3 Apr 2025 13:12:04 +0200 Subject: [PATCH 240/327] Address comments --- .../share/classes/java/lang/StableValue.java | 52 ++++++++++--------- .../lang/stable/StableEnumFunction.java | 4 +- .../internal/lang/stable/StableValueImpl.java | 24 ++++++--- .../lang/StableValue/StableFunctionTest.java | 47 ++++++++++------- .../StableValue/StableIntFunctionTest.java | 4 +- .../java/lang/StableValue/StableListTest.java | 22 +++++--- .../java/lang/StableValue/StableMapTest.java | 16 +++--- .../lang/StableValue/StableSupplierTest.java | 4 +- .../StableValue/StableValueFactoriesTest.java | 4 +- .../lang/StableValue/StableValueTest.java | 20 +++---- .../StableValuesSafePublicationTest.java | 35 ++++++------- .../StableValue/TrustedFieldTypeTest.java | 10 ++-- 12 files changed, 133 insertions(+), 109 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 71668190e71f3..d6471c350eec0 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -91,7 +91,9 @@ * } *} *

    - * Note that the content can only be set at most once. + * If {@code getLogger()} is called from several threads, several instances of + * {@code Logger} might be created. However, the content can only be set at most once + * meaning one "winner" is picked among the many loggers. * In the example above, the {@code logger} field is declared {@code static final} which * is a prerequisite for being treated as a constant by the JVM. * @@ -127,7 +129,7 @@ * before it is used. *

    * Furthermore, {@code orElseSet()} guarantees that the supplier provided is - * evaluated only once, even when {@code logger.orElseSet()} is invoked concurrently. + * evaluated at most once, even when {@code logger.orElseSet()} is invoked concurrently. * This property is crucial as evaluation of the supplier may have side effects, * e.g., the call above to {@code Logger.create()} may result in storage resources * being prepared. @@ -159,7 +161,7 @@ *

    * A stable int function is a function that takes an {@code int} parameter and * uses it to compute a result that is then cached by the backing stable value storage - * for that parameter value. A stable int function is created via the + * for that parameter value. A stable {@link IntFunction} is created via the * {@linkplain StableValue#intFunction(int, IntFunction) StableValue.intFunction()} * factory. Upon creation, the input range (i.e. {@code [0, size)}) is specified together * with an original {@linkplain IntFunction} which is invoked at most once per input value. @@ -169,7 +171,7 @@ * {@snippet lang = java: * public final class SqrtUtil { * - * private SqrtUtil(){} + * private SqrtUtil() {} * * private static final int CACHED_SIZE = 10; * @@ -204,7 +206,7 @@ * {@snippet lang = java: * public final class SqrtUtil { * - * private SqrtUtil(){} + * private SqrtUtil() {} * * private static final Set CACHED_KEYS = Set.of(1, 2, 4, 8, 16, 32); * @@ -239,7 +241,7 @@ * * private static final int CACHED_SIZE = 10; * - * private SqrtUtil(){} + * private SqrtUtil() {} * * private static final List SQRT = * // @link substring="list" target="#list(int,IntFunction)" : @@ -268,13 +270,13 @@ * {@snippet lang = java: * public final class SqrtUtil { * - * private SqrtUtil(){} + * private SqrtUtil() {} * * private static final Set CACHED_KEYS = Set.of(1, 2, 4, 8, 16, 32); * * private static final Map SQRT = * // @link substring="map" target="#map(Set,Function)" : - * StableValue.map(Set.of(1, 2, 4, 8, 16, 32), StrictMath::sqrt); + * StableValue.map(CACHED_KEYS, StrictMath::sqrt); * * public static double sqrt(int a) { * if (CACHED_KEYS.contains(a)) { @@ -293,7 +295,7 @@ *} * *

    Composing stable values

    - * A stable value can depend on other stable values, thereby creating a dependency graph + * A stable value can depend on other stable values, forming a dependency graph * that can be lazily computed but where access to individual elements still can be * constant-folded. In the following example, a single {@code Foo} and a {@code Bar} * instance (that is dependent on the {@code Foo} instance) are lazily created, both of @@ -301,7 +303,7 @@ * {@snippet lang = java: * public final class DependencyUtil { * - * private DependencyUtil(){} + * private DependencyUtil() {} * * public static class Foo { * // ... @@ -350,7 +352,7 @@ * * } *} - * Both {@code FIB} and {@code Fibonacci::fib} recurses into each other. Because the + * Both {@code FIB} and {@code Fibonacci::fib} recurse into each other. Because the * stable int function {@code FIB} caches intermediate results, the initial * computational complexity is reduced from exponential to linear compared to a * traditional non-caching recursive fibonacci method. Once computed, the VM is free to @@ -370,7 +372,7 @@ *} * * If there are circular dependencies in a dependency graph, a stable value will - * eventually throw a {@linkplain StackOverflowError} upon referencing elements in + * eventually throw an {@linkplain IllegalStateException} upon referencing elements in * a circularity. * *

    Thread Safety

    @@ -423,7 +425,7 @@ * of unintended memory leaks. A stable value's content is * {@linkplain java.lang.ref##reachability strongly reachable}. Clients are * advised that {@linkplain java.lang.ref##reachability reachable} stable values - * will hold their set content perpetually. + * will hold their set content until the stable value itself is collected. * A {@linkplain StableValue} that has a type parameter {@code T} that is an array * type (of arbitrary rank) will only allow the JVM to treat the array reference * as a stable value but not its components. Clients can instead use @@ -590,27 +592,27 @@ static Supplier supplier(Supplier original) { } /** - * {@return a new stable int function} + * {@return a new stable {@linkplain IntFunction}} *

    - * The returned {@link IntFunction int function} is a caching int function that, - * for each allowed input, records the values of the provided {@code original} - * int function upon being first accessed via the returned int function's + * The returned function is a caching function that, for each allowed {@code int} + * input, records the values of the provided {@code original} + * function upon being first accessed via the returned function's * {@linkplain IntFunction#apply(int) apply()} method. *

    - * The provided {@code original} int function is guaranteed to be successfully invoked + * The provided {@code original} function is guaranteed to be successfully invoked * at most once per allowed input, even in a multi-threaded environment. Competing - * threads invoking the returned int function's + * threads invoking the returned function's * {@linkplain IntFunction#apply(int) apply()} method when a value is already under * computation will block until a value is computed or an exception is thrown by * the computing thread. *

    - * If the provided {@code original} int function throws an exception, it is relayed + * If the provided {@code original} function throws an exception, it is relayed * to the initial caller and no content is recorded. *

    - * The returned int function is not {@link Serializable}. + * The returned function is not {@link Serializable}. *

    - * If the provided {@code original} int function recursively calls the returned - * int function for the same index, an {@linkplain IllegalStateException} will + * If the provided {@code original} function recursively calls the returned + * function for the same index, an {@linkplain IllegalStateException} will * be thrown. * * @param size the size of the allowed inputs in {@code [0, size)} @@ -628,9 +630,9 @@ static IntFunction intFunction(int size, } /** - * {@return a new stable function} + * {@return a new stable {@linkplain Function}} *

    - * The returned {@link Function function} is a caching function that, for each allowed + * The returned function is a caching function that, for each allowed * input in the given set of {@code inputs}, records the values of the provided * {@code original} function upon being first accessed via the returned function's * {@linkplain Function#apply(Object) apply()} method. diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java index 8d03a7c773a74..bb958353d9513 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java @@ -59,7 +59,7 @@ public record StableEnumFunction, R>(Class enumType, @ForceInline @Override public R apply(E value) { - if (!member.test(value.ordinal())) { + if (!member.test(value.ordinal())) { // Implicit null-check of value throw new IllegalArgumentException("Input not allowed: " + value); } final int index = value.ordinal() - firstOrdinal; @@ -86,8 +86,8 @@ public boolean equals(Object obj) { @Override public String toString() { - final Collection>> entries = new ArrayList<>(); final E[] enumElements = enumType.getEnumConstants(); + final Collection>> entries = new ArrayList<>(enumElements.length); int ordinal = firstOrdinal; for (int i = 0; i < delegates.length; i++, ordinal++) { if (member.test(ordinal)) { 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 6cd357f15dd1c..6a6fbdbe1e55b 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 @@ -89,8 +89,9 @@ public boolean trySet(T content) { @Override public void setOrThrow(T content) { if (!trySet(content)) { - throw new IllegalStateException("Cannot set the content to " + content + - " because the content is already set: " + orElseThrow()); + // Neither the set content nor the provided content is reveled in the + // exception message as it might be sensitive. + throw new IllegalStateException("The content is already set"); } } @@ -165,20 +166,27 @@ static String renderWrapped(Object t) { private void preventReentry() { if (Thread.holdsLock(this)) { - throw new IllegalStateException("Recursing supplier detected"); + throw new IllegalStateException("Recursive initialization is not supported"); } } + /** + * Wraps the provided {@code newValue} and tries to set the content. + *

    + * This method ensures the {@link Stable} field is written to at most once. + * + * @param newValue to wrap and set + * @return if the content was set + */ @ForceInline private boolean wrapAndSet(Object newValue) { assert Thread.holdsLock(this); - // This upholds the invariant, a `@Stable` field is written to at most once // We know we hold the monitor here so plain semantic is enough - if (content != null) { - return false; + if (content == null) { + UNSAFE.putReferenceRelease(this, CONTENT_OFFSET, wrap(newValue)); + return true; } - UNSAFE.putReferenceRelease(this, CONTENT_OFFSET, wrap(newValue)); - return true; + return false; } // Used to indicate a holder value is `null` (see field `value` below) diff --git a/test/jdk/java/lang/StableValue/StableFunctionTest.java b/test/jdk/java/lang/StableValue/StableFunctionTest.java index 1a9fead60bbf7..2a674b70be6e8 100644 --- a/test/jdk/java/lang/StableValue/StableFunctionTest.java +++ b/test/jdk/java/lang/StableValue/StableFunctionTest.java @@ -23,8 +23,8 @@ /* @test * @summary Basic tests for StableFunctionTest methods - * @compile --enable-preview -source ${jdk.version} StableFunctionTest.java - * @run junit/othervm --enable-preview StableFunctionTest + * @enablePreview + * @run junit StableFunctionTest */ import org.junit.jupiter.api.Test; @@ -83,7 +83,9 @@ void factoryInvariants(Set inputs) { @MethodSource("nonEmptySets") void basic(Set inputs) { basic(inputs, MAPPER); + toStringTest(inputs, MAPPER); basic(inputs, _ -> null); + toStringTest(inputs, _ -> null); } void basic(Set inputs, Function mapper) { @@ -93,19 +95,25 @@ void basic(Set inputs, Function mapper) { assertEquals(1, cif.cnt()); assertEquals(mapper.apply(Value.FORTY_TWO), cached.apply(Value.FORTY_TWO)); assertEquals(1, cif.cnt()); - assertTrue(cached.toString().startsWith("{"), cached.toString()); - // Key order is unspecified - assertTrue(cached.toString().contains(Value.THIRTEEN + "=.unset")); - assertTrue(cached.toString().contains(Value.FORTY_TWO + "=" + mapper.apply(Value.FORTY_TWO)), cached.toString()); - assertTrue(cached.toString().endsWith("}")); - // One between the values - assertEquals(1L, cached.toString().chars().filter(ch -> ch == ',').count(), cached.toString()); var x0 = assertThrows(IllegalArgumentException.class, () -> cached.apply(Value.ILLEGAL_BEFORE)); - assertTrue(x0.getMessage().contains("ILLEGAL")); + assertEquals("Input not allowed: ILLEGAL_BEFORE", x0.getMessage()); var x1 = assertThrows(IllegalArgumentException.class, () -> cached.apply(Value.ILLEGAL_BETWEEN)); - assertTrue(x1.getMessage().contains("ILLEGAL")); + assertEquals("Input not allowed: ILLEGAL_BETWEEN", x1.getMessage()); var x2 = assertThrows(IllegalArgumentException.class, () -> cached.apply(Value.ILLEGAL_AFTER)); - assertTrue(x2.getMessage().contains("ILLEGAL")); + assertEquals("Input not allowed: ILLEGAL_AFTER", x2.getMessage()); + } + + void toStringTest(Set inputs, Function mapper) { + var cached = StableValue.function(inputs, mapper); + cached.apply(Value.FORTY_TWO); + var toString = cached.toString(); + assertTrue(toString.startsWith("{")); + // Key order is unspecified + assertTrue(toString.contains(Value.THIRTEEN + "=.unset")); + assertTrue(toString.contains(Value.FORTY_TWO + "=" + mapper.apply(Value.FORTY_TWO))); + assertTrue(toString.endsWith("}")); + // One between the values + assertEquals(1L, toString.chars().filter(ch -> ch == ',').count()); } @ParameterizedTest @@ -113,7 +121,7 @@ void basic(Set inputs, Function mapper) { void empty(Set inputs) { Function f0 = StableValue.function(inputs, Value::asInt); Function f1 = StableValue.function(inputs, Value::asInt); - assertTrue(f0.toString().contains("{}")); + assertEquals("{}", f0.toString()); assertThrows(NullPointerException.class, () -> f0.apply(null)); assertNotEquals(f0, f1); assertNotEquals(null, f0); @@ -130,11 +138,12 @@ void exception(Set inputs) { assertEquals(1, cif.cnt()); assertThrows(UnsupportedOperationException.class, () -> cached.apply(Value.FORTY_TWO)); assertEquals(2, cif.cnt()); - assertTrue(cached.toString().startsWith("{")); + var toString = cached.toString(); + assertTrue(toString.startsWith("{")); // Key order is unspecified - assertTrue(cached.toString().contains(Value.THIRTEEN + "=.unset")); - assertTrue(cached.toString().contains(Value.FORTY_TWO + "=.unset"), cached.toString()); - assertTrue(cached.toString().endsWith("}")); + assertTrue(toString.contains(Value.THIRTEEN + "=.unset")); + assertTrue(toString.contains(Value.FORTY_TWO + "=.unset")); + assertTrue(toString.endsWith("}")); } @ParameterizedTest @@ -144,8 +153,8 @@ void circular(Set inputs) { Function> cached = StableValue.function(inputs, _ -> ref.get()); ref.set(cached); cached.apply(Value.FORTY_TWO); - String toString = cached.toString(); - assertTrue(toString.contains("(this StableFunction)"), toString); + var toString = cached.toString(); + assertTrue(toString.contains("FORTY_TWO=(this StableFunction)"), toString); assertDoesNotThrow(cached::hashCode); assertDoesNotThrow((() -> cached.equals(cached))); } diff --git a/test/jdk/java/lang/StableValue/StableIntFunctionTest.java b/test/jdk/java/lang/StableValue/StableIntFunctionTest.java index 8ceb7adea3a94..5a29ba0ecc887 100644 --- a/test/jdk/java/lang/StableValue/StableIntFunctionTest.java +++ b/test/jdk/java/lang/StableValue/StableIntFunctionTest.java @@ -23,8 +23,8 @@ /* @test * @summary Basic tests for StableIntFunctionTest methods - * @compile --enable-preview -source ${jdk.version} StableIntFunctionTest.java - * @run junit/othervm --enable-preview StableIntFunctionTest + * @enablePreview + * @run junit StableIntFunctionTest */ import org.junit.jupiter.api.Test; diff --git a/test/jdk/java/lang/StableValue/StableListTest.java b/test/jdk/java/lang/StableValue/StableListTest.java index 15792e043907f..2a27ef5a85af7 100644 --- a/test/jdk/java/lang/StableValue/StableListTest.java +++ b/test/jdk/java/lang/StableValue/StableListTest.java @@ -24,8 +24,8 @@ /* @test * @summary Basic tests for LazyList methods * @modules java.base/jdk.internal.lang.stable - * @compile --enable-preview -source ${jdk.version} StableListTest.java - * @run junit/othervm --enable-preview StableListTest + * @enablePreview + * @run junit StableListTest */ import jdk.internal.lang.stable.StableUtil; @@ -35,6 +35,7 @@ import org.junit.jupiter.params.provider.MethodSource; import java.io.Serializable; +import java.util.Arrays; import java.util.Comparator; import java.util.IdentityHashMap; import java.util.List; @@ -109,10 +110,16 @@ void toArray() { @Test void toArrayWithArrayLarger() { - Integer[] arr = new Integer[SIZE]; - arr[INDEX] = 1; - assertSame(arr, StableValue.list(INDEX, IDENTITY).toArray(arr)); - assertNull(arr[INDEX]); + Integer[] actual = new Integer[SIZE]; + for (int i = 0; i < SIZE; i++) { + actual[INDEX] = 100 + i; + } + var list = StableValue.list(INDEX, IDENTITY); + assertSame(actual, list.toArray(actual)); + Integer[] expected = IntStream.range(0, SIZE) + .mapToObj(i -> i < INDEX ? i : null) + .toArray(Integer[]::new); + assertArrayEquals(expected, actual); } @Test @@ -280,7 +287,8 @@ void recursiveCall() { AtomicReference> ref = new AtomicReference<>(); var lazy = StableValue.list(SIZE, i -> ref.get().apply(i)); ref.set(lazy::get); - assertThrows(IllegalStateException.class, () -> lazy.get(INDEX)); + var x = assertThrows(IllegalStateException.class, () -> lazy.get(INDEX)); + assertEquals("Recursive initialization is not supported", x.getMessage()); } // Immutability diff --git a/test/jdk/java/lang/StableValue/StableMapTest.java b/test/jdk/java/lang/StableValue/StableMapTest.java index f68b87d22990d..0f034520c3b84 100644 --- a/test/jdk/java/lang/StableValue/StableMapTest.java +++ b/test/jdk/java/lang/StableValue/StableMapTest.java @@ -24,8 +24,8 @@ /* @test * @summary Basic tests for LazyMap methods * @modules java.base/jdk.internal.lang.stable - * @compile --enable-preview -source ${jdk.version} StableMapTest.java - * @run junit/othervm --enable-preview StableMapTest + * @enablePreview + * @run junit StableMapTest */ import jdk.internal.lang.stable.StableUtil; @@ -171,11 +171,12 @@ void entrySet() { void entrySetToString() { var map = newMap(); var entrySet = map.entrySet(); + var toString = entrySet.toString(); for (var key : KEYS) { - assertTrue(entrySet.toString().contains(key + "=.unset")); + assertTrue(toString.contains(key + "=.unset")); } - assertTrue(entrySet.toString().startsWith("[")); - assertTrue(entrySet.toString().endsWith("]")); + assertTrue(toString.startsWith("[")); + assertTrue(toString.endsWith("]")); map.get(KEY); for (var key : KEYS) { @@ -193,11 +194,12 @@ void values() { var values = map.values(); // Look at one of the elements var val = values.stream().iterator().next(); + var toString = map.toString(); for (var key : KEYS) { if (key.equals(val)) { - assertTrue(map.toString().contains(key + "=" + key)); + assertTrue(toString.contains(key + "=" + key)); } else { - assertTrue(map.toString().contains(key + "=.unset")); + assertTrue(toString.contains(key + "=.unset")); } } diff --git a/test/jdk/java/lang/StableValue/StableSupplierTest.java b/test/jdk/java/lang/StableValue/StableSupplierTest.java index 8f7a961120183..3e23ccc631ada 100644 --- a/test/jdk/java/lang/StableValue/StableSupplierTest.java +++ b/test/jdk/java/lang/StableValue/StableSupplierTest.java @@ -23,8 +23,8 @@ /* @test * @summary Basic tests for StableSupplierTest methods - * @compile --enable-preview -source ${jdk.version} StableSupplierTest.java - * @run junit/othervm --enable-preview StableSupplierTest + * @enablePreview + * @run junit StableSupplierTest */ import org.junit.jupiter.api.Test; diff --git a/test/jdk/java/lang/StableValue/StableValueFactoriesTest.java b/test/jdk/java/lang/StableValue/StableValueFactoriesTest.java index 7510f0012313d..6bbb0c67c3ea3 100644 --- a/test/jdk/java/lang/StableValue/StableValueFactoriesTest.java +++ b/test/jdk/java/lang/StableValue/StableValueFactoriesTest.java @@ -24,8 +24,8 @@ /* @test * @summary Basic tests for StableValueFactoriesTest implementations * @modules java.base/jdk.internal.lang.stable - * @compile --enable-preview -source ${jdk.version} StableValueFactoriesTest.java - * @run junit/othervm --enable-preview StableValueFactoriesTest + * @enablePreview + * @run junit StableValueFactoriesTest */ import jdk.internal.lang.stable.StableUtil; diff --git a/test/jdk/java/lang/StableValue/StableValueTest.java b/test/jdk/java/lang/StableValue/StableValueTest.java index 33ac9e79bb923..f8d2d4ee83f42 100644 --- a/test/jdk/java/lang/StableValue/StableValueTest.java +++ b/test/jdk/java/lang/StableValue/StableValueTest.java @@ -23,22 +23,22 @@ /* @test * @summary Basic tests for StableValue implementations - * @compile --enable-preview -source ${jdk.version} StableValueTest.java - * @run junit/othervm --enable-preview StableValueTest + * @enablePreview + * @run junit StableValueTest */ import org.junit.jupiter.api.Test; -import java.util.BitSet; import java.util.List; +import java.util.Map; import java.util.NoSuchElementException; import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.LockSupport; import java.util.function.BiPredicate; -import java.util.function.IntFunction; import java.util.stream.IntStream; import static org.junit.jupiter.api.Assertions.*; @@ -65,7 +65,7 @@ void preSet() { assertFalse(stable.trySet(VALUE2)); var e = assertThrows(IllegalStateException.class, () -> stable.setOrThrow(VALUE2)); assertEquals( - "Cannot set the content to " + VALUE2 + " because the content is already set: " + VALUE, + "The content is already set", e.getMessage()); } @@ -83,7 +83,7 @@ void setOrThrowValue() { StableValue stable = StableValue.of(); stable.setOrThrow(VALUE); var e = assertThrows(IllegalStateException.class, () -> stable.setOrThrow(VALUE2)); - assertEquals("Cannot set the content to " + VALUE2 + " because the content is already set: " + VALUE, e.getMessage()); + assertEquals("The content is already set", e.getMessage()); } @Test @@ -91,7 +91,7 @@ void setOrThrowNull() { StableValue stable = StableValue.of(); stable.setOrThrow(null); var e = assertThrows(IllegalStateException.class, () -> stable.setOrThrow(null)); - assertEquals("Cannot set the content to null because the content is already set: null", e.getMessage()); + assertEquals("The content is already set", e.getMessage()); } @Test @@ -241,13 +241,13 @@ void race(BiPredicate, Integer> winnerPredicate) { int noThreads = 10; CountDownLatch starter = new CountDownLatch(1); StableValue stable = StableValue.of(); - BitSet winner = new BitSet(noThreads); + Map winners = new ConcurrentHashMap<>(); List threads = IntStream.range(0, noThreads).mapToObj(i -> new Thread(() -> { try { // Ready, set ... starter.await(); // Here we go! - winner.set(i, winnerPredicate.test(stable, i)); + winners.put(i, winnerPredicate.test(stable, i)); } catch (Throwable t) { fail(t); } @@ -259,7 +259,7 @@ void race(BiPredicate, Integer> winnerPredicate) { starter.countDown(); threads.forEach(StableValueTest::join); // There can only be one winner - assertEquals(1, winner.cardinality()); + assertEquals(1, winners.values().stream().filter(b -> b).count()); } private static void join(Thread thread) { diff --git a/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java b/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java index 3531793ab9bfa..31585f5147589 100644 --- a/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java +++ b/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java @@ -24,8 +24,8 @@ /* @test * @summary Basic tests for making sure StableValue publishes values safely * @modules java.base/jdk.internal.misc - * @compile --enable-preview -source ${jdk.version} StableValuesSafePublicationTest.java - * @run junit/othervm --enable-preview StableValuesSafePublicationTest + * @enablePreview + * @run junit StableValuesSafePublicationTest */ import org.junit.jupiter.api.Test; @@ -60,12 +60,10 @@ static StableValue[] stables() { static final class Holder { // These are non-final fields but should be seen // fully initialized thanks to the HB properties of StableValue. - int a; - int b; + int a, b, c, d, e; Holder() { - a = 1; - b = 1; + a = b = c = d = e = 1; } } @@ -84,7 +82,10 @@ public void run() { while ((h = s.orElse(null)) == null) {} int a = h.a; int b = h.b; - observations[i] = a + (b << 1); + int c = h.c; + int d = h.d; + int e = h.e; + observations[i] = a + (b << 1) + (c << 2) + (c << 3) + (d << 4) + (e << 5); } } } @@ -129,28 +130,26 @@ void main() { join(consumers, producerThread); join(consumers, consumersThreads.toArray(Thread[]::new)); - int[] histogram = new int[4]; + int[] histogram = new int[64]; for (Consumer consumer : consumers) { for (int i = 0; i < SIZE; i++) { histogram[consumer.observations[i]]++; } } - // a = 0, b = 0 : index 0 - assertEquals(0, histogram[0]); - // a = 1, b = 0 : index 1 - assertEquals(0, histogram[1]); - // a = 0, b = 1 : index 2 - assertEquals(0, histogram[2]); - // a = 1, b = 1 : index 3 + // unless a = 1, ..., e = 1, zero observations should be seen + for (int i = 0; i < 63; i++) { + assertEquals(0, histogram[i]); + } + // a = 1, ..., e = 1 : index 2^5-1 = 63 // All observations should end up in this bucket - assertEquals(THREADS * SIZE, histogram[3]); + assertEquals(THREADS * SIZE, histogram[63]); } static void join(List consumers, Thread... threads) { try { for (Thread t:threads) { - long deadline = System.currentTimeMillis()+TimeUnit.MINUTES.toMillis(1); + long deadline = System.nanoTime() + TimeUnit.MINUTES.toNanos(1); while (t.isAlive()) { t.join(TimeUnit.SECONDS.toMillis(10)); if (t.isAlive()) { @@ -162,7 +161,7 @@ static void join(List consumers, Thread... threads) { System.err.println("Consumer " + i + ": " + consumers.get(i).i); } } - if (System.currentTimeMillis() > deadline) { + if (System.nanoTime() > deadline) { long nonNulls = CompletableFuture.supplyAsync(() -> Stream.of(STABLES) .map(s -> s.orElse(null)) diff --git a/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java b/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java index 1074db55d2c31..f7217dc966f05 100644 --- a/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java +++ b/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java @@ -26,9 +26,9 @@ * @modules jdk.unsupported/sun.misc * @modules java.base/jdk.internal.lang.stable * @modules java.base/jdk.internal.misc - * @compile --enable-preview -source ${jdk.version} TrustedFieldTypeTest.java - * @run junit/othervm --enable-preview --add-opens java.base/jdk.internal.lang.stable=ALL-UNNAMED -Dopens=true TrustedFieldTypeTest - * @run junit/othervm --enable-preview -Dopens=false TrustedFieldTypeTest + * @enablePreview + * @run junit/othervm --add-opens java.base/jdk.internal.lang.stable=ALL-UNNAMED -Dopens=true TrustedFieldTypeTest + * @run junit/othervm -Dopens=false TrustedFieldTypeTest */ import jdk.internal.lang.stable.StableValueImpl; @@ -110,14 +110,10 @@ void updateStableValueContentViaSetAccessible() throws NoSuchFieldException, Ill StableValue stableValue = StableValue.of(); stableValue.trySet(42); -// assertThrows(IllegalAccessException.class, () -> { Object oldData = field.get(stableValue); assertEquals(42, oldData); -// }); -// assertThrows(IllegalAccessException.class, () -> { field.set(stableValue, 13); -// }); assertEquals(13, stableValue.orElseThrow()); } else { Field field = StableValueImpl.class.getDeclaredField("content"); From 63f885446838fb1527f0a025f3f3c960f41da96c Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Thu, 3 Apr 2025 13:29:52 +0200 Subject: [PATCH 241/327] Move Serializable specs to @implNote --- .../share/classes/java/lang/StableValue.java | 23 ++++++------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index d6471c350eec0..bce9cec8011bb 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -432,6 +432,7 @@ * {@linkplain #list(int, IntFunction) a stable list} of arbitrary depth, which * provides stable components. More generally, a stable value can hold other * stable values of arbitrary depth and still provide transitive constantness. + * Stable values, functions and collections are not {@link Serializable}. * * @param type of the content * @@ -539,8 +540,6 @@ public sealed interface StableValue * {@return a new unset stable value} *

    * An unset stable value has no content. - *

    - * The returned stable value is not {@link Serializable}. * * @param type of the content */ @@ -550,8 +549,6 @@ static StableValue of() { /** * {@return a new pre-set stable value with the provided {@code content}} - *

    - * The returned stable value is not {@link Serializable}. * * @param content to set * @param type of the content @@ -578,8 +575,6 @@ static StableValue of(T content) { * If the provided {@code original} supplier throws an exception, it is relayed * to the initial caller and no content is recorded. *

    - * The returned supplier is not {@link Serializable}. - *

    * If the provided {@code original} supplier recursively calls the returned * supplier, an {@linkplain IllegalStateException} will be thrown. * @@ -609,8 +604,6 @@ static Supplier supplier(Supplier original) { * If the provided {@code original} function throws an exception, it is relayed * to the initial caller and no content is recorded. *

    - * The returned function is not {@link Serializable}. - *

    * If the provided {@code original} function recursively calls the returned * function for the same index, an {@linkplain IllegalStateException} will * be thrown. @@ -646,8 +639,6 @@ static IntFunction intFunction(int size, * If the provided {@code original} function throws an exception, it is relayed to * the initial caller and no content is recorded. *

    - * The returned function is not {@link Serializable}. - *

    * If the provided {@code original} function recursively calls the returned * function for the same input, an {@linkplain IllegalStateException} will * be thrown. @@ -690,9 +681,9 @@ static Function function(Set inputs, * The returned list and its {@link List#subList(int, int) subList} or * {@link List#reversed()} views implement the {@link RandomAccess} interface. *

    - * The returned list is not {@link Serializable} and, as it is unmodifiable, does - * not implement the {@linkplain Collection##optional-operation optional operations} - * in the {@linkplain List} interface. + * The returned list is unmodifiable and does not implement the + * {@linkplain Collection##optional-operation optional operations} in the + * {@linkplain List} interface. *

    * If the provided {@code mapper} recursively calls the returned list for the * same index, an {@linkplain IllegalStateException} will be thrown. @@ -731,9 +722,9 @@ static List list(int size, * Any direct {@link Map#values()} or {@link Map#entrySet()} views * of the returned map are also stable. *

    - * The returned map is not {@link Serializable} and, as it is unmodifiable, does - * not implement the {@linkplain Collection##optional-operations optional operations} - * in the {@linkplain Map} interface. + * The returned map is unmodifiable and does not implement the + * {@linkplain Collection##optional-operations optional operations} in the + * {@linkplain Map} interface. *

    * If the provided {@code mapper} recursively calls the returned map for * the same key, an {@linkplain IllegalStateException} will be thrown. From 596bc1316efa5afb3699a49f31887e72a273f03b Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Thu, 3 Apr 2025 14:39:04 +0200 Subject: [PATCH 242/327] Break out and move performance related text --- .../share/classes/java/lang/StableValue.java | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index bce9cec8011bb..ba1b52a31a6b7 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -58,12 +58,6 @@ * can never change and can be retrieved by calling {@linkplain #orElseThrow() orElseThrow()} * , {@linkplain #orElse(Object) orElse()}, or {@linkplain #orElseSet(Supplier) orElseSet()}. *

    - * A stable value that is set is treated as a constant by the JVM, enabling the - * same performance optimizations that are available for {@code final} fields. - * As such, stable values can be used to replace {@code final} fields in cases where - * at-most-once update semantics is crucial, but where the eager initialization - * semantics associated with {@code final} fields is too restrictive. - *

    * Consider the following example where a stable value field "{@code logger}" is a * shallowly immutable holder of content of type {@code Logger} and that is initially * created as unset, which means it holds no content. Later in the example, the @@ -75,7 +69,7 @@ * * // Creates a new unset stable value with no content * // @link substring="of" target="#of" : - * private static final StableValue logger = StableValue.of(); + * private final StableValue logger = StableValue.of(); * * private Logger getLogger() { * if (!logger.isSet()) { @@ -94,9 +88,6 @@ * If {@code getLogger()} is called from several threads, several instances of * {@code Logger} might be created. However, the content can only be set at most once * meaning one "winner" is picked among the many loggers. - * In the example above, the {@code logger} field is declared {@code static final} which - * is a prerequisite for being treated as a constant by the JVM. - * *

    * To guarantee that, even under races, only one instance of {@code Logger} is ever * created, the {@linkplain #orElseSet(Supplier) orElseSet()} method can be used @@ -109,7 +100,7 @@ * * // Creates a new unset stable value with no content * // @link substring="of" target="#of" : - * private static final StableValue logger = StableValue.of(); + * private final StableValue logger = StableValue.of(); * * private Logger getLogger() { * return logger.orElseSet( () -> Logger.create(Component.class) ); @@ -145,7 +136,7 @@ * {@snippet lang = java: * public class Component { * - * private static final Supplier logger = + * private final Supplier logger = * // @link substring="supplier" target="#supplier(Supplier)" : * StableValue.supplier( () -> Logger.getLogger(Component.class) ); * @@ -297,7 +288,7 @@ *

    Composing stable values

    * A stable value can depend on other stable values, forming a dependency graph * that can be lazily computed but where access to individual elements still can be - * constant-folded. In the following example, a single {@code Foo} and a {@code Bar} + * performant. In the following example, a single {@code Foo} and a {@code Bar} * instance (that is dependent on the {@code Foo} instance) are lazily created, both of * which are held by stable values: * {@snippet lang = java: @@ -356,7 +347,7 @@ * stable int function {@code FIB} caches intermediate results, the initial * computational complexity is reduced from exponential to linear compared to a * traditional non-caching recursive fibonacci method. Once computed, the VM is free to - * constant-fold expressions like {@code Fibonacci.fib(10)}. + * constant-fold expressions like {@code Fibonacci.fib(5)}. *

    * The fibonacci example above is a dependency graph with no circular dependencies (i.e., * it is a dependency tree): @@ -403,6 +394,19 @@ * {@linkplain StableValue#orElseSet(Supplier) orElseSet()} they too are * thread safe and guarantee at-most-once-per-input invocation. * + *

    Performance

    + * A stable value that is set is treated as a constant by the JVM, enabling the + * same performance optimizations that are available for {@code final} fields. + * As such, stable values can be used to replace {@code final} fields in cases where + * at-most-once update semantics is crucial, but where the eager initialization + * semantics associated with {@code final} fields is too restrictive. + *

    + * In JDK 24, {@code final} instance fields in records and hidden classes (such as classes + * spun from method references) are trusted by the JVM, allowing them to be treated as + * constants. However, {@code final} instance fields in regular classes are not + * trusted meaning that such fields are not eligible for performance + * optimizations by the JVM (such as constant folding). + * * @implSpec Implementing classes of {@linkplain StableValue} are free to synchronize on * {@code this} and consequently, care should be taken whenever * (directly or indirectly) synchronizing on a {@code StableValue}. Failure to From 8ec7ac01a32a409b6fc31119be461a7540eff441 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Thu, 3 Apr 2025 14:51:20 +0200 Subject: [PATCH 243/327] Clean up StableValue docs --- src/java.base/share/classes/java/lang/StableValue.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index ba1b52a31a6b7..d9f502e30673a 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -407,7 +407,7 @@ * trusted meaning that such fields are not eligible for performance * optimizations by the JVM (such as constant folding). * - * @implSpec Implementing classes of {@linkplain StableValue} are free to synchronize on + * @implSpec Implementing classes of {@code StableValue} are free to synchronize on * {@code this} and consequently, care should be taken whenever * (directly or indirectly) synchronizing on a {@code StableValue}. Failure to * do this may lead to deadlock. Stable functions and collections on the @@ -417,7 +417,7 @@ * method parameters must be non-null or a {@link NullPointerException} * will be thrown. * - * @implNote A {@linkplain StableValue} is mainly intended to be a non-public field in + * @implNote A {@code StableValue} is mainly intended to be a non-public field in * a class and is usually neither exposed directly via accessors nor passed as * a method parameter. * Stable functions and collections make all reasonable efforts to provide @@ -430,7 +430,7 @@ * {@linkplain java.lang.ref##reachability strongly reachable}. Clients are * advised that {@linkplain java.lang.ref##reachability reachable} stable values * will hold their set content until the stable value itself is collected. - * A {@linkplain StableValue} that has a type parameter {@code T} that is an array + * A {@code StableValue} that has a type parameter {@code T} that is an array * type (of arbitrary rank) will only allow the JVM to treat the array reference * as a stable value but not its components. Clients can instead use * {@linkplain #list(int, IntFunction) a stable list} of arbitrary depth, which From a83ec0e7d2c59ceb91acdb6a913370a026a38e9f Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Thu, 3 Apr 2025 14:58:25 +0200 Subject: [PATCH 244/327] Specify that an exception is thrown for illegal inputs --- src/java.base/share/classes/java/lang/StableValue.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index d9f502e30673a..92c2a648adabb 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -596,7 +596,9 @@ static Supplier supplier(Supplier original) { * The returned function is a caching function that, for each allowed {@code int} * input, records the values of the provided {@code original} * function upon being first accessed via the returned function's - * {@linkplain IntFunction#apply(int) apply()} method. + * {@linkplain IntFunction#apply(int) apply()} method. If the returned function is + * invoked with an input that is not allowed, an {@link IllegalArgumentException} + * will be thrown. *

    * The provided {@code original} function is guaranteed to be successfully invoked * at most once per allowed input, even in a multi-threaded environment. Competing @@ -632,7 +634,9 @@ static IntFunction intFunction(int size, * The returned function is a caching function that, for each allowed * input in the given set of {@code inputs}, records the values of the provided * {@code original} function upon being first accessed via the returned function's - * {@linkplain Function#apply(Object) apply()} method. + * {@linkplain Function#apply(Object) apply()} method. If the returned function is + * invoked with an input that is not allowed, an {@link IllegalArgumentException} + * will be thrown. *

    * The provided {@code original} function is guaranteed to be successfully invoked * at most once per allowed input, even in a multi-threaded environment. Competing From c80452c8edab73524427eb024cba10acbb8d4729 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Thu, 3 Apr 2025 15:56:15 +0200 Subject: [PATCH 245/327] Make the sqrt example different --- .../share/classes/java/lang/StableValue.java | 86 ++++++++++--------- .../lang/StableValue/StableValueTest.java | 66 ++++++++++++++ 2 files changed, 112 insertions(+), 40 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 92c2a648adabb..6eba2d54b167e 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -177,13 +177,13 @@ * return StrictMath.sqrt(a); * } * } + * } * - * public static void computeSomeValues() { - * double sqrt9 = sqrt(9); // May eventually constant fold to 3.0 at runtime - * double sqrt81 = sqrt(81); // Will not constant fold - * } - * + * public static void computeSomeValues() { + * double sqrt9 = sqrt(9); // May eventually constant fold to 3.0 at runtime + * double sqrt81 = sqrt(81); // Will not constant fold * } + * *} *

    * A stable function is a function that takes a parameter (of type {@code T}) and @@ -195,31 +195,34 @@ * stable function will act like a cache for the original {@linkplain Function}: * * {@snippet lang = java: - * public final class SqrtUtil { + * class Log2Util { * - * private SqrtUtil() {} + * private Log2Util() {} * - * private static final Set CACHED_KEYS = Set.of(1, 2, 4, 8, 16, 32); + * private static final Set CACHED_KEYS = + * Set.of(1, 2, 4, 8, 16, 32); + * private static final UnaryOperator LOG2_ORIGINAL = + * i -> 31 - Integer.numberOfLeadingZeros(i); * - * private static final Function SQRT = + * private static final Function LOG2_CACHED = * // @link substring="function" target="#function(Set,Function)" : - * StableValue.function(CACHED_KEYS, StrictMath::sqrt); + * StableValue.function(CACHED_KEYS, LOG2_ORIGINAL); * - * public static double sqrt(int a) { - * if (CACHED_KEYS.contains(a)) { - * return SQRT.apply(a); - * } else { - * return StrictMath.sqrt(a); - * } + * public static double log2(int a) { + * if (CACHED_KEYS.contains(a)) { + * return LOG2_CACHED.apply(a); + * } else { + * return LOG2_ORIGINAL.apply(a); + * } * } * - * public static double computeSomeValues() { - * double sqrt16 = sqrt(16); // May eventually constant fold to 4.0 at runtime - * double sqrt81 = sqrt(81); // Will not constant fold - * } + * } * + * public static double computeSomeValues() { + * double log16 = Log2Util.log2(16); // May eventually constant fold to 4 at runtime + * double log256 = Log2Util.log2(81); // Will not constant fold + * } * } - *} * *

    Stable Collections

    * Stable values can also be used as backing storage for @@ -232,11 +235,11 @@ * * private static final int CACHED_SIZE = 10; * - * private SqrtUtil() {} + * private SqrtUtil() {} * - * private static final List SQRT = - * // @link substring="list" target="#list(int,IntFunction)" : - * StableValue.list(CACHED_SIZE, StrictMath::sqrt); + * private static final List SQRT = + * // @link substring="list" target="#list(int,IntFunction)" : + * StableValue.list(CACHED_SIZE, StrictMath::sqrt); * * public static double sqrt(int a) { * if (a < CACHED_SIZE) { @@ -245,41 +248,44 @@ * return StrictMath.sqrt(a); * } * } + * } * - * public static void computeSomeValues() { - * double sqrt9 = sqrt(9); // May eventually constant fold to 3.0 at runtime - * double sqrt81 = sqrt(81); // Will not constant fold - * } + * public static void computeSomeValues() { + * double sqrt9 = SqrtUtil.sqrt(9); // May eventually constant fold to 3.0 at runtime + * double sqrt81 = SqrtUtil.sqrt(81); // Will not constant fold + * } * * } - *} *

    * Similarly, a stable map is an unmodifiable map whose keys are known at * construction. The stable map values are computed when they are first accessed, * using a provided {@linkplain Function}: * * {@snippet lang = java: - * public final class SqrtUtil { + * class Log2Util { * - * private SqrtUtil() {} + * private Log2Util() {} * - * private static final Set CACHED_KEYS = Set.of(1, 2, 4, 8, 16, 32); + * private static final Set CACHED_KEYS = + * Set.of(1, 2, 4, 8, 16, 32); + * private static final UnaryOperator LOG2_ORIGINAL = + * i -> 31 - Integer.numberOfLeadingZeros(i); * - * private static final Map SQRT = + * private static final Map LOG2_CACHED = * // @link substring="map" target="#map(Set,Function)" : - * StableValue.map(CACHED_KEYS, StrictMath::sqrt); + * StableValue.map(CACHED_KEYS, LOG2_ORIGINAL); * - * public static double sqrt(int a) { + * public static double log2(int a) { * if (CACHED_KEYS.contains(a)) { - * return SQRT.get(a); + * return LOG2_CACHED.get(a); * } else { - * return StrictMath.sqrt(a); + * return LOG2_ORIGINAL.apply(a); * } * } * * public static double computeSomeValues() { - * double sqrt16 = sqrt(16); // May eventually constant fold to 4.0 at runtime - * double sqrt81 = sqrt(81); // Will not constant fold + * double log16 = Log2Util.log2(16); // May eventually constant fold to 4 at runtime + * double log256 = Log2Util.log2(256); // Will not constant fold * } * * } diff --git a/test/jdk/java/lang/StableValue/StableValueTest.java b/test/jdk/java/lang/StableValue/StableValueTest.java index f8d2d4ee83f42..d98874583c791 100644 --- a/test/jdk/java/lang/StableValue/StableValueTest.java +++ b/test/jdk/java/lang/StableValue/StableValueTest.java @@ -33,12 +33,16 @@ import java.util.Map; import java.util.NoSuchElementException; import java.util.Objects; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.LockSupport; import java.util.function.BiPredicate; +import java.util.function.Function; +import java.util.function.IntFunction; +import java.util.function.UnaryOperator; import java.util.stream.IntStream; import static org.junit.jupiter.api.Assertions.*; @@ -208,6 +212,68 @@ void recursiveCall() { ); } + @Test + void intFunctionExample() { + final class SqrtUtil { + + private SqrtUtil() {} + + private static final int CACHED_SIZE = 10; + + private static final IntFunction SQRT = + // @link substring="intFunction" target="#intFunction(int,IntFunction)" : + StableValue.intFunction(CACHED_SIZE, StrictMath::sqrt); + + public static double sqrt(int a) { + if (a < CACHED_SIZE) { + return SQRT.apply(a); + } else { + return StrictMath.sqrt(a); + } + } + } + + + double sqrt9 = SqrtUtil.sqrt(9); // May eventually constant fold to 3.0 at runtime + double sqrt81 = SqrtUtil.sqrt(81); // Will not constant fold + + assertEquals(3, sqrt9); + assertEquals(9, sqrt81); + } + + @Test + void functionExample() { + + class Log2Util { + + private Log2Util() {} + + private static final Set CACHED_KEYS = + Set.of(1, 2, 4, 8, 16, 32); + private static final UnaryOperator LOG2_ORIGINAL = + i -> 31 - Integer.numberOfLeadingZeros(i); + + private static final Function LOG2_CACHED = + // @link substring="function" target="#function(Set,Function)" : + StableValue.function(CACHED_KEYS, LOG2_ORIGINAL); + + public static double log2(int a) { + if (CACHED_KEYS.contains(a)) { + return LOG2_CACHED.apply(a); + } else { + return LOG2_ORIGINAL.apply(a); + } + } + + } + + double log16 = Log2Util.log2(16); // May eventually constant fold to 4.0 at runtime + double log256 = Log2Util.log2(256); // Will not constant fold + + assertEquals(4, log16); + assertEquals(8, log256); + } + private static final BiPredicate, Integer> TRY_SET = StableValue::trySet; private static final BiPredicate, Integer> SET_OR_THROW = (s, i) -> { try { From 35ced1901bed78c92532f0a56c05c1511500aa35 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Fri, 4 Apr 2025 11:11:02 +0200 Subject: [PATCH 246/327] Address comments on docs --- .../share/classes/java/lang/StableValue.java | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 6eba2d54b167e..b51898f45bcb8 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -390,8 +390,9 @@ * A successful read operation can be either: *

      *
    • a {@link #orElseThrow()} that does not throw,
    • - *
    • a {@link #orElse(Object) orElse(other)} that does not return the {@code other} value, or
    • - *
    • an {@link #orElseSet(Supplier)} that does not {@code throw}
    • + *
    • a {@link #orElse(Object) orElse(other)} that does not return the {@code other} value
    • + *
    • an {@link #orElseSet(Supplier)} that does not {@code throw}, or
    • + *
    • an {@link #isSet()} that returns {@code true}
    • *
    *

    * The method {@linkplain StableValue#orElseSet(Supplier) orElseSet()} guarantees that @@ -401,17 +402,13 @@ * thread safe and guarantee at-most-once-per-input invocation. * *

    Performance

    - * A stable value that is set is treated as a constant by the JVM, enabling the - * same performance optimizations that are available for {@code final} fields. - * As such, stable values can be used to replace {@code final} fields in cases where - * at-most-once update semantics is crucial, but where the eager initialization - * semantics associated with {@code final} fields is too restrictive. + * The _content_ of a set stable value is treated as a constant by the JVM, provided that + * the reference to the stable value is also constant (e.g. in cases where the + * stable value itself is stored in a `static final` field). *

    - * In JDK 24, {@code final} instance fields in records and hidden classes (such as classes - * spun from method references) are trusted by the JVM, allowing them to be treated as - * constants. However, {@code final} instance fields in regular classes are not - * trusted meaning that such fields are not eligible for performance - * optimizations by the JVM (such as constant folding). + * This means that, at least in some cases, access to the content of a stable value + * enjoys the same constant-folding optimizations that are available when accessing + * `static final` fields. * * @implSpec Implementing classes of {@code StableValue} are free to synchronize on * {@code this} and consequently, care should be taken whenever From f1d964411e8981a405f1f147e33c02227d3f6f56 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 7 Apr 2025 10:29:59 +0200 Subject: [PATCH 247/327] Update doces --- .../share/classes/java/lang/StableValue.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index b51898f45bcb8..f406891f32245 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -47,7 +47,7 @@ import java.util.function.Supplier; /** - * A stable value is a holder of shallowly immutable content that can be lazily computed. + * A stable value is a holder of content that can be set at most once. *

    * A {@code StableValue} is typically created using the factory method * {@linkplain StableValue#of() {@code StableValue.of()}}. When created this way, @@ -395,10 +395,12 @@ *

  • an {@link #isSet()} that returns {@code true}
  • * *

    - * The method {@linkplain StableValue#orElseSet(Supplier) orElseSet()} guarantees that - * the provided {@linkplain Supplier} is invoked successfully at most once even under - * race. Since stable functions and stable collections are built on top of - * {@linkplain StableValue#orElseSet(Supplier) orElseSet()} they too are + * The method {@link #orElseSet(Supplier)} guarantees that the provided + * {@linkplain Supplier} is invoked successfully at most once even under race. + * Invocations of {@link #setOrThrow(Object)} forms a total order of zero or more + * exceptional invocations followed by zero (if the content was already set) or one + * successful invocation. Since stable functions and stable collections are built on top + * of {@linkplain StableValue#orElseSet(Supplier) orElseSet()} they too are * thread safe and guarantee at-most-once-per-input invocation. * *

    Performance

    From 365d9468fdb1841d28813bfea587ec7d9943dd55 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 7 Apr 2025 10:35:44 +0200 Subject: [PATCH 248/327] Fix doc issue with NULL_SENTINEL --- .../jdk/internal/lang/stable/StableValueImpl.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) 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 6a6fbdbe1e55b..5b6099a55f8cc 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 @@ -53,6 +53,10 @@ public final class StableValueImpl implements StableValue { private static final long CONTENT_OFFSET = UNSAFE.objectFieldOffset(StableValueImpl.class, "content"); + // Used to indicate a holder value is `null` (see field `value` below) + // A wrapper method `nullSentinel()` is used for generic type conversion. + private static final Object NULL_SENTINEL = new Object(); + // Generally, fields annotated with `@Stable` are accessed by the JVM using special // memory semantics rules (see `parse.hpp` and `parse(1|2|3).cpp`). // @@ -61,7 +65,7 @@ public final class StableValueImpl implements StableValue { // | Value | Meaning | // | -------------- | ------------ | // | null | Unset | - // | nullSentinel() | Set(null) | + // | NULL_SENTINEL | Set(null) | // | other | Set(other) | // @Stable @@ -189,9 +193,6 @@ private boolean wrapAndSet(Object newValue) { return false; } - // Used to indicate a holder value is `null` (see field `value` below) - // A wrapper method `nullSentinel()` is used for generic type conversion. - private static final Object NULL_SENTINEL = new Object(); // Wraps `null` values into a sentinel value @ForceInline From 0f11342f451791236ce5c422ce310d8b2f22831e Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 7 Apr 2025 10:42:33 +0200 Subject: [PATCH 249/327] Add comment about stable fun/coll and fix typos --- src/java.base/share/classes/java/lang/StableValue.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index f406891f32245..d333820a42880 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -406,11 +406,13 @@ *

    Performance

    * The _content_ of a set stable value is treated as a constant by the JVM, provided that * the reference to the stable value is also constant (e.g. in cases where the - * stable value itself is stored in a `static final` field). + * stable value itself is stored in a {@code static final} field). Stable functions and + * collections are built on top of StableValue. As such, they are also treated as + * constants by the JFM. *

    * This means that, at least in some cases, access to the content of a stable value * enjoys the same constant-folding optimizations that are available when accessing - * `static final` fields. + * {@code static final} fields. * * @implSpec Implementing classes of {@code StableValue} are free to synchronize on * {@code this} and consequently, care should be taken whenever From 5003dce18b675cbed502b5b789e8c8c3ba2812ea Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 7 Apr 2025 11:30:58 +0200 Subject: [PATCH 250/327] Add note an partial and total functions --- src/java.base/share/classes/java/lang/StableValue.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index d333820a42880..ba714cdd7576b 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -223,6 +223,9 @@ * double log256 = Log2Util.log2(81); // Will not constant fold * } * } + * Note: The {@code LOG2_CACHED} function is a _partial function_ that only has a small + * number of allowed inputs whereas {@code Log2Util.log2} is a _total function_ that + * allows the same input set that the original function is defined for. * *

    Stable Collections

    * Stable values can also be used as backing storage for From 26b84e89ff2db1ad85e43df84827b4343c480e7a Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 7 Apr 2025 11:52:12 +0200 Subject: [PATCH 251/327] Fix typo --- src/java.base/share/classes/java/lang/StableValue.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index ba714cdd7576b..8fb2fdaab7b0c 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -223,9 +223,10 @@ * double log256 = Log2Util.log2(81); // Will not constant fold * } * } - * Note: The {@code LOG2_CACHED} function is a _partial function_ that only has a small - * number of allowed inputs whereas {@code Log2Util.log2} is a _total function_ that - * allows the same input set that the original function is defined for. + * Note: The {@code LOG2_CACHED} function is a partial function that only has + * a small number of allowed inputs whereas {@code Log2Util.log2} is a + * total function that allows the same input set that the original function + * is defined for. * *

    Stable Collections

    * Stable values can also be used as backing storage for From 7e15e49eea8e539ac7a8222bea76d81488ba9499 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 7 Apr 2025 13:14:10 +0200 Subject: [PATCH 252/327] Only use partial functions --- .../share/classes/java/lang/StableValue.java | 51 +++++---------- .../lang/StableValue/StableValueTest.java | 63 ++++++++++++++++--- 2 files changed, 72 insertions(+), 42 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 8fb2fdaab7b0c..4b3213c44489b 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -171,20 +171,18 @@ * StableValue.intFunction(CACHED_SIZE, StrictMath::sqrt); * * public static double sqrt(int a) { - * if (a < CACHED_SIZE) { - * return SQRT.apply(a); - * } else { - * return StrictMath.sqrt(a); - * } + * return SQRT.apply(a); * } * } * * public static void computeSomeValues() { * double sqrt9 = sqrt(9); // May eventually constant fold to 3.0 at runtime - * double sqrt81 = sqrt(81); // Will not constant fold * } * *} + * The {@code SqrtUtil.sqrt()} function is a partial function that only allows a + * subset {@code [0, 9]} of the original function's {@code StrictMath::sqrt} input range. + * *

    * A stable function is a function that takes a parameter (of type {@code T}) and * uses it to compute a result (of type {@code R}) that is then cached by the backing @@ -199,34 +197,29 @@ * * private Log2Util() {} * - * private static final Set CACHED_KEYS = + * private static final Set KEYS = * Set.of(1, 2, 4, 8, 16, 32); * private static final UnaryOperator LOG2_ORIGINAL = * i -> 31 - Integer.numberOfLeadingZeros(i); * - * private static final Function LOG2_CACHED = + * private static final Function LOG2 = * // @link substring="function" target="#function(Set,Function)" : - * StableValue.function(CACHED_KEYS, LOG2_ORIGINAL); + * StableValue.function(KEYS, LOG2_ORIGINAL); * * public static double log2(int a) { - * if (CACHED_KEYS.contains(a)) { - * return LOG2_CACHED.apply(a); - * } else { - * return LOG2_ORIGINAL.apply(a); - * } + * return LOG2.apply(a); * } * * } * * public static double computeSomeValues() { * double log16 = Log2Util.log2(16); // May eventually constant fold to 4 at runtime - * double log256 = Log2Util.log2(81); // Will not constant fold - * } * } - * Note: The {@code LOG2_CACHED} function is a partial function that only has - * a small number of allowed inputs whereas {@code Log2Util.log2} is a - * total function that allows the same input set that the original function - * is defined for. + *} + * + * The {@code Log2Util.log2()} function is a partial function that only allows + * a subset ({@code {1, 2, 4, 8, 16, 32}}) of the original function's + * {@code LOG2_ORIGINAL} input range. * *

    Stable Collections

    * Stable values can also be used as backing storage for @@ -246,17 +239,12 @@ * StableValue.list(CACHED_SIZE, StrictMath::sqrt); * * public static double sqrt(int a) { - * if (a < CACHED_SIZE) { - * return SQRT.get(a); - * } else { - * return StrictMath.sqrt(a); - * } + * return SQRT.get(a); * } * } * * public static void computeSomeValues() { * double sqrt9 = SqrtUtil.sqrt(9); // May eventually constant fold to 3.0 at runtime - * double sqrt81 = SqrtUtil.sqrt(81); // Will not constant fold * } * * } @@ -270,26 +258,21 @@ * * private Log2Util() {} * - * private static final Set CACHED_KEYS = + * private static final Set KEYS = * Set.of(1, 2, 4, 8, 16, 32); * private static final UnaryOperator LOG2_ORIGINAL = * i -> 31 - Integer.numberOfLeadingZeros(i); * - * private static final Map LOG2_CACHED = + * private static final Map LOG2 = * // @link substring="map" target="#map(Set,Function)" : * StableValue.map(CACHED_KEYS, LOG2_ORIGINAL); * * public static double log2(int a) { - * if (CACHED_KEYS.contains(a)) { - * return LOG2_CACHED.get(a); - * } else { - * return LOG2_ORIGINAL.apply(a); - * } + * return LOG2.get(a); * } * * public static double computeSomeValues() { * double log16 = Log2Util.log2(16); // May eventually constant fold to 4 at runtime - * double log256 = Log2Util.log2(256); // Will not constant fold * } * * } diff --git a/test/jdk/java/lang/StableValue/StableValueTest.java b/test/jdk/java/lang/StableValue/StableValueTest.java index d98874583c791..d10d4edab1603 100644 --- a/test/jdk/java/lang/StableValue/StableValueTest.java +++ b/test/jdk/java/lang/StableValue/StableValueTest.java @@ -225,20 +225,39 @@ private SqrtUtil() {} StableValue.intFunction(CACHED_SIZE, StrictMath::sqrt); public static double sqrt(int a) { - if (a < CACHED_SIZE) { - return SQRT.apply(a); - } else { - return StrictMath.sqrt(a); - } + return SQRT.apply(a); } } - double sqrt9 = SqrtUtil.sqrt(9); // May eventually constant fold to 3.0 at runtime - double sqrt81 = SqrtUtil.sqrt(81); // Will not constant fold assertEquals(3, sqrt9); - assertEquals(9, sqrt81); + assertThrows(IllegalArgumentException.class, () -> SqrtUtil.sqrt(16)); + } + + @Test + void intFunctionExample2() { + final class HexUtil { + + private HexUtil() {} + + private static final int SIZE = 0x10; + + private static final IntFunction TO_HEX = + // @link substring="intFunction" target="#intFunction(int,IntFunction)" : + StableValue.intFunction(SIZE, Integer::toHexString); + + public static String toHex(int a) { + return TO_HEX.apply(a); + } + } + + String hex10 = HexUtil.toHex(10); // May eventually constant fold to "A" at runtime + + assertEquals("0", HexUtil.toHex(0x0)); + assertEquals("a", hex10); + assertEquals("f", HexUtil.toHex(0xf)); + assertThrows(IllegalArgumentException.class, () -> HexUtil.toHex(0x10)); } @Test @@ -274,6 +293,34 @@ public static double log2(int a) { assertEquals(8, log256); } + @Test + void functionExample2() { + + class Log2Util { + + private Log2Util() {} + + private static final Set KEYS = + Set.of(1, 2, 4, 8); + private static final UnaryOperator LOG2_ORIGINAL = + i -> 31 - Integer.numberOfLeadingZeros(i); + + private static final Function LOG2 = + // @link substring="function" target="#function(Set,Function)" : + StableValue.function(KEYS, LOG2_ORIGINAL); + + public static double log2(int a) { + return LOG2.apply(a); + } + + } + + double log16 = Log2Util.log2(8); // May eventually constant fold to 3.0 at runtime + + assertEquals(3, log16); + assertThrows(IllegalArgumentException.class, () -> Log2Util.log2(3)); + } + private static final BiPredicate, Integer> TRY_SET = StableValue::trySet; private static final BiPredicate, Integer> SET_OR_THROW = (s, i) -> { try { From 95f1b3d8dce732569ebf3815beea39b502e7487f Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 7 Apr 2025 13:27:02 +0200 Subject: [PATCH 253/327] Remove parantheses --- src/java.base/share/classes/java/lang/StableValue.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 4b3213c44489b..1dc79ef846dc1 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -218,7 +218,7 @@ *} * * The {@code Log2Util.log2()} function is a partial function that only allows - * a subset ({@code {1, 2, 4, 8, 16, 32}}) of the original function's + * a subset {@code {1, 2, 4, 8, 16, 32}} of the original function's * {@code LOG2_ORIGINAL} input range. * *

    Stable Collections

    From f1f188b2d7ec9c73233254645338c4040e72ff95 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 7 Apr 2025 14:22:00 +0200 Subject: [PATCH 254/327] Update examples --- .../share/classes/java/lang/StableValue.java | 63 ++++++++++--------- .../lang/StableValue/StableValueTest.java | 27 ++++---- 2 files changed, 47 insertions(+), 43 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 1dc79ef846dc1..f5db08157985f 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -160,28 +160,29 @@ * {@linkplain IntFunction}: * * {@snippet lang = java: - * public final class SqrtUtil { + * final class PowerOf2Util { * - * private SqrtUtil() {} + * private PowerOf2Util() {} * - * private static final int CACHED_SIZE = 10; + * private static final int SIZE = 6; + * private static final IntFunction ORIGINAL_POWER_OF_TWO = + * v -> 1 << v; * - * private static final IntFunction SQRT = - * // @link substring="intFunction" target="#intFunction(int,IntFunction)" : - * StableValue.intFunction(CACHED_SIZE, StrictMath::sqrt); + * private static final IntFunction POWER_OF_TWO = + * // @link substring="intFunction" target="#intFunction(int,IntFunction)" : + * StableValue.intFunction(SIZE, ORIGINAL_POWER_OF_TWO); * - * public static double sqrt(int a) { - * return SQRT.apply(a); - * } - * } + * public static int powerOfTwo(int a) { + * return POWER_OF_TWO.apply(a); + * } + * } * - * public static void computeSomeValues() { - * double sqrt9 = sqrt(9); // May eventually constant fold to 3.0 at runtime - * } + * int pwr4 = PowerOf2Util.powerOfTwo(4); // May eventually constant fold to 16 at runtime * *} - * The {@code SqrtUtil.sqrt()} function is a partial function that only allows a - * subset {@code [0, 9]} of the original function's {@code StrictMath::sqrt} input range. + * The {@code PowerOf2Util.powerOfTwo()} function is a partial function that only + * allows a subset {@code [0, 5]} of the original function's {@code ORIGINAL_POWER_OF_TWO} + * input range. * *

    * A stable function is a function that takes a parameter (of type {@code T}) and @@ -199,12 +200,12 @@ * * private static final Set KEYS = * Set.of(1, 2, 4, 8, 16, 32); - * private static final UnaryOperator LOG2_ORIGINAL = + * private static final UnaryOperator ORIGINAL_LOG2 = * i -> 31 - Integer.numberOfLeadingZeros(i); * * private static final Function LOG2 = * // @link substring="function" target="#function(Set,Function)" : - * StableValue.function(KEYS, LOG2_ORIGINAL); + * StableValue.function(KEYS, ORIGINAL_LOG2); * * public static double log2(int a) { * return LOG2.apply(a); @@ -219,7 +220,7 @@ * * The {@code Log2Util.log2()} function is a partial function that only allows * a subset {@code {1, 2, 4, 8, 16, 32}} of the original function's - * {@code LOG2_ORIGINAL} input range. + * {@code ORIGINAL_LOG2} input range. * *

    Stable Collections

    * Stable values can also be used as backing storage for @@ -228,24 +229,24 @@ * are computed when they are first accessed, using a provided {@linkplain IntFunction}: * * {@snippet lang = java: - * public final class SqrtUtil { + * final class PowerOf2Util { * - * private static final int CACHED_SIZE = 10; + * private PowerOf2Util() {} * - * private SqrtUtil() {} + * private static final int SIZE = 6; + * private static final IntFunction ORIGINAL_POWER_OF_TWO = + * v -> 1 << v; * - * private static final List SQRT = - * // @link substring="list" target="#list(int,IntFunction)" : - * StableValue.list(CACHED_SIZE, StrictMath::sqrt); + * private static final List POWER_OF_TWO = + * // @link substring="list" target="#list(int,IntFunction)" : + * StableValue.list(SIZE, ORIGINAL_POWER_OF_TWO); * - * public static double sqrt(int a) { - * return SQRT.get(a); - * } - * } + * public static int powerOfTwo(int a) { + * return POWER_OF_TWO.gety(a); + * } + * } * - * public static void computeSomeValues() { - * double sqrt9 = SqrtUtil.sqrt(9); // May eventually constant fold to 3.0 at runtime - * } + * int pwr4 = PowerOf2Util.powerOfTwo(4); // May eventually constant fold to 16 at runtime * * } *

    diff --git a/test/jdk/java/lang/StableValue/StableValueTest.java b/test/jdk/java/lang/StableValue/StableValueTest.java index d10d4edab1603..9dc80f0b392af 100644 --- a/test/jdk/java/lang/StableValue/StableValueTest.java +++ b/test/jdk/java/lang/StableValue/StableValueTest.java @@ -237,27 +237,30 @@ public static double sqrt(int a) { @Test void intFunctionExample2() { - final class HexUtil { + final class PowerOf2Util { - private HexUtil() {} + private PowerOf2Util() {} - private static final int SIZE = 0x10; + private static final int SIZE = 6; + private static final IntFunction ORIGINAL_POWER_OF_TWO = + v -> 1 << v; - private static final IntFunction TO_HEX = + private static final IntFunction POWER_OF_TWO = // @link substring="intFunction" target="#intFunction(int,IntFunction)" : - StableValue.intFunction(SIZE, Integer::toHexString); + StableValue.intFunction(SIZE, ORIGINAL_POWER_OF_TWO); - public static String toHex(int a) { - return TO_HEX.apply(a); + public static int powerOfTwo(int a) { + return POWER_OF_TWO.apply(a); } } - String hex10 = HexUtil.toHex(10); // May eventually constant fold to "A" at runtime + int pwr4 = PowerOf2Util.powerOfTwo(4); // May eventually constant fold to 16 at runtime - assertEquals("0", HexUtil.toHex(0x0)); - assertEquals("a", hex10); - assertEquals("f", HexUtil.toHex(0xf)); - assertThrows(IllegalArgumentException.class, () -> HexUtil.toHex(0x10)); + assertEquals(16, pwr4); + assertEquals(1, PowerOf2Util.powerOfTwo(0)); + assertEquals(8, PowerOf2Util.powerOfTwo(3)); + assertEquals(32, PowerOf2Util.powerOfTwo(5)); + assertThrows(IllegalArgumentException.class, () -> PowerOf2Util.powerOfTwo(10)); } @Test From 0a0e5203723ee880b94264ab484f1cd2c94d9ece Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 7 Apr 2025 14:30:25 +0200 Subject: [PATCH 255/327] Fix typo --- src/java.base/share/classes/java/lang/StableValue.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index f5db08157985f..3f2548e121345 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -242,7 +242,7 @@ * StableValue.list(SIZE, ORIGINAL_POWER_OF_TWO); * * public static int powerOfTwo(int a) { - * return POWER_OF_TWO.gety(a); + * return POWER_OF_TWO.get(a); * } * } * From 111f4d0d4887b4456c6d67fb6e6095772a50c1ce Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 7 Apr 2025 14:38:09 +0200 Subject: [PATCH 256/327] Change double to int --- .../share/classes/java/lang/StableValue.java | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 3f2548e121345..6cbf0d875b905 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -213,9 +213,7 @@ * * } * - * public static double computeSomeValues() { - * double log16 = Log2Util.log2(16); // May eventually constant fold to 4 at runtime - * } + * int log16 = Log2Util.log2(16); // May eventually constant fold to 4 at runtime *} * * The {@code Log2Util.log2()} function is a partial function that only allows @@ -272,11 +270,10 @@ * return LOG2.get(a); * } * - * public static double computeSomeValues() { - * double log16 = Log2Util.log2(16); // May eventually constant fold to 4 at runtime - * } - * * } + * + * int log16 = Log2Util.log2(16); // May eventually constant fold to 4 at runtime + * *} * *

    Composing stable values

    From 5bb5e48af8be921e48f06430a7a2e8a00fab1cba Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 7 Apr 2025 17:57:10 +0200 Subject: [PATCH 257/327] Fix typo in return type --- src/java.base/share/classes/java/lang/StableValue.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 6cbf0d875b905..d027233700062 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -207,7 +207,7 @@ * // @link substring="function" target="#function(Set,Function)" : * StableValue.function(KEYS, ORIGINAL_LOG2); * - * public static double log2(int a) { + * public static int log2(int a) { * return LOG2.apply(a); * } * @@ -266,7 +266,7 @@ * // @link substring="map" target="#map(Set,Function)" : * StableValue.map(CACHED_KEYS, LOG2_ORIGINAL); * - * public static double log2(int a) { + * public static int log2(int a) { * return LOG2.get(a); * } * From 9c7b48b74f912523ff52d31d8c4f0b020992a946 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Thu, 10 Apr 2025 08:30:54 +0200 Subject: [PATCH 258/327] Fix typo --- src/java.base/share/classes/java/lang/StableValue.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index d027233700062..e1ffddf8b964b 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -393,7 +393,7 @@ * the reference to the stable value is also constant (e.g. in cases where the * stable value itself is stored in a {@code static final} field). Stable functions and * collections are built on top of StableValue. As such, they are also treated as - * constants by the JFM. + * constants by the JVM. *

    * This means that, at least in some cases, access to the content of a stable value * enjoys the same constant-folding optimizations that are available when accessing From 6a6dd4b4c4e073cf192e0cb65042fc828fa22e21 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Thu, 10 Apr 2025 12:23:10 +0200 Subject: [PATCH 259/327] Improve docs as per comments --- src/java.base/share/classes/java/lang/StableValue.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index e1ffddf8b964b..e7827b05a1363 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -87,10 +87,10 @@ *

    * If {@code getLogger()} is called from several threads, several instances of * {@code Logger} might be created. However, the content can only be set at most once - * meaning one "winner" is picked among the many loggers. + * meaning the first writer wins. *

    - * To guarantee that, even under races, only one instance of {@code Logger} is ever - * created, the {@linkplain #orElseSet(Supplier) orElseSet()} method can be used + * In order to guarantee that, even under races, only one instance of {@code Logger} is + * evee created, the {@linkplain #orElseSet(Supplier) orElseSet()} method can be used * instead, where the content is atomically and lazily computed via a * {@linkplain Supplier supplier}. In the example below, the supplier is provided in the * form of a lambda expression: From 70a158152243b74924b447434829954393b57660 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Thu, 10 Apr 2025 15:48:03 +0200 Subject: [PATCH 260/327] Remove support function and clean up benchmark --- .../java/util/ImmutableCollections.java | 7 +- .../lang/stable/VarHandleHolderBenchmark.java | 151 ++++++++++++++++++ 2 files changed, 152 insertions(+), 6 deletions(-) create mode 100644 test/micro/org/openjdk/bench/java/lang/stable/VarHandleHolderBenchmark.java diff --git a/src/java.base/share/classes/java/util/ImmutableCollections.java b/src/java.base/share/classes/java/util/ImmutableCollections.java index 0db4bf8a084bd..255190c6b759c 100644 --- a/src/java.base/share/classes/java/util/ImmutableCollections.java +++ b/src/java.base/share/classes/java/util/ImmutableCollections.java @@ -1537,17 +1537,12 @@ static final class StableMap @ForceInline @Override public V get(Object key) { - return getOrDefault0(key, null); + return getOrDefault(key, null); } @ForceInline @Override public V getOrDefault(Object key, V defaultValue) { - return getOrDefault0(key, defaultValue); - } - - @ForceInline - private V getOrDefault0(Object key, V defaultValue) { final StableValueImpl stable = delegate.get(key); if (stable == null) { return defaultValue; diff --git a/test/micro/org/openjdk/bench/java/lang/stable/VarHandleHolderBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/VarHandleHolderBenchmark.java new file mode 100644 index 0000000000000..6b1f8cfab8b5c --- /dev/null +++ b/test/micro/org/openjdk/bench/java/lang/stable/VarHandleHolderBenchmark.java @@ -0,0 +1,151 @@ +package org.openjdk.bench.java.lang.stable; + +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.Warmup; + +import java.lang.foreign.Arena; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; +import java.lang.invoke.VarHandle; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Supplier; + +import static java.lang.foreign.MemoryLayout.PathElement.groupElement; +import static java.util.concurrent.TimeUnit.*; + +@Warmup(iterations = 5, time = 5, timeUnit = SECONDS) +@Measurement(iterations = 5, time = 5, timeUnit = SECONDS) +@Fork(value = 1, jvmArgs = { "--enable-preview" }) +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(NANOSECONDS) +@State(Scope.Benchmark) +public class VarHandleHolderBenchmark { + + private static final MemoryLayout LAYOUT = MemoryLayout.structLayout( + ValueLayout.JAVA_INT.withName("x"), + ValueLayout.JAVA_INT.withName("y") + ); + + private static final long SIZEOF = LAYOUT.byteSize(); + private static final long OFFSET_X = LAYOUT.byteOffset(MemoryLayout.PathElement.groupElement("x")); + private static final long OFFSET_Y = LAYOUT.byteOffset(groupElement("y")); + + static final class MyVarHandleLookup implements Function { + @Override + public VarHandle apply(String name) { + return LAYOUT.arrayElementVarHandle(groupElement(name)).withInvokeExactBehavior(); + } + } + + private static final Function VAR_HANDLE_FUNCTION = new MyVarHandleLookup(); + + private static final VarHandle VH_X = VAR_HANDLE_FUNCTION.apply("x"); + private static final VarHandle VH_Y = VAR_HANDLE_FUNCTION.apply("y"); + + private static final Supplier SV_X = StableValue.supplier(() -> VAR_HANDLE_FUNCTION.apply("x")); + private static final Supplier SV_Y = StableValue.supplier(() -> VAR_HANDLE_FUNCTION.apply("y")); + + private static final Map U_MAP = Map.of( + "x", VH_X, + "y", VH_Y); + + private static final Map U_MAP_ELEMENT = Map.of( + "x", LAYOUT.varHandle(groupElement("x")), + "y", LAYOUT.varHandle(groupElement("y"))); + + private static final Map S_MAP = StableValue.map( + Set.of("x", "y"), + VAR_HANDLE_FUNCTION); + + private static final Function S_FUN = StableValue.function( + Set.of("x", "y"), + VAR_HANDLE_FUNCTION); + + private static final MemorySegment confined; + static { + var array = new int[512 * (int) SIZEOF / (int) ValueLayout.JAVA_INT.byteSize()]; + var heap = MemorySegment.ofArray(array); + for(var i = 0; i < 512; i++) { + heap.set(ValueLayout.JAVA_INT, i * SIZEOF + OFFSET_X, i); + heap.set(ValueLayout.JAVA_INT, i * SIZEOF + OFFSET_Y, i); + } + confined = Arena.ofConfined().allocate(LAYOUT, 512); + confined.copyFrom(heap); + } + + @Benchmark + public int confinedVarHandleLoop() { + var sum = 0; + for (var i = 0; i < 512; i++) { + var x = (int) VH_X.get(confined, 0L, (long) i); + var y = (int) VH_Y.get(confined, 0L, (long) i); + sum += x /*+y*/; + } + return sum; + } + + @Benchmark + public int confinedStableValueLoop() { + var sum = 0; + for (var i = 0; i < 512; i++) { + var x = (int) SV_X.get().get(confined, 0L, (long) i); + var y = (int) SV_Y.get().get(confined, 0L, (long) i); + sum += x + y; + } + return sum; + } + + @Benchmark + public int confinedStableMapLoop() { + var sum = 0; + for (var i = 0; i < 512; i++) { + var x = (int) S_MAP.get("x").get(confined, 0L, (long) i); + var y = (int) S_MAP.get("y").get(confined, 0L, (long) i); + sum += x + y; + } + return sum; + } + + @Benchmark + public int confinedStableMapElementLoop() { + var sum = 0; + for (var i = 0; i < 512; i++) { + var x = (int) U_MAP_ELEMENT.get("x").get(confined, i * 8L); + var y = (int) U_MAP_ELEMENT.get("y").get(confined, i * 8L); + sum += x + y; + } + return sum; + } + + @Benchmark + public int confinedUnmodifiableMapLoop() { + var sum = 0; + for (var i = 0; i < 512; i++) { + var x = (int) U_MAP.get("x").get(confined, 0L, (long) i); + var y = (int) U_MAP.get("y").get(confined, 0L, (long) i); + sum += x + y; + } + return sum; + } + + @Benchmark + public int confinedStableFunctionLoop() { + var sum = 0; + for (var i = 0; i < 512; i++) { + var x = (int) S_FUN.apply("x").get(confined, 0L, (long) i); + var y = (int) S_FUN.apply("y").get(confined, 0L, (long) i); + sum += x + y; + } + return sum; + } +} \ No newline at end of file From 53837e43c9f36d97f0863d4c5eb49166e26697f8 Mon Sep 17 00:00:00 2001 From: Per-Ake Minborg Date: Thu, 10 Apr 2025 15:51:46 +0200 Subject: [PATCH 261/327] Update src/java.base/share/classes/java/lang/StableValue.java Co-authored-by: Viktor Klang --- src/java.base/share/classes/java/lang/StableValue.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index e7827b05a1363..da0dece250ead 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -90,7 +90,7 @@ * meaning the first writer wins. *

    * In order to guarantee that, even under races, only one instance of {@code Logger} is - * evee created, the {@linkplain #orElseSet(Supplier) orElseSet()} method can be used + * ever created, the {@linkplain #orElseSet(Supplier) orElseSet()} method can be used * instead, where the content is atomically and lazily computed via a * {@linkplain Supplier supplier}. In the example below, the supplier is provided in the * form of a lambda expression: From 934a890f5005f3a5d3cbf289dedf0f3976acefb2 Mon Sep 17 00:00:00 2001 From: Per-Ake Minborg Date: Thu, 10 Apr 2025 15:52:08 +0200 Subject: [PATCH 262/327] Update src/java.base/share/classes/java/lang/StableValue.java Co-authored-by: Viktor Klang --- src/java.base/share/classes/java/lang/StableValue.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index da0dece250ead..99adc61130254 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -91,7 +91,7 @@ *

    * In order to guarantee that, even under races, only one instance of {@code Logger} is * ever created, the {@linkplain #orElseSet(Supplier) orElseSet()} method can be used - * instead, where the content is atomically and lazily computed via a + * instead, where the content is lazily computed, and atomically set, via a * {@linkplain Supplier supplier}. In the example below, the supplier is provided in the * form of a lambda expression: * From e74a2f121bc22615164d9e683c50e26a72a771e2 Mon Sep 17 00:00:00 2001 From: Per-Ake Minborg Date: Thu, 10 Apr 2025 15:54:03 +0200 Subject: [PATCH 263/327] Update src/java.base/share/classes/java/lang/StableValue.java Co-authored-by: Viktor Klang --- src/java.base/share/classes/java/lang/StableValue.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 99adc61130254..6a4f077a14ae5 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -128,7 +128,7 @@ *

    Stable Functions

    * Stable values provide the foundation for higher-level functional abstractions. A * stable supplier is a supplier that computes a value and then caches it into - * a backing stable value storage for later use. A stable supplier is created via the + * a backing stable value storage for subsequent use. A stable supplier is created via the * {@linkplain StableValue#supplier(Supplier) StableValue.supplier()} factory, by * providing an original {@linkplain Supplier} which is invoked when the stable supplier * is first accessed: From 3c0a9b9912e1505e782825680b06947411d01290 Mon Sep 17 00:00:00 2001 From: Per-Ake Minborg Date: Thu, 10 Apr 2025 15:55:27 +0200 Subject: [PATCH 264/327] Update src/java.base/share/classes/java/lang/StableValue.java Co-authored-by: Viktor Klang --- src/java.base/share/classes/java/lang/StableValue.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 6a4f077a14ae5..05bba139516ec 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -166,7 +166,7 @@ * * private static final int SIZE = 6; * private static final IntFunction ORIGINAL_POWER_OF_TWO = - * v -> 1 << v; + * v -> 1 << v; * * private static final IntFunction POWER_OF_TWO = * // @link substring="intFunction" target="#intFunction(int,IntFunction)" : From 0d2697991c8b08a5a1e0dfa9a295c18b64b7d8d1 Mon Sep 17 00:00:00 2001 From: Per-Ake Minborg Date: Thu, 10 Apr 2025 15:55:48 +0200 Subject: [PATCH 265/327] Update src/java.base/share/classes/java/lang/StableValue.java Co-authored-by: Viktor Klang --- src/java.base/share/classes/java/lang/StableValue.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 05bba139516ec..9760fd8628c24 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -169,8 +169,8 @@ * v -> 1 << v; * * private static final IntFunction POWER_OF_TWO = - * // @link substring="intFunction" target="#intFunction(int,IntFunction)" : - * StableValue.intFunction(SIZE, ORIGINAL_POWER_OF_TWO); + * // @link substring="intFunction" target="#intFunction(int,IntFunction)" : + * StableValue.intFunction(SIZE, ORIGINAL_POWER_OF_TWO); * * public static int powerOfTwo(int a) { * return POWER_OF_TWO.apply(a); From 7d417d25cf7c08e260768f64bf95867654cefcbf Mon Sep 17 00:00:00 2001 From: Per-Ake Minborg Date: Thu, 10 Apr 2025 15:56:14 +0200 Subject: [PATCH 266/327] Update src/java.base/share/classes/java/lang/StableValue.java Co-authored-by: Viktor Klang --- src/java.base/share/classes/java/lang/StableValue.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 9760fd8628c24..d4cf59ea8b9de 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -177,7 +177,7 @@ * } * } * - * int pwr4 = PowerOf2Util.powerOfTwo(4); // May eventually constant fold to 16 at runtime + * int result = PowerOf2Util.powerOfTwo(4); // May eventually constant fold to 16 at runtime * *} * The {@code PowerOf2Util.powerOfTwo()} function is a partial function that only From 4acda49a9af7098c61e27cc793f8b7b3ea90a5a3 Mon Sep 17 00:00:00 2001 From: Per-Ake Minborg Date: Thu, 10 Apr 2025 15:56:39 +0200 Subject: [PATCH 267/327] Update src/java.base/share/classes/java/lang/StableValue.java Co-authored-by: Viktor Klang --- src/java.base/share/classes/java/lang/StableValue.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index d4cf59ea8b9de..3cb2941fc163e 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -199,13 +199,13 @@ * private Log2Util() {} * * private static final Set KEYS = - * Set.of(1, 2, 4, 8, 16, 32); + * Set.of(1, 2, 4, 8, 16, 32); * private static final UnaryOperator ORIGINAL_LOG2 = - * i -> 31 - Integer.numberOfLeadingZeros(i); + * i -> 31 - Integer.numberOfLeadingZeros(i); * * private static final Function LOG2 = - * // @link substring="function" target="#function(Set,Function)" : - * StableValue.function(KEYS, ORIGINAL_LOG2); + * // @link substring="function" target="#function(Set,Function)" : + * StableValue.function(KEYS, ORIGINAL_LOG2); * * public static int log2(int a) { * return LOG2.apply(a); From a724a4b5775f8437d05ff91033dc232f7dbe431e Mon Sep 17 00:00:00 2001 From: Per-Ake Minborg Date: Thu, 10 Apr 2025 15:57:05 +0200 Subject: [PATCH 268/327] Update src/java.base/share/classes/java/lang/StableValue.java Co-authored-by: Viktor Klang --- src/java.base/share/classes/java/lang/StableValue.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 3cb2941fc163e..428d88803266c 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -213,7 +213,7 @@ * * } * - * int log16 = Log2Util.log2(16); // May eventually constant fold to 4 at runtime + * int result = Log2Util.log2(16); // May eventually constant fold to 4 at runtime *} * * The {@code Log2Util.log2()} function is a partial function that only allows From 4c1c898bc9c1ebe3258b489b83052eaf9c153827 Mon Sep 17 00:00:00 2001 From: Per-Ake Minborg Date: Thu, 10 Apr 2025 15:57:22 +0200 Subject: [PATCH 269/327] Update src/java.base/share/classes/java/lang/StableValue.java Co-authored-by: Viktor Klang --- src/java.base/share/classes/java/lang/StableValue.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 428d88803266c..90bee796d5a8b 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -236,8 +236,8 @@ * v -> 1 << v; * * private static final List POWER_OF_TWO = - * // @link substring="list" target="#list(int,IntFunction)" : - * StableValue.list(SIZE, ORIGINAL_POWER_OF_TWO); + * // @link substring="list" target="#list(int,IntFunction)" : + * StableValue.list(SIZE, ORIGINAL_POWER_OF_TWO); * * public static int powerOfTwo(int a) { * return POWER_OF_TWO.get(a); From 525f46413c48c7e5da7368c113cd2fbad70876df Mon Sep 17 00:00:00 2001 From: Per-Ake Minborg Date: Thu, 10 Apr 2025 15:57:46 +0200 Subject: [PATCH 270/327] Update src/java.base/share/classes/java/lang/StableValue.java Co-authored-by: Viktor Klang --- src/java.base/share/classes/java/lang/StableValue.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 90bee796d5a8b..56ac3cb4b0775 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -244,7 +244,7 @@ * } * } * - * int pwr4 = PowerOf2Util.powerOfTwo(4); // May eventually constant fold to 16 at runtime + * int result = PowerOf2Util.powerOfTwo(4); // May eventually constant fold to 16 at runtime * * } *

    From a393f2c9f44ebd8a7b0d4945f323b69b9cff8850 Mon Sep 17 00:00:00 2001 From: Per-Ake Minborg Date: Thu, 10 Apr 2025 15:58:05 +0200 Subject: [PATCH 271/327] Update src/java.base/share/classes/java/lang/StableValue.java Co-authored-by: Viktor Klang --- src/java.base/share/classes/java/lang/StableValue.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 56ac3cb4b0775..2aaa0415fa486 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -258,13 +258,13 @@ * private Log2Util() {} * * private static final Set KEYS = - * Set.of(1, 2, 4, 8, 16, 32); + * Set.of(1, 2, 4, 8, 16, 32); * private static final UnaryOperator LOG2_ORIGINAL = - * i -> 31 - Integer.numberOfLeadingZeros(i); + * i -> 31 - Integer.numberOfLeadingZeros(i); * * private static final Map LOG2 = - * // @link substring="map" target="#map(Set,Function)" : - * StableValue.map(CACHED_KEYS, LOG2_ORIGINAL); + * // @link substring="map" target="#map(Set,Function)" : + * StableValue.map(CACHED_KEYS, LOG2_ORIGINAL); * * public static int log2(int a) { * return LOG2.get(a); From 94a344553d50e78a1bf1e9baada614f197e94228 Mon Sep 17 00:00:00 2001 From: Per-Ake Minborg Date: Thu, 10 Apr 2025 16:00:09 +0200 Subject: [PATCH 272/327] Update src/java.base/share/classes/java/lang/StableValue.java Co-authored-by: Viktor Klang --- src/java.base/share/classes/java/lang/StableValue.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 2aaa0415fa486..aa53ef17155d7 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -272,7 +272,7 @@ * * } * - * int log16 = Log2Util.log2(16); // May eventually constant fold to 4 at runtime + * int result = Log2Util.log2(16); // May eventually constant fold to 4 at runtime * *} * From d28607bf411a9e47ad4989abc4012c535b946bcf Mon Sep 17 00:00:00 2001 From: Per-Ake Minborg Date: Thu, 10 Apr 2025 16:00:29 +0200 Subject: [PATCH 273/327] Update src/java.base/share/classes/java/lang/StableValue.java Co-authored-by: Viktor Klang --- src/java.base/share/classes/java/lang/StableValue.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index aa53ef17155d7..17623663c7441 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -324,7 +324,7 @@ * private static final int MAX_SIZE_INT = 46; * * private static final IntFunction FIB = - * StableValue.intFunction(MAX_SIZE_INT, Fibonacci::fib); + * StableValue.intFunction(MAX_SIZE_INT, Fibonacci::fib); * * public static int fib(int n) { * return n < 2 From 8e3178d1b1bb300ff83b73335184b0daa7dd7fbd Mon Sep 17 00:00:00 2001 From: Per-Ake Minborg Date: Thu, 10 Apr 2025 16:01:23 +0200 Subject: [PATCH 274/327] Update src/java.base/share/classes/java/lang/StableValue.java Co-authored-by: Viktor Klang --- src/java.base/share/classes/java/lang/StableValue.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 17623663c7441..0b75434931962 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -488,7 +488,7 @@ public sealed interface StableValue * as a lazily computed value or memoized result, as in: * * {@snippet lang=java: - * Value witness = stable.orElseSet(Value::new); + * Value v = stable.orElseSet(Value::new); * } *

    * When this method returns successfully, the content is always set. From 9c4c6ed453d804e9e4524369b5407c26b593ab6f Mon Sep 17 00:00:00 2001 From: Per-Ake Minborg Date: Thu, 10 Apr 2025 16:05:25 +0200 Subject: [PATCH 275/327] Update test/jdk/java/lang/StableValue/StableListTest.java Co-authored-by: Viktor Klang --- test/jdk/java/lang/StableValue/StableListTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/jdk/java/lang/StableValue/StableListTest.java b/test/jdk/java/lang/StableValue/StableListTest.java index 2a27ef5a85af7..9ad89a0de4f01 100644 --- a/test/jdk/java/lang/StableValue/StableListTest.java +++ b/test/jdk/java/lang/StableValue/StableListTest.java @@ -22,7 +22,7 @@ */ /* @test - * @summary Basic tests for LazyList methods + * @summary Basic tests for StableList methods * @modules java.base/jdk.internal.lang.stable * @enablePreview * @run junit StableListTest From cc57b323dadc166b79ede7b7715b4834ba2fe26c Mon Sep 17 00:00:00 2001 From: Per-Ake Minborg Date: Thu, 10 Apr 2025 16:05:51 +0200 Subject: [PATCH 276/327] Update test/jdk/java/lang/StableValue/StableIntFunctionTest.java Co-authored-by: Viktor Klang --- test/jdk/java/lang/StableValue/StableIntFunctionTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/jdk/java/lang/StableValue/StableIntFunctionTest.java b/test/jdk/java/lang/StableValue/StableIntFunctionTest.java index 5a29ba0ecc887..083cca71dcbd1 100644 --- a/test/jdk/java/lang/StableValue/StableIntFunctionTest.java +++ b/test/jdk/java/lang/StableValue/StableIntFunctionTest.java @@ -22,7 +22,7 @@ */ /* @test - * @summary Basic tests for StableIntFunctionTest methods + * @summary Basic tests for StableIntFunction methods * @enablePreview * @run junit StableIntFunctionTest */ From 9448a3ab4c52f5059cfc43e01b10bcc317b048a2 Mon Sep 17 00:00:00 2001 From: Per-Ake Minborg Date: Thu, 10 Apr 2025 16:06:09 +0200 Subject: [PATCH 277/327] Update test/jdk/java/lang/StableValue/StableFunctionTest.java Co-authored-by: Viktor Klang --- test/jdk/java/lang/StableValue/StableFunctionTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/jdk/java/lang/StableValue/StableFunctionTest.java b/test/jdk/java/lang/StableValue/StableFunctionTest.java index 2a674b70be6e8..b4a100da1dce1 100644 --- a/test/jdk/java/lang/StableValue/StableFunctionTest.java +++ b/test/jdk/java/lang/StableValue/StableFunctionTest.java @@ -22,7 +22,7 @@ */ /* @test - * @summary Basic tests for StableFunctionTest methods + * @summary Basic tests for StableFunction methods * @enablePreview * @run junit StableFunctionTest */ From dc01634a14de1185e5adcd6529633065c8184977 Mon Sep 17 00:00:00 2001 From: Per-Ake Minborg Date: Thu, 10 Apr 2025 16:06:29 +0200 Subject: [PATCH 278/327] Update test/jdk/java/lang/StableValue/StableMapTest.java Co-authored-by: Viktor Klang --- test/jdk/java/lang/StableValue/StableMapTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/jdk/java/lang/StableValue/StableMapTest.java b/test/jdk/java/lang/StableValue/StableMapTest.java index 0f034520c3b84..96cd46f756b45 100644 --- a/test/jdk/java/lang/StableValue/StableMapTest.java +++ b/test/jdk/java/lang/StableValue/StableMapTest.java @@ -22,7 +22,7 @@ */ /* @test - * @summary Basic tests for LazyMap methods + * @summary Basic tests for StableMap methods * @modules java.base/jdk.internal.lang.stable * @enablePreview * @run junit StableMapTest From cdc1282e3efbf5f55c2fe8f5d3eeb3460f713d69 Mon Sep 17 00:00:00 2001 From: Per-Ake Minborg Date: Thu, 10 Apr 2025 16:06:43 +0200 Subject: [PATCH 279/327] Update test/jdk/java/lang/StableValue/StableSupplierTest.java Co-authored-by: Viktor Klang --- test/jdk/java/lang/StableValue/StableSupplierTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/jdk/java/lang/StableValue/StableSupplierTest.java b/test/jdk/java/lang/StableValue/StableSupplierTest.java index 3e23ccc631ada..06038335c3358 100644 --- a/test/jdk/java/lang/StableValue/StableSupplierTest.java +++ b/test/jdk/java/lang/StableValue/StableSupplierTest.java @@ -22,7 +22,7 @@ */ /* @test - * @summary Basic tests for StableSupplierTest methods + * @summary Basic tests for StableSupplier methods * @enablePreview * @run junit StableSupplierTest */ From 2a36bb334a6b0cd13805e3cf42bcc306d0a5b57b Mon Sep 17 00:00:00 2001 From: Per-Ake Minborg Date: Thu, 10 Apr 2025 16:06:59 +0200 Subject: [PATCH 280/327] Update test/jdk/java/lang/StableValue/StableValueFactoriesTest.java Co-authored-by: Viktor Klang --- test/jdk/java/lang/StableValue/StableValueFactoriesTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/jdk/java/lang/StableValue/StableValueFactoriesTest.java b/test/jdk/java/lang/StableValue/StableValueFactoriesTest.java index 6bbb0c67c3ea3..85aee0cbeec66 100644 --- a/test/jdk/java/lang/StableValue/StableValueFactoriesTest.java +++ b/test/jdk/java/lang/StableValue/StableValueFactoriesTest.java @@ -22,7 +22,7 @@ */ /* @test - * @summary Basic tests for StableValueFactoriesTest implementations + * @summary Basic tests for StableValue factory implementations * @modules java.base/jdk.internal.lang.stable * @enablePreview * @run junit StableValueFactoriesTest From cd654b2f51d151a8e7cd486cd80c8e4cef66c45e Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Thu, 10 Apr 2025 16:15:20 +0200 Subject: [PATCH 281/327] Address comments on original vs underlying --- .../share/classes/java/lang/StableValue.java | 100 +++++++++--------- 1 file changed, 50 insertions(+), 50 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 0b75434931962..53d525a954b19 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -117,7 +117,7 @@ * retrieve its content. If the stable value is unset, then {@code orElseSet()} * evaluates and sets the content; the content is then returned to the client. In other * words, {@code orElseSet()} guarantees that a stable value's content is set - * before it is used. + * before it returns. *

    * Furthermore, {@code orElseSet()} guarantees that the supplier provided is * evaluated at most once, even when {@code logger.orElseSet()} is invoked concurrently. @@ -130,7 +130,7 @@ * stable supplier is a supplier that computes a value and then caches it into * a backing stable value storage for subsequent use. A stable supplier is created via the * {@linkplain StableValue#supplier(Supplier) StableValue.supplier()} factory, by - * providing an original {@linkplain Supplier} which is invoked when the stable supplier + * providing an underlying {@linkplain Supplier} which is invoked when the stable supplier * is first accessed: * * {@snippet lang = java: @@ -155,8 +155,8 @@ * for that parameter value. A stable {@link IntFunction} is created via the * {@linkplain StableValue#intFunction(int, IntFunction) StableValue.intFunction()} * factory. Upon creation, the input range (i.e. {@code [0, size)}) is specified together - * with an original {@linkplain IntFunction} which is invoked at most once per input value. - * In effect, the stable int function will act like a cache for the original + * with an underlying {@linkplain IntFunction} which is invoked at most once per input + * value. In effect, the stable int function will act like a cache for the underlying * {@linkplain IntFunction}: * * {@snippet lang = java: @@ -165,12 +165,12 @@ * private PowerOf2Util() {} * * private static final int SIZE = 6; - * private static final IntFunction ORIGINAL_POWER_OF_TWO = + * private static final IntFunction UNDERLYING_POWER_OF_TWO = * v -> 1 << v; * * private static final IntFunction POWER_OF_TWO = * // @link substring="intFunction" target="#intFunction(int,IntFunction)" : - * StableValue.intFunction(SIZE, ORIGINAL_POWER_OF_TWO); + * StableValue.intFunction(SIZE, UNDERLYING_POWER_OF_TWO); * * public static int powerOfTwo(int a) { * return POWER_OF_TWO.apply(a); @@ -181,7 +181,7 @@ * *} * The {@code PowerOf2Util.powerOfTwo()} function is a partial function that only - * allows a subset {@code [0, 5]} of the original function's {@code ORIGINAL_POWER_OF_TWO} + * allows a subset {@code [0, 5]} of the underlying function's {@code UNDERLYING_POWER_OF_TWO} * input range. * *

    @@ -189,9 +189,9 @@ * uses it to compute a result (of type {@code R}) that is then cached by the backing * stable value storage for that parameter value. A stable function is created via the * {@linkplain StableValue#function(Set, Function) StableValue.function()} factory. - * Upon creation, the input {@linkplain Set} is specified together with an original + * Upon creation, the input {@linkplain Set} is specified together with an underlying * {@linkplain Function} which is invoked at most once per input value. In effect, the - * stable function will act like a cache for the original {@linkplain Function}: + * stable function will act like a cache for the underlying {@linkplain Function}: * * {@snippet lang = java: * class Log2Util { @@ -200,12 +200,12 @@ * * private static final Set KEYS = * Set.of(1, 2, 4, 8, 16, 32); - * private static final UnaryOperator ORIGINAL_LOG2 = + * private static final UnaryOperator UNDERLYING_LOG2 = * i -> 31 - Integer.numberOfLeadingZeros(i); * * private static final Function LOG2 = * // @link substring="function" target="#function(Set,Function)" : - * StableValue.function(KEYS, ORIGINAL_LOG2); + * StableValue.function(KEYS, UNDERLYING_LOG2); * * public static int log2(int a) { * return LOG2.apply(a); @@ -217,8 +217,8 @@ *} * * The {@code Log2Util.log2()} function is a partial function that only allows - * a subset {@code {1, 2, 4, 8, 16, 32}} of the original function's - * {@code ORIGINAL_LOG2} input range. + * a subset {@code {1, 2, 4, 8, 16, 32}} of the underlying function's + * {@code UNDERLYING_LOG2} input range. * *

    Stable Collections

    * Stable values can also be used as backing storage for @@ -232,12 +232,12 @@ * private PowerOf2Util() {} * * private static final int SIZE = 6; - * private static final IntFunction ORIGINAL_POWER_OF_TWO = + * private static final IntFunction UNDERLYING_POWER_OF_TWO = * v -> 1 << v; * * private static final List POWER_OF_TWO = * // @link substring="list" target="#list(int,IntFunction)" : - * StableValue.list(SIZE, ORIGINAL_POWER_OF_TWO); + * StableValue.list(SIZE, UNDERLYING_POWER_OF_TWO); * * public static int powerOfTwo(int a) { * return POWER_OF_TWO.get(a); @@ -259,12 +259,12 @@ * * private static final Set KEYS = * Set.of(1, 2, 4, 8, 16, 32); - * private static final UnaryOperator LOG2_ORIGINAL = + * private static final UnaryOperator UNDERLYING_LOG2 = * i -> 31 - Integer.numberOfLeadingZeros(i); * * private static final Map LOG2 = * // @link substring="map" target="#map(Set,Function)" : - * StableValue.map(CACHED_KEYS, LOG2_ORIGINAL); + * StableValue.map(CACHED_KEYS, UNDERLYING_LOG2); * * public static int log2(int a) { * return LOG2.get(a); @@ -314,8 +314,8 @@ * created. Upon such a creation, the dependent {@code Foo} will first be created if * the {@code Foo} does not already exist. *

    - * Here is another example where a more complex dependency graph is created in which - * integers in the Fibonacci delta series are lazily computed: + * Another example, which has a more complex dependency graph, is to lazily computing the + * Fibonacci sequence: * {@snippet lang = java: * public final class Fibonacci { * @@ -559,65 +559,65 @@ static StableValue of(T content) { * {@return a new stable supplier} *

    * The returned {@linkplain Supplier supplier} is a caching supplier that records - * the value of the provided {@code original} supplier upon being first accessed via + * the value of the provided {@code underlying} supplier upon being first accessed via * the returned supplier's {@linkplain Supplier#get() get()} method. *

    - * The provided {@code original} supplier is guaranteed to be successfully invoked + * The provided {@code underlying} supplier is guaranteed to be successfully invoked * at most once even in a multi-threaded environment. Competing threads invoking the * returned supplier's {@linkplain Supplier#get() get()} method when a value is * already under computation will block until a value is computed or an exception is * thrown by the computing thread. *

    - * If the provided {@code original} supplier throws an exception, it is relayed + * If the provided {@code underlying} supplier throws an exception, it is relayed * to the initial caller and no content is recorded. *

    - * If the provided {@code original} supplier recursively calls the returned + * If the provided {@code underlying} supplier recursively calls the returned * supplier, an {@linkplain IllegalStateException} will be thrown. * - * @param original supplier used to compute a cached value - * @param the type of results supplied by the returned supplier + * @param underlying supplier used to compute a cached value + * @param the type of results supplied by the returned supplier */ - static Supplier supplier(Supplier original) { - Objects.requireNonNull(original); - return StableSupplier.of(original); + static Supplier supplier(Supplier underlying) { + Objects.requireNonNull(underlying); + return StableSupplier.of(underlying); } /** * {@return a new stable {@linkplain IntFunction}} *

    * The returned function is a caching function that, for each allowed {@code int} - * input, records the values of the provided {@code original} + * input, records the values of the provided {@code underlying} * function upon being first accessed via the returned function's * {@linkplain IntFunction#apply(int) apply()} method. If the returned function is * invoked with an input that is not allowed, an {@link IllegalArgumentException} * will be thrown. *

    - * The provided {@code original} function is guaranteed to be successfully invoked + * The provided {@code underlying} function is guaranteed to be successfully invoked * at most once per allowed input, even in a multi-threaded environment. Competing * threads invoking the returned function's * {@linkplain IntFunction#apply(int) apply()} method when a value is already under * computation will block until a value is computed or an exception is thrown by * the computing thread. *

    - * If the provided {@code original} function throws an exception, it is relayed + * If the provided {@code underlying} function throws an exception, it is relayed * to the initial caller and no content is recorded. *

    - * If the provided {@code original} function recursively calls the returned + * If the provided {@code underlying} function recursively calls the returned * function for the same index, an {@linkplain IllegalStateException} will * be thrown. * - * @param size the size of the allowed inputs in {@code [0, size)} - * @param original IntFunction used to compute cached values - * @param the type of results delivered by the returned IntFunction + * @param size the size of the allowed inputs in {@code [0, size)} + * @param underlying IntFunction used to compute cached values + * @param the type of results delivered by the returned IntFunction * @throws IllegalArgumentException if the provided {@code size} is negative. */ static IntFunction intFunction(int size, - IntFunction original) { + IntFunction underlying) { if (size < 0) { throw new IllegalArgumentException(); } - Objects.requireNonNull(original); - return StableIntFunction.of(size, original); + Objects.requireNonNull(underlying); + return StableIntFunction.of(size, underlying); } /** @@ -625,38 +625,38 @@ static IntFunction intFunction(int size, *

    * The returned function is a caching function that, for each allowed * input in the given set of {@code inputs}, records the values of the provided - * {@code original} function upon being first accessed via the returned function's + * {@code underlying} function upon being first accessed via the returned function's * {@linkplain Function#apply(Object) apply()} method. If the returned function is * invoked with an input that is not allowed, an {@link IllegalArgumentException} * will be thrown. *

    - * The provided {@code original} function is guaranteed to be successfully invoked + * The provided {@code underlying} function is guaranteed to be successfully invoked * at most once per allowed input, even in a multi-threaded environment. Competing * threads invoking the returned function's {@linkplain Function#apply(Object) apply()} * method when a value is already under computation will block until a value is * computed or an exception is thrown by the computing thread. *

    - * If the provided {@code original} function throws an exception, it is relayed to + * If the provided {@code underlying} function throws an exception, it is relayed to * the initial caller and no content is recorded. *

    - * If the provided {@code original} function recursively calls the returned + * If the provided {@code underlying} function recursively calls the returned * function for the same input, an {@linkplain IllegalStateException} will * be thrown. * - * @param inputs the set of (non-null) allowed input values - * @param original Function used to compute cached values - * @param the type of the input to the returned Function - * @param the type of results delivered by the returned Function + * @param inputs the set of (non-null) allowed input values + * @param underlying Function used to compute cached values + * @param the type of the input to the returned Function + * @param the type of results delivered by the returned Function * @throws NullPointerException if the provided set of {@code inputs} contains a * {@code null} element. */ static Function function(Set inputs, - Function original) { + Function underlying) { Objects.requireNonNull(inputs); - Objects.requireNonNull(original); + Objects.requireNonNull(underlying); return inputs instanceof EnumSet && !inputs.isEmpty() - ? StableEnumFunction.of(inputs, original) - : StableFunction.of(inputs, original); + ? StableEnumFunction.of(inputs, underlying) + : StableFunction.of(inputs, underlying); } /** From b68f77af6fb4121ce9f147fd508cf48d54ebc51a Mon Sep 17 00:00:00 2001 From: Per-Ake Minborg Date: Tue, 22 Apr 2025 11:29:00 +0200 Subject: [PATCH 282/327] Update src/java.base/share/classes/java/lang/StableValue.java Co-authored-by: Viktor Klang --- src/java.base/share/classes/java/lang/StableValue.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 53d525a954b19..d4dddbe08fa97 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -314,8 +314,8 @@ * created. Upon such a creation, the dependent {@code Foo} will first be created if * the {@code Foo} does not already exist. *

    - * Another example, which has a more complex dependency graph, is to lazily computing the - * Fibonacci sequence: + * Another example, which has a more complex dependency graph, is to compute the + * Fibonacci sequence lazily: * {@snippet lang = java: * public final class Fibonacci { * From e25ee0324428cc4944616638473ddfc9ef04ab7e Mon Sep 17 00:00:00 2001 From: Per-Ake Minborg Date: Tue, 22 Apr 2025 11:49:36 +0200 Subject: [PATCH 283/327] Update src/java.base/share/classes/java/lang/StableValue.java Co-authored-by: Viktor Klang --- src/java.base/share/classes/java/lang/StableValue.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index d4dddbe08fa97..360348c47c639 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -412,7 +412,7 @@ * @implNote A {@code StableValue} is mainly intended to be a non-public field in * a class and is usually neither exposed directly via accessors nor passed as * a method parameter. - * Stable functions and collections make all reasonable efforts to provide + * Stable functions and collections make reasonable efforts to provide * {@link Object#toString()} operations that do not trigger evaluation * of the internal stable values when called. * Stable collections have {@link Object#equals(Object)} operations that tries From 9602fbf51666bda743b84662232173b1a1bbcbc3 Mon Sep 17 00:00:00 2001 From: Per-Ake Minborg Date: Tue, 22 Apr 2025 11:50:16 +0200 Subject: [PATCH 284/327] Update src/java.base/share/classes/java/lang/StableValue.java Co-authored-by: Viktor Klang --- src/java.base/share/classes/java/lang/StableValue.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 360348c47c639..c3dc8ef45531d 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -415,7 +415,7 @@ * Stable functions and collections make reasonable efforts to provide * {@link Object#toString()} operations that do not trigger evaluation * of the internal stable values when called. - * Stable collections have {@link Object#equals(Object)} operations that tries + * Stable collections have {@link Object#equals(Object)} operations that try * to minimize evaluation of the internal stable values when called. * As objects can be set via stable values but never removed, this can be a source * of unintended memory leaks. A stable value's content is From 7b80b03f017a5e8b1ad246804caf4655bf29f7fa Mon Sep 17 00:00:00 2001 From: Per-Ake Minborg Date: Tue, 22 Apr 2025 11:52:26 +0200 Subject: [PATCH 285/327] Update src/java.base/share/classes/java/lang/StableValue.java Co-authored-by: Viktor Klang --- src/java.base/share/classes/java/lang/StableValue.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index c3dc8ef45531d..9b8ce8439b267 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -483,7 +483,7 @@ public sealed interface StableValue * with different suppliers, only one of them will be invoked provided it completes * without throwing an exception. *

    - * If the supplier throws an (unchecked) exception, the exception is rethrown, and no + * If the supplier throws an (unchecked) exception, the exception is rethrown and no * content is set. The most common usage is to construct a new object serving * as a lazily computed value or memoized result, as in: * From 38956e6c4b56033b3b3c1048ff343db1f513005a Mon Sep 17 00:00:00 2001 From: Per-Ake Minborg Date: Tue, 22 Apr 2025 11:53:54 +0200 Subject: [PATCH 286/327] Update src/java.base/share/classes/java/lang/StableValue.java Co-authored-by: Viktor Klang --- src/java.base/share/classes/java/lang/StableValue.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 9b8ce8439b267..8a0cbfd5d67d7 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -667,7 +667,7 @@ static Function function(Set inputs, * provided {@code mapper} when they are first accessed * (e.g. via {@linkplain List#get(int) List::get}). *

    - * The provided {@code mapper} int function is guaranteed to be successfully invoked + * The provided {@code mapper} function is guaranteed to be successfully invoked * at most once per list index, even in a multi-threaded environment. Competing * threads accessing an element already under computation will block until an element * is computed or an exception is thrown by the computing thread. From 58a58ca3bb0d35b89eb6c1ad6e7e64b906052598 Mon Sep 17 00:00:00 2001 From: Per-Ake Minborg Date: Tue, 22 Apr 2025 11:59:21 +0200 Subject: [PATCH 287/327] Update src/java.base/share/classes/java/lang/StableValue.java Co-authored-by: Viktor Klang --- src/java.base/share/classes/java/lang/StableValue.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 8a0cbfd5d67d7..8e15c1ce4eb7a 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -716,7 +716,7 @@ static List list(int size, * threads accessing a value already under computation will block until an element * is computed or an exception is thrown by the computing thread. *

    - * If the provided {@code mapper} throws an exception, it is relayed to the initial + * If invoking the provided {@code mapper} function throws an exception, it is rethrown to the initial * caller and no value associated with the provided key is recorded. *

    * Any direct {@link Map#values()} or {@link Map#entrySet()} views From 433537bb5fc1dd3c47596126be00f0c1c8df3573 Mon Sep 17 00:00:00 2001 From: Per-Ake Minborg Date: Tue, 22 Apr 2025 12:06:12 +0200 Subject: [PATCH 288/327] Update test/jdk/java/lang/StableValue/StableMapTest.java Co-authored-by: Viktor Klang --- test/jdk/java/lang/StableValue/StableMapTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/jdk/java/lang/StableValue/StableMapTest.java b/test/jdk/java/lang/StableValue/StableMapTest.java index 96cd46f756b45..77580cf5d3576 100644 --- a/test/jdk/java/lang/StableValue/StableMapTest.java +++ b/test/jdk/java/lang/StableValue/StableMapTest.java @@ -137,7 +137,7 @@ void toStringTest() { assertEquals("{" + KEY + "=" + KEY + "}", map.toString()); String actual = newMap().toString(); assertTrue(actual.startsWith("{")); - for (int key:KEYS) { + for (int key : KEYS) { assertTrue(actual.contains(key + "=.unset")); } assertTrue(actual.endsWith("}")); From c6d27c6c77c764216679dc3b187739b32d91ebf6 Mon Sep 17 00:00:00 2001 From: Per-Ake Minborg Date: Tue, 22 Apr 2025 12:06:29 +0200 Subject: [PATCH 289/327] Update test/jdk/java/lang/StableValue/StableMapTest.java Co-authored-by: Viktor Klang --- test/jdk/java/lang/StableValue/StableMapTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/jdk/java/lang/StableValue/StableMapTest.java b/test/jdk/java/lang/StableValue/StableMapTest.java index 77580cf5d3576..10c4b1c3ac203 100644 --- a/test/jdk/java/lang/StableValue/StableMapTest.java +++ b/test/jdk/java/lang/StableValue/StableMapTest.java @@ -314,7 +314,7 @@ void nullKeys() { record Operation(String name, Consumer> consumer) implements Consumer> { @java.lang.Override - public void accept(Map map) { consumer.accept(map); } + public void accept(Map map) { consumer.accept(map); } @java.lang.Override public String toString() { return name; } } From fa775238442381b05863925e5cd3e3d393f6034b Mon Sep 17 00:00:00 2001 From: Per-Ake Minborg Date: Tue, 22 Apr 2025 12:06:41 +0200 Subject: [PATCH 290/327] Update test/jdk/java/lang/StableValue/StableMapTest.java Co-authored-by: Viktor Klang --- test/jdk/java/lang/StableValue/StableMapTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/jdk/java/lang/StableValue/StableMapTest.java b/test/jdk/java/lang/StableValue/StableMapTest.java index 10c4b1c3ac203..7cbc7eec770d1 100644 --- a/test/jdk/java/lang/StableValue/StableMapTest.java +++ b/test/jdk/java/lang/StableValue/StableMapTest.java @@ -321,7 +321,7 @@ record Operation(String name, static Stream nullAverseOperations() { return Stream.of( - new Operation("forEach", m -> m.forEach(null)) + new Operation("forEach", m -> m.forEach(null)) ); } From e1b5d144daf4086573c5469157484adb24a9a408 Mon Sep 17 00:00:00 2001 From: Per-Ake Minborg Date: Tue, 22 Apr 2025 12:06:55 +0200 Subject: [PATCH 291/327] Update test/jdk/java/lang/StableValue/StableMapTest.java Co-authored-by: Viktor Klang --- .../java/lang/StableValue/StableMapTest.java | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/test/jdk/java/lang/StableValue/StableMapTest.java b/test/jdk/java/lang/StableValue/StableMapTest.java index 7cbc7eec770d1..2b00d725a25b9 100644 --- a/test/jdk/java/lang/StableValue/StableMapTest.java +++ b/test/jdk/java/lang/StableValue/StableMapTest.java @@ -327,18 +327,18 @@ static Stream nullAverseOperations() { static Stream unsupportedOperations() { return Stream.of( - new Operation("clear", Map::clear), - new Operation("compute", m -> m.compute(KEY, (_, _) -> 1)), - new Operation("computeIfAbsent", m -> m.computeIfAbsent(KEY, _ -> 1)), - new Operation("computeIfPresent", m -> m.computeIfPresent(KEY, (_, _) -> 1)), - new Operation("merge", m -> m.merge(KEY, KEY, (a, _) -> a)), - new Operation("put", m -> m.put(0, 0)), - new Operation("putAll", m -> m.putAll(Map.of())), - new Operation("remove1", m -> m.remove(KEY)), - new Operation("remove2", m -> m.remove(KEY, KEY)), - new Operation("replace2", m -> m.replace(KEY, 1)), - new Operation("replace3", m -> m.replace(KEY, KEY, 1)), - new Operation("replaceAll", m -> m.replaceAll((a, _) -> a)) + new Operation("clear", Map::clear), + new Operation("compute", m -> m.compute(KEY, (_, _) -> 1)), + new Operation("computeIfAbsent", m -> m.computeIfAbsent(KEY, _ -> 1)), + new Operation("computeIfPresent", m -> m.computeIfPresent(KEY, (_, _) -> 1)), + new Operation("merge", m -> m.merge(KEY, KEY, (a, _) -> a)), + new Operation("put", m -> m.put(0, 0)), + new Operation("putAll", m -> m.putAll(Map.of())), + new Operation("remove1", m -> m.remove(KEY)), + new Operation("remove2", m -> m.remove(KEY, KEY)), + new Operation("replace2", m -> m.replace(KEY, 1)), + new Operation("replace3", m -> m.replace(KEY, KEY, 1)), + new Operation("replaceAll", m -> m.replaceAll((a, _) -> a)) ); } From c3fabd68b7fcea8c8ed37593c9a2fe93bf6e680d Mon Sep 17 00:00:00 2001 From: Per-Ake Minborg Date: Tue, 22 Apr 2025 12:07:42 +0200 Subject: [PATCH 292/327] Update src/java.base/share/classes/java/lang/StableValue.java Co-authored-by: Jorn Vernee --- src/java.base/share/classes/java/lang/StableValue.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 8e15c1ce4eb7a..052a2a9e7a9ae 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -115,7 +115,7 @@ *

    * The {@code getLogger()} method calls {@code logger.orElseSet()} on the stable value to * retrieve its content. If the stable value is unset, then {@code orElseSet()} - * evaluates and sets the content; the content is then returned to the client. In other + * evaluates the given supplier, and sets the content to the result; the content is then returned to the client. In other * words, {@code orElseSet()} guarantees that a stable value's content is set * before it returns. *

    From 5952f9e6b7aeba43db7c58a4a8ecedaf7705eaaf Mon Sep 17 00:00:00 2001 From: Per-Ake Minborg Date: Tue, 22 Apr 2025 12:12:25 +0200 Subject: [PATCH 293/327] Update src/java.base/share/classes/java/lang/StableValue.java Co-authored-by: Jorn Vernee --- src/java.base/share/classes/java/lang/StableValue.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 052a2a9e7a9ae..9e3fa33a7ed94 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -278,7 +278,7 @@ * *

    Composing stable values

    * A stable value can depend on other stable values, forming a dependency graph - * that can be lazily computed but where access to individual elements still can be + * that can be lazily computed but where access to individual elements can still be * performant. In the following example, a single {@code Foo} and a {@code Bar} * instance (that is dependent on the {@code Foo} instance) are lazily created, both of * which are held by stable values: From 99be781817fd2689f2a8c976b2715ac8e956ebc5 Mon Sep 17 00:00:00 2001 From: Per-Ake Minborg Date: Tue, 22 Apr 2025 12:41:15 +0200 Subject: [PATCH 294/327] Update src/java.base/share/classes/java/lang/StableValue.java Co-authored-by: Jorn Vernee --- src/java.base/share/classes/java/lang/StableValue.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 9e3fa33a7ed94..ad8d4d215b5c4 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -382,7 +382,7 @@ *

    * The method {@link #orElseSet(Supplier)} guarantees that the provided * {@linkplain Supplier} is invoked successfully at most once even under race. - * Invocations of {@link #setOrThrow(Object)} forms a total order of zero or more + * Invocations of {@link #setOrThrow(Object)} form a total order of zero or more * exceptional invocations followed by zero (if the content was already set) or one * successful invocation. Since stable functions and stable collections are built on top * of {@linkplain StableValue#orElseSet(Supplier) orElseSet()} they too are From db700805af8949357784c6d7d01060adcad7fbe7 Mon Sep 17 00:00:00 2001 From: Per-Ake Minborg Date: Tue, 22 Apr 2025 12:42:03 +0200 Subject: [PATCH 295/327] Update src/java.base/share/classes/java/lang/StableValue.java Co-authored-by: Jorn Vernee --- src/java.base/share/classes/java/lang/StableValue.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index ad8d4d215b5c4..af164b6e5c8b4 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -392,8 +392,8 @@ * The _content_ of a set stable value is treated as a constant by the JVM, provided that * the reference to the stable value is also constant (e.g. in cases where the * stable value itself is stored in a {@code static final} field). Stable functions and - * collections are built on top of StableValue. As such, they are also treated as - * constants by the JVM. + * collections are built on top of StableValue. As such, their contents is also treated as + * constant by the JVM. *

    * This means that, at least in some cases, access to the content of a stable value * enjoys the same constant-folding optimizations that are available when accessing From 828cae770a8350c9207ae9dd16ccab4585760ae7 Mon Sep 17 00:00:00 2001 From: Per-Ake Minborg Date: Tue, 22 Apr 2025 12:59:35 +0200 Subject: [PATCH 296/327] Update src/java.base/share/classes/java/lang/StableValue.java Co-authored-by: Jorn Vernee --- src/java.base/share/classes/java/lang/StableValue.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index af164b6e5c8b4..5a87ffa5dbbc2 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -450,8 +450,8 @@ public sealed interface StableValue * @return {@code true} if the content of this StableValue was set to the * provided {@code content}, {@code false} otherwise * @param content to set - * @throws IllegalStateException if this method is invoked directly by a supplier - * provided to the {@link #orElseSet(Supplier)} method. + * @throws IllegalStateException if a supplier invoked by {@link #orElseSet(Supplier)} recursively + * attempts to set this stable value by calling this method. */ boolean trySet(T content); From 848843eb7526ebfecf72f6d3a9cb56799984d23f Mon Sep 17 00:00:00 2001 From: Per-Ake Minborg Date: Tue, 22 Apr 2025 13:05:49 +0200 Subject: [PATCH 297/327] Update src/java.base/share/classes/java/lang/StableValue.java Co-authored-by: Jorn Vernee --- src/java.base/share/classes/java/lang/StableValue.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 5a87ffa5dbbc2..39f8b57aea613 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -627,7 +627,7 @@ static IntFunction intFunction(int size, * input in the given set of {@code inputs}, records the values of the provided * {@code underlying} function upon being first accessed via the returned function's * {@linkplain Function#apply(Object) apply()} method. If the returned function is - * invoked with an input that is not allowed, an {@link IllegalArgumentException} + * invoked with an input that is not in {@code inputs}, an {@link IllegalArgumentException} * will be thrown. *

    * The provided {@code underlying} function is guaranteed to be successfully invoked From 561801fc8768cdb3d33759d7f1d5756a8faf088b Mon Sep 17 00:00:00 2001 From: Per-Ake Minborg Date: Tue, 22 Apr 2025 13:06:38 +0200 Subject: [PATCH 298/327] Update src/java.base/share/classes/java/lang/StableValue.java Co-authored-by: Jorn Vernee --- src/java.base/share/classes/java/lang/StableValue.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 39f8b57aea613..75566532abb7a 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -589,7 +589,7 @@ static Supplier supplier(Supplier underlying) { * input, records the values of the provided {@code underlying} * function upon being first accessed via the returned function's * {@linkplain IntFunction#apply(int) apply()} method. If the returned function is - * invoked with an input that is not allowed, an {@link IllegalArgumentException} + * invoked with an input that is not in the range {@code [0, size)}, an {@link IllegalArgumentException} * will be thrown. *

    * The provided {@code underlying} function is guaranteed to be successfully invoked From af1ca2bd4a889a7c3651a88d1efa7ad099dd8526 Mon Sep 17 00:00:00 2001 From: Per-Ake Minborg Date: Tue, 22 Apr 2025 13:07:26 +0200 Subject: [PATCH 299/327] Update src/java.base/share/classes/java/lang/StableValue.java Co-authored-by: Jorn Vernee --- src/java.base/share/classes/java/lang/StableValue.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 75566532abb7a..e31a502921764 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -603,7 +603,7 @@ static Supplier supplier(Supplier underlying) { * to the initial caller and no content is recorded. *

    * If the provided {@code underlying} function recursively calls the returned - * function for the same index, an {@linkplain IllegalStateException} will + * function for the same input, an {@linkplain IllegalStateException} will * be thrown. * * @param size the size of the allowed inputs in {@code [0, size)} From 68e2aebf6fdf5190ac94de1e4b315701baab4ecf Mon Sep 17 00:00:00 2001 From: Per-Ake Minborg Date: Tue, 22 Apr 2025 13:08:26 +0200 Subject: [PATCH 300/327] Update src/java.base/share/classes/java/lang/StableValue.java Co-authored-by: Jorn Vernee --- src/java.base/share/classes/java/lang/StableValue.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index e31a502921764..f6e458378c5ac 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -644,7 +644,7 @@ static IntFunction intFunction(int size, * be thrown. * * @param inputs the set of (non-null) allowed input values - * @param underlying Function used to compute cached values + * @param underlying {@code Function} used to compute cached values * @param the type of the input to the returned Function * @param the type of results delivered by the returned Function * @throws NullPointerException if the provided set of {@code inputs} contains a From f97235f900005a0d6215c105ce05f99a708426c7 Mon Sep 17 00:00:00 2001 From: Per-Ake Minborg Date: Tue, 22 Apr 2025 13:25:14 +0200 Subject: [PATCH 301/327] Update src/java.base/share/classes/java/util/ImmutableCollections.java Co-authored-by: Jorn Vernee --- .../share/classes/java/util/ImmutableCollections.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/java.base/share/classes/java/util/ImmutableCollections.java b/src/java.base/share/classes/java/util/ImmutableCollections.java index 255190c6b759c..85351a80263d7 100644 --- a/src/java.base/share/classes/java/util/ImmutableCollections.java +++ b/src/java.base/share/classes/java/util/ImmutableCollections.java @@ -1625,9 +1625,7 @@ public String toString() { final StableValueImpl[] values = delegate.values().toArray(new IntFunction[]>() { @Override public StableValueImpl[] apply(int len) { - @SuppressWarnings("unchecked") - var array = (StableValueImpl[]) Array.newInstance(StableValueImpl.class, len); - return array; + return new StableValueImpl[len]; } }); return StableUtil.renderElements(StableMap.this, "StableMap", values); From 444188afb2352855da99eab46ae18d608375dfa6 Mon Sep 17 00:00:00 2001 From: Per-Ake Minborg Date: Tue, 22 Apr 2025 13:53:42 +0200 Subject: [PATCH 302/327] Update src/java.base/share/classes/jdk/internal/lang/stable/StableValueImpl.java Co-authored-by: Jorn Vernee --- .../share/classes/jdk/internal/lang/stable/StableValueImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 5b6099a55f8cc..ba82f7f1c6239 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 @@ -93,7 +93,7 @@ public boolean trySet(T content) { @Override public void setOrThrow(T content) { if (!trySet(content)) { - // Neither the set content nor the provided content is reveled in the + // Neither the set content nor the provided content is revealed in the // exception message as it might be sensitive. throw new IllegalStateException("The content is already set"); } From 63f3d1c2704e74f8f94a970c01979d44c2a0d6d6 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 22 Apr 2025 14:01:26 +0200 Subject: [PATCH 303/327] Address comments in PR --- .../share/classes/java/lang/StableValue.java | 87 +++++++++++-------- .../java/util/ImmutableCollections.java | 2 +- .../lang/stable/StableEnumFunction.java | 15 ++-- .../internal/lang/stable/StableFunction.java | 2 +- .../lang/stable/StableIntFunction.java | 2 +- .../internal/lang/stable/StableSupplier.java | 2 +- .../jdk/internal/lang/stable/StableUtil.java | 66 +++++++++----- .../internal/lang/stable/StableValueImpl.java | 4 +- .../lang/StableValue/StableFunctionTest.java | 2 +- .../StableValue/StableIntFunctionTest.java | 2 +- .../java/lang/StableValue/StableListTest.java | 2 +- .../java/lang/StableValue/StableMapTest.java | 2 +- .../lang/StableValue/StableSupplierTest.java | 2 +- .../java/lang/StableValue/StableTestUtil.java | 2 +- .../lang/StableValue/StableValueTest.java | 2 +- .../StableValuesSafePublicationTest.java | 2 +- .../StableValue/TrustedFieldTypeTest.java | 2 +- 17 files changed, 117 insertions(+), 81 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index f6e458378c5ac..8ca5b963089c7 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * 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 @@ -31,6 +31,7 @@ import jdk.internal.lang.stable.StableFunction; import jdk.internal.lang.stable.StableIntFunction; import jdk.internal.lang.stable.StableSupplier; +import jdk.internal.lang.stable.StableUtil; import jdk.internal.lang.stable.StableValueImpl; import java.io.Serializable; @@ -119,11 +120,11 @@ * words, {@code orElseSet()} guarantees that a stable value's content is set * before it returns. *

    - * Furthermore, {@code orElseSet()} guarantees that the supplier provided is - * evaluated at most once, even when {@code logger.orElseSet()} is invoked concurrently. - * This property is crucial as evaluation of the supplier may have side effects, - * e.g., the call above to {@code Logger.create()} may result in storage resources - * being prepared. + * Furthermore, {@code orElseSet()} guarantees that out of one or more suppliers provided, + * only at most one is ever evaluated and that one is only ever evaluated once, + * even when {@code logger.orElseSet()} is invoked concurrently. This property is crucial + * as evaluation of the supplier may have side effects, for example, the call above to + * {@code Logger.create()} may result in storage resources being prepared. * *

    Stable Functions

    * Stable values provide the foundation for higher-level functional abstractions. A @@ -340,8 +341,8 @@ * traditional non-caching recursive fibonacci method. Once computed, the VM is free to * constant-fold expressions like {@code Fibonacci.fib(5)}. *

    - * The fibonacci example above is a dependency graph with no circular dependencies (i.e., - * it is a dependency tree): + * The fibonacci example above is a directed acyclic graph (i.e., + * it has no circular dependencies and is therefore a dependency tree): *{@snippet lang=text : * * ___________fib(5)____________ @@ -360,7 +361,8 @@ *

    Thread Safety

    * The content of a stable value is guaranteed to be set at most once. If competing * threads are racing to set a stable value, only one update succeeds, while other updates - * are blocked until the stable value becomes set. + * are blocked until the stable value becomes set whereafter the other updates + * observes the stable value is set and leave the stable value unchanged. *

    * The at-most-once write operation on a stable value that succeeds * (e.g. {@linkplain #trySet(Object) trySet()}) @@ -385,10 +387,11 @@ * Invocations of {@link #setOrThrow(Object)} form a total order of zero or more * exceptional invocations followed by zero (if the content was already set) or one * successful invocation. Since stable functions and stable collections are built on top - * of {@linkplain StableValue#orElseSet(Supplier) orElseSet()} they too are - * thread safe and guarantee at-most-once-per-input invocation. + * of the same principles as {@linkplain StableValue#orElseSet(Supplier) orElseSet()} they + * too are thread safe and guarantee at-most-once-per-input invocation. * *

    Performance

    +<<<<<<< Updated upstream * The _content_ of a set stable value is treated as a constant by the JVM, provided that * the reference to the stable value is also constant (e.g. in cases where the * stable value itself is stored in a {@code static final} field). Stable functions and @@ -398,12 +401,22 @@ * This means that, at least in some cases, access to the content of a stable value * enjoys the same constant-folding optimizations that are available when accessing * {@code static final} fields. +======= + * As the contents of a stable value can never change after it has been set, a JVM + * implementation may, for a set stable value, elide all future reads of that + * stable value, and instead directly use any content that it has previously observed. + * This is true if the reference to the stable value is a constant (e.g. in cases where + * the stable value itself is stored in a {@code static final} field). Stable functions + * and collections are built on top of StableValue. As such, they might also be eligible + * for the same JVM optimizations as for StableValue. +>>>>>>> Stashed changes * * @implSpec Implementing classes of {@code StableValue} are free to synchronize on - * {@code this} and consequently, care should be taken whenever - * (directly or indirectly) synchronizing on a {@code StableValue}. Failure to - * do this may lead to deadlock. Stable functions and collections on the - * other hand are guaranteed not to synchronize on {@code this}. + * {@code this} and consequently, it should be avoided to + * (directly or indirectly) synchronize on a {@code StableValue}. Hence, + * synchronizing on {@code this} may lead to deadlock. Stable functions + * and collections on the other hand are guaranteed not to synchronize + * on {@code this}. * Except for a {@code StableValue}'s content itself, an {@linkplain #orElse(Object) orElse(other)} * parameter, and an {@linkplain #equals(Object) equals(obj)} parameter; all * method parameters must be non-null or a {@link NullPointerException} @@ -419,16 +432,17 @@ * to minimize evaluation of the internal stable values when called. * As objects can be set via stable values but never removed, this can be a source * of unintended memory leaks. A stable value's content is - * {@linkplain java.lang.ref##reachability strongly reachable}. Clients are - * advised that {@linkplain java.lang.ref##reachability reachable} stable values - * will hold their set content until the stable value itself is collected. + * {@linkplain java.lang.ref##reachability strongly reachable}. + * Be advised that reachable stable values will hold their set content until + * the stable value itself is collected. * A {@code StableValue} that has a type parameter {@code T} that is an array * type (of arbitrary rank) will only allow the JVM to treat the array reference * as a stable value but not its components. Clients can instead use * {@linkplain #list(int, IntFunction) a stable list} of arbitrary depth, which * provides stable components. More generally, a stable value can hold other * stable values of arbitrary depth and still provide transitive constantness. - * Stable values, functions and collections are not {@link Serializable}. + * + * @implNote Stable values, functions and collections are not {@link Serializable}. * * @param type of the content * @@ -566,7 +580,8 @@ static StableValue of(T content) { * at most once even in a multi-threaded environment. Competing threads invoking the * returned supplier's {@linkplain Supplier#get() get()} method when a value is * already under computation will block until a value is computed or an exception is - * thrown by the computing thread. + * thrown by the computing thread. The computing threads will then observe the newly + * computed value (if any) and will then never execute. *

    * If the provided {@code underlying} supplier throws an exception, it is relayed * to the initial caller and no content is recorded. @@ -599,23 +614,22 @@ static Supplier supplier(Supplier underlying) { * computation will block until a value is computed or an exception is thrown by * the computing thread. *

    - * If the provided {@code underlying} function throws an exception, it is relayed - * to the initial caller and no content is recorded. + * If invoking the provided {@code underlying} function throws an exception, it is + * relayed to the initial caller and no content is recorded. *

    * If the provided {@code underlying} function recursively calls the returned * function for the same input, an {@linkplain IllegalStateException} will * be thrown. * - * @param size the size of the allowed inputs in {@code [0, size)} + * @param size the size of the allowed inputs in the continuous + * interval {@code [0, size)} * @param underlying IntFunction used to compute cached values * @param the type of results delivered by the returned IntFunction * @throws IllegalArgumentException if the provided {@code size} is negative. */ static IntFunction intFunction(int size, IntFunction underlying) { - if (size < 0) { - throw new IllegalArgumentException(); - } + StableUtil.assertSizeNonNegative(size); Objects.requireNonNull(underlying); return StableIntFunction.of(size, underlying); } @@ -636,8 +650,8 @@ static IntFunction intFunction(int size, * method when a value is already under computation will block until a value is * computed or an exception is thrown by the computing thread. *

    - * If the provided {@code underlying} function throws an exception, it is relayed to - * the initial caller and no content is recorded. + * If invoking the provided {@code underlying} function throws an exception, it is + * relayed to the initial caller and no content is recorded. *

    * If the provided {@code underlying} function recursively calls the returned * function for the same input, an {@linkplain IllegalStateException} will @@ -653,6 +667,8 @@ static IntFunction intFunction(int size, static Function function(Set inputs, Function underlying) { Objects.requireNonNull(inputs); + // Checking that the Set of inputs does not contain a `null` value is made in the + // implementing classes. Objects.requireNonNull(underlying); return inputs instanceof EnumSet && !inputs.isEmpty() ? StableEnumFunction.of(inputs, underlying) @@ -672,8 +688,8 @@ static Function function(Set inputs, * threads accessing an element already under computation will block until an element * is computed or an exception is thrown by the computing thread. *

    - * If the provided {@code mapper} throws an exception, it is relayed to the initial - * caller and no value for the element is recorded. + * If invoking the provided {@code mapper} function throws an exception, it + * is rethrown to the initial caller and no value for the element is recorded. *

    * Any direct {@link List#subList(int, int) subList} or {@link List#reversed()} views * of the returned list are also stable. @@ -696,9 +712,7 @@ static Function function(Set inputs, */ static List list(int size, IntFunction mapper) { - if (size < 0) { - throw new IllegalArgumentException(); - } + StableUtil.assertSizeNonNegative(size); Objects.requireNonNull(mapper); return SharedSecrets.getJavaUtilCollectionAccess().stableList(size, mapper); } @@ -716,8 +730,9 @@ static List list(int size, * threads accessing a value already under computation will block until an element * is computed or an exception is thrown by the computing thread. *

    - * If invoking the provided {@code mapper} function throws an exception, it is rethrown to the initial - * caller and no value associated with the provided key is recorded. + * If invoking the provided {@code mapper} function throws an exception, it + * is rethrown to the initial caller and no value associated with the provided key + * is recorded. *

    * Any direct {@link Map#values()} or {@link Map#entrySet()} views * of the returned map are also stable. @@ -740,6 +755,8 @@ static List list(int size, static Map map(Set keys, Function mapper) { Objects.requireNonNull(keys); + // Checking that the Set of keys does not contain a `null` value is made in the + // implementing class. Objects.requireNonNull(mapper); return SharedSecrets.getJavaUtilCollectionAccess().stableMap(keys, mapper); } diff --git a/src/java.base/share/classes/java/util/ImmutableCollections.java b/src/java.base/share/classes/java/util/ImmutableCollections.java index 85351a80263d7..86ba800cf895d 100644 --- a/src/java.base/share/classes/java/util/ImmutableCollections.java +++ b/src/java.base/share/classes/java/util/ImmutableCollections.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 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 diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java index bb958353d9513..88be2cea1b6bc 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * 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 @@ -33,6 +33,7 @@ import java.util.ArrayList; import java.util.BitSet; import java.util.Collection; +import java.util.EnumSet; import java.util.Map; import java.util.Set; import java.util.function.Function; @@ -64,11 +65,8 @@ public R apply(E value) { } final int index = value.ordinal() - firstOrdinal; final StableValueImpl delegate; - try { - delegate = delegates[index]; - } catch (ArrayIndexOutOfBoundsException ioob) { - throw new IllegalArgumentException("Input not allowed: " + value, ioob); - } + // Since we did the member.test above, we know the index is in bounds + delegate = delegates[index]; return delegate.orElseSet(new Supplier() { @Override public R get() { return original.apply(value); }}); @@ -100,8 +98,9 @@ public String toString() { @SuppressWarnings("unchecked") public static , R> Function of(Set inputs, Function original) { - final BitSet bitSet = new BitSet(inputs.size()); // The input set is not empty + final Class enumType = (Class)inputs.iterator().next().getClass(); + final BitSet bitSet = new BitSet(enumType.getEnumConstants().length); int min = Integer.MAX_VALUE; int max = Integer.MIN_VALUE; for (T t : inputs) { @@ -110,9 +109,7 @@ public static , R> Function of(Set input max = Math.max(max, ordinal); bitSet.set(ordinal); } - final int size = max - min + 1; - final Class enumType = (Class)inputs.iterator().next().getClass(); final IntPredicate member = ImmutableBitSetPredicate.of(bitSet); return (Function) new StableEnumFunction(enumType, min, member, StableUtil.array(size), (Function) original); } diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableFunction.java index c03949c1a728b..1b10593e5e883 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableFunction.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * 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 diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableIntFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableIntFunction.java index 7a4b9de5c701c..8bb046a762d94 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableIntFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableIntFunction.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * 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 diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableSupplier.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableSupplier.java index 01125664190e5..bdb40648db6f0 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableSupplier.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableSupplier.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * 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 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 a4fe74791b647..b2040c5f95217 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 @@ -1,9 +1,34 @@ +/* + * 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 java.util.Collection; import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.StringJoiner; public final class StableUtil { @@ -20,48 +45,38 @@ public static String renderElements(Object self, StableValueImpl[] delegates, int offset, int length) { - final StringBuilder sb = new StringBuilder(); - sb.append("["); - boolean first = true; + final StringJoiner sj = new StringJoiner(", ", "[", "]"); for (int i = 0; i < length; i++) { - if (first) { first = false; } else { sb.append(", "); } final Object value = delegates[i + offset].wrappedContentAcquire(); if (value == self) { - sb.append("(this ").append(selfName).append(")"); + sj.add("(this " + selfName + ")"); } else { - sb.append(StableValueImpl.renderWrapped(value)); + sj.add(StableValueImpl.renderWrapped(value)); } } - sb.append("]"); - return sb.toString(); + return sj.toString(); } public static String renderMappings(Object self, String selfName, Iterable>> delegates, boolean curly) { - final StringBuilder sb = new StringBuilder(); - sb.append(curly ? "{" : "["); - boolean first = true; + final StringJoiner sj = new StringJoiner(", ", curly ? "{" : "[", curly ? "}" : "]"); for (var e : delegates) { - if (first) { first = false; } else { sb.append(", "); } final Object value = e.getValue().wrappedContentAcquire(); - sb.append(e.getKey()).append('='); + final String valueString; if (value == self) { - sb.append("(this ").append(selfName).append(")"); + valueString = ("(this ") + selfName + ")"; } else { - sb.append(StableValueImpl.renderWrapped(value)); + valueString = StableValueImpl.renderWrapped(value); } + sj.add(e.getKey() + "=" + valueString); } - sb.append(curly ? "}" : "]"); - return sb.toString(); + return sj.toString(); } - public static StableValueImpl[] array(int size) { - if (size < 0) { - throw new IllegalArgumentException(); - } + assertSizeNonNegative(size); @SuppressWarnings("unchecked") final var stableValues = (StableValueImpl[]) new StableValueImpl[size]; for (int i = 0; i < size; i++) { @@ -80,4 +95,11 @@ public static Map> map(Set keys) { } return Map.ofEntries(entries); } + + 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 ba82f7f1c6239..38ff7ada62df5 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * 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 @@ -170,7 +170,7 @@ static String renderWrapped(Object t) { private void preventReentry() { if (Thread.holdsLock(this)) { - throw new IllegalStateException("Recursive initialization is not supported"); + throw new IllegalStateException("Recursive initialization of a stable value is illegal"); } } diff --git a/test/jdk/java/lang/StableValue/StableFunctionTest.java b/test/jdk/java/lang/StableValue/StableFunctionTest.java index b4a100da1dce1..4476c04695783 100644 --- a/test/jdk/java/lang/StableValue/StableFunctionTest.java +++ b/test/jdk/java/lang/StableValue/StableFunctionTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * 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 diff --git a/test/jdk/java/lang/StableValue/StableIntFunctionTest.java b/test/jdk/java/lang/StableValue/StableIntFunctionTest.java index 083cca71dcbd1..7397a688ee68f 100644 --- a/test/jdk/java/lang/StableValue/StableIntFunctionTest.java +++ b/test/jdk/java/lang/StableValue/StableIntFunctionTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * 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 diff --git a/test/jdk/java/lang/StableValue/StableListTest.java b/test/jdk/java/lang/StableValue/StableListTest.java index 9ad89a0de4f01..4d4970ad0c156 100644 --- a/test/jdk/java/lang/StableValue/StableListTest.java +++ b/test/jdk/java/lang/StableValue/StableListTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * 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 diff --git a/test/jdk/java/lang/StableValue/StableMapTest.java b/test/jdk/java/lang/StableValue/StableMapTest.java index 2b00d725a25b9..3133acc2f2993 100644 --- a/test/jdk/java/lang/StableValue/StableMapTest.java +++ b/test/jdk/java/lang/StableValue/StableMapTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * 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 diff --git a/test/jdk/java/lang/StableValue/StableSupplierTest.java b/test/jdk/java/lang/StableValue/StableSupplierTest.java index 06038335c3358..2d542fbf6cacf 100644 --- a/test/jdk/java/lang/StableValue/StableSupplierTest.java +++ b/test/jdk/java/lang/StableValue/StableSupplierTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * 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 diff --git a/test/jdk/java/lang/StableValue/StableTestUtil.java b/test/jdk/java/lang/StableValue/StableTestUtil.java index 4a5457ccc1eed..f71915c28ee7e 100644 --- a/test/jdk/java/lang/StableValue/StableTestUtil.java +++ b/test/jdk/java/lang/StableValue/StableTestUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * 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 diff --git a/test/jdk/java/lang/StableValue/StableValueTest.java b/test/jdk/java/lang/StableValue/StableValueTest.java index 9dc80f0b392af..751334b0ab383 100644 --- a/test/jdk/java/lang/StableValue/StableValueTest.java +++ b/test/jdk/java/lang/StableValue/StableValueTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * 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 diff --git a/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java b/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java index 31585f5147589..151d6f9c805f8 100644 --- a/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java +++ b/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * 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 diff --git a/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java b/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java index f7217dc966f05..08d876fd5369e 100644 --- a/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java +++ b/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * 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 From 82f39246bb9c433bc5957e800d0ed22392b014d1 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 22 Apr 2025 14:38:33 +0200 Subject: [PATCH 304/327] Resolve merge problem --- .../share/classes/java/lang/StableValue.java | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 8ca5b963089c7..9643718ad52ea 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -391,17 +391,6 @@ * too are thread safe and guarantee at-most-once-per-input invocation. * *

    Performance

    -<<<<<<< Updated upstream - * The _content_ of a set stable value is treated as a constant by the JVM, provided that - * the reference to the stable value is also constant (e.g. in cases where the - * stable value itself is stored in a {@code static final} field). Stable functions and - * collections are built on top of StableValue. As such, their contents is also treated as - * constant by the JVM. - *

    - * This means that, at least in some cases, access to the content of a stable value - * enjoys the same constant-folding optimizations that are available when accessing - * {@code static final} fields. -======= * As the contents of a stable value can never change after it has been set, a JVM * implementation may, for a set stable value, elide all future reads of that * stable value, and instead directly use any content that it has previously observed. @@ -409,7 +398,6 @@ * the stable value itself is stored in a {@code static final} field). Stable functions * and collections are built on top of StableValue. As such, they might also be eligible * for the same JVM optimizations as for StableValue. ->>>>>>> Stashed changes * * @implSpec Implementing classes of {@code StableValue} are free to synchronize on * {@code this} and consequently, it should be avoided to From 06585e62502f15e7fd7bc5b4ab7cbe1226c3891d Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 22 Apr 2025 15:12:18 +0200 Subject: [PATCH 305/327] Fix build problem --- .../share/classes/java/util/ImmutableCollections.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/java.base/share/classes/java/util/ImmutableCollections.java b/src/java.base/share/classes/java/util/ImmutableCollections.java index 86ba800cf895d..0d312482795bd 100644 --- a/src/java.base/share/classes/java/util/ImmutableCollections.java +++ b/src/java.base/share/classes/java/util/ImmutableCollections.java @@ -1623,9 +1623,10 @@ final class StableMapValues extends AbstractCollection { @Override public String toString() { final StableValueImpl[] values = delegate.values().toArray(new IntFunction[]>() { + @SuppressWarnings({"unchecked"}) @Override public StableValueImpl[] apply(int len) { - return new StableValueImpl[len]; + return (StableValueImpl[]) new StableValueImpl[len]; } }); return StableUtil.renderElements(StableMap.this, "StableMap", values); From 3f71da0c0a5dae59552fc969f4ff5e7dd1c6826b Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 22 Apr 2025 15:27:25 +0200 Subject: [PATCH 306/327] Fix rawtype problem --- src/java.base/share/classes/java/util/ImmutableCollections.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/java.base/share/classes/java/util/ImmutableCollections.java b/src/java.base/share/classes/java/util/ImmutableCollections.java index 0d312482795bd..2fb4f8577997f 100644 --- a/src/java.base/share/classes/java/util/ImmutableCollections.java +++ b/src/java.base/share/classes/java/util/ImmutableCollections.java @@ -1626,7 +1626,7 @@ public String toString() { @SuppressWarnings({"unchecked"}) @Override public StableValueImpl[] apply(int len) { - return (StableValueImpl[]) new StableValueImpl[len]; + return (StableValueImpl[]) new StableValueImpl[len]; } }); return StableUtil.renderElements(StableMap.this, "StableMap", values); From 397750f970add71aebf48f8f0d643ec31dd33d10 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 22 Apr 2025 15:42:21 +0200 Subject: [PATCH 307/327] Fix copyright related issues --- .../access/JavaUtilCollectionAccess.java | 2 +- test/jdk/java/util/Collection/MOAT.java | 2 +- .../lang/stable/StableFunctionBenchmark.java | 2 +- .../stable/StableFunctionSingleBenchmark.java | 2 +- .../stable/StableIntFunctionBenchmark.java | 2 +- .../StableIntFunctionSingleBenchmark.java | 2 +- .../lang/stable/StableSupplierBenchmark.java | 2 +- .../lang/stable/StableValueBenchmark.java | 2 +- .../lang/stable/VarHandleHolderBenchmark.java | 23 +++++++++++++++++++ 9 files changed, 31 insertions(+), 8 deletions(-) diff --git a/src/java.base/share/classes/jdk/internal/access/JavaUtilCollectionAccess.java b/src/java.base/share/classes/jdk/internal/access/JavaUtilCollectionAccess.java index e2d97d067765e..cac8785b158dd 100644 --- a/src/java.base/share/classes/jdk/internal/access/JavaUtilCollectionAccess.java +++ b/src/java.base/share/classes/jdk/internal/access/JavaUtilCollectionAccess.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 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 diff --git a/test/jdk/java/util/Collection/MOAT.java b/test/jdk/java/util/Collection/MOAT.java index 2546d50d12f50..45af7913d80a9 100644 --- a/test/jdk/java/util/Collection/MOAT.java +++ b/test/jdk/java/util/Collection/MOAT.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 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 diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableFunctionBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableFunctionBenchmark.java index b59532c8cf743..44fd3f2c18e6e 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/StableFunctionBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableFunctionBenchmark.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * 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 diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableFunctionSingleBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableFunctionSingleBenchmark.java index d0a621f91fc00..1cb1a04582f8f 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/StableFunctionSingleBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableFunctionSingleBenchmark.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * 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 diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableIntFunctionBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableIntFunctionBenchmark.java index 7eee8f903112b..0b8e5d97cac71 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/StableIntFunctionBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableIntFunctionBenchmark.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * 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 diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableIntFunctionSingleBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableIntFunctionSingleBenchmark.java index ca29805468155..1e8e250ba8a52 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/StableIntFunctionSingleBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableIntFunctionSingleBenchmark.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * 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 diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableSupplierBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableSupplierBenchmark.java index 3777a6bb491b7..883f13da05aff 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/StableSupplierBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableSupplierBenchmark.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * 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 diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java index d58d2727e2424..505cfffdc2ca4 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * 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 diff --git a/test/micro/org/openjdk/bench/java/lang/stable/VarHandleHolderBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/VarHandleHolderBenchmark.java index 6b1f8cfab8b5c..65d53a42e88f8 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/VarHandleHolderBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/VarHandleHolderBenchmark.java @@ -1,3 +1,26 @@ +/* + * 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 org.openjdk.jmh.annotations.Benchmark; From b7ba735c11aef8f109ce8735eeee87c241775489 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 22 Apr 2025 16:07:44 +0200 Subject: [PATCH 308/327] Rework relayed to rethrown --- src/java.base/share/classes/java/lang/StableValue.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 9643718ad52ea..7d7d5ef01329c 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -571,7 +571,7 @@ static StableValue of(T content) { * thrown by the computing thread. The computing threads will then observe the newly * computed value (if any) and will then never execute. *

    - * If the provided {@code underlying} supplier throws an exception, it is relayed + * If the provided {@code underlying} supplier throws an exception, it is rethrown * to the initial caller and no content is recorded. *

    * If the provided {@code underlying} supplier recursively calls the returned @@ -603,7 +603,7 @@ static Supplier supplier(Supplier underlying) { * the computing thread. *

    * If invoking the provided {@code underlying} function throws an exception, it is - * relayed to the initial caller and no content is recorded. + * rethrown to the initial caller and no content is recorded. *

    * If the provided {@code underlying} function recursively calls the returned * function for the same input, an {@linkplain IllegalStateException} will @@ -639,7 +639,7 @@ static IntFunction intFunction(int size, * computed or an exception is thrown by the computing thread. *

    * If invoking the provided {@code underlying} function throws an exception, it is - * relayed to the initial caller and no content is recorded. + * rethrown to the initial caller and no content is recorded. *

    * If the provided {@code underlying} function recursively calls the returned * function for the same input, an {@linkplain IllegalStateException} will From 1670cfdce3ab266d69dab89ca1dcce886583cf64 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 22 Apr 2025 17:00:31 +0200 Subject: [PATCH 309/327] Fix failing test (exception message) --- test/jdk/java/lang/StableValue/StableListTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/jdk/java/lang/StableValue/StableListTest.java b/test/jdk/java/lang/StableValue/StableListTest.java index 4d4970ad0c156..05a06ad1b335d 100644 --- a/test/jdk/java/lang/StableValue/StableListTest.java +++ b/test/jdk/java/lang/StableValue/StableListTest.java @@ -288,7 +288,7 @@ void recursiveCall() { var lazy = StableValue.list(SIZE, i -> ref.get().apply(i)); ref.set(lazy::get); var x = assertThrows(IllegalStateException.class, () -> lazy.get(INDEX)); - assertEquals("Recursive initialization is not supported", x.getMessage()); + assertEquals("Recursive initialization of a stable value is illegal", x.getMessage()); } // Immutability From f9d423de3344050c4bdeac1c498fc8afa3c8edaf Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 22 Apr 2025 17:06:01 +0200 Subject: [PATCH 310/327] Reformat docs --- .../share/classes/java/lang/StableValue.java | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 7d7d5ef01329c..8f712af72ab29 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -116,9 +116,9 @@ *

    * The {@code getLogger()} method calls {@code logger.orElseSet()} on the stable value to * retrieve its content. If the stable value is unset, then {@code orElseSet()} - * evaluates the given supplier, and sets the content to the result; the content is then returned to the client. In other - * words, {@code orElseSet()} guarantees that a stable value's content is set - * before it returns. + * evaluates the given supplier, and sets the content to the result; the content is then + * returned to the client. In other words, {@code orElseSet()} guarantees that a + * stable value's content is set before it returns. *

    * Furthermore, {@code orElseSet()} guarantees that out of one or more suppliers provided, * only at most one is ever evaluated and that one is only ever evaluated once, @@ -424,11 +424,12 @@ * Be advised that reachable stable values will hold their set content until * the stable value itself is collected. * A {@code StableValue} that has a type parameter {@code T} that is an array - * type (of arbitrary rank) will only allow the JVM to treat the array reference - * as a stable value but not its components. Clients can instead use - * {@linkplain #list(int, IntFunction) a stable list} of arbitrary depth, which - * provides stable components. More generally, a stable value can hold other - * stable values of arbitrary depth and still provide transitive constantness. + * type (of arbitrary rank) will only allow the JVM to treat the + * array reference as a stable value but not its components. + * Instead, a {@linkplain #list(int, IntFunction) a stable list} of arbitrary + * depth can be used, which provides stable components. More generally, a + * stable value can hold other stable values of arbitrary depth and still + * provide transitive constantness. * * @implNote Stable values, functions and collections are not {@link Serializable}. * @@ -452,8 +453,8 @@ public sealed interface StableValue * @return {@code true} if the content of this StableValue was set to the * provided {@code content}, {@code false} otherwise * @param content to set - * @throws IllegalStateException if a supplier invoked by {@link #orElseSet(Supplier)} recursively - * attempts to set this stable value by calling this method. + * @throws IllegalStateException if a supplier invoked by {@link #orElseSet(Supplier)} + * recursively attempts to set this stable value by calling this method. */ boolean trySet(T content); @@ -592,8 +593,8 @@ static Supplier supplier(Supplier underlying) { * input, records the values of the provided {@code underlying} * function upon being first accessed via the returned function's * {@linkplain IntFunction#apply(int) apply()} method. If the returned function is - * invoked with an input that is not in the range {@code [0, size)}, an {@link IllegalArgumentException} - * will be thrown. + * invoked with an input that is not in the range {@code [0, size)}, an + * {@link IllegalArgumentException} will be thrown. *

    * The provided {@code underlying} function is guaranteed to be successfully invoked * at most once per allowed input, even in a multi-threaded environment. Competing From 1a25b8639b15228287da59fa0856eec1141d5021 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 23 Apr 2025 09:36:22 +0200 Subject: [PATCH 311/327] Remove section on fun/coll sync --- src/java.base/share/classes/java/lang/StableValue.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 8f712af72ab29..50bc8227aa252 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -402,11 +402,10 @@ * @implSpec Implementing classes of {@code StableValue} are free to synchronize on * {@code this} and consequently, it should be avoided to * (directly or indirectly) synchronize on a {@code StableValue}. Hence, - * synchronizing on {@code this} may lead to deadlock. Stable functions - * and collections on the other hand are guaranteed not to synchronize - * on {@code this}. - * Except for a {@code StableValue}'s content itself, an {@linkplain #orElse(Object) orElse(other)} - * parameter, and an {@linkplain #equals(Object) equals(obj)} parameter; all + * synchronizing on {@code this} may lead to deadlock. + * Except for a {@code StableValue}'s content itself, + * an {@linkplain #orElse(Object) orElse(other)} parameter, and + * an {@linkplain #equals(Object) equals(obj)} parameter; all * method parameters must be non-null or a {@link NullPointerException} * will be thrown. * From ac6accd88d52bfa0fbcc18d6cfce815b1df14655 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 23 Apr 2025 12:35:02 +0200 Subject: [PATCH 312/327] Replace 'content' with 'contents' and doc updates --- .../share/classes/java/lang/StableValue.java | 123 +++++++++--------- .../internal/lang/stable/StableValueImpl.java | 32 ++--- .../lang/StableValue/StableValueTest.java | 8 +- .../StableValue/TrustedFieldTypeTest.java | 6 +- 4 files changed, 86 insertions(+), 83 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 50bc8227aa252..e25f181dd217f 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -48,27 +48,27 @@ import java.util.function.Supplier; /** - * A stable value is a holder of content that can be set at most once. + * A stable value is a holder of contents that can be set at most once. *

    * A {@code StableValue} is typically created using the factory method * {@linkplain StableValue#of() {@code StableValue.of()}}. When created this way, - * the stable value is unset, which means it holds no content. - * Its content, of type {@code T}, can be set by calling + * the stable value is unset, which means it holds no contents. + * Its contents, of type {@code T}, can be set by calling * {@linkplain #trySet(Object) trySet()}, {@linkplain #setOrThrow(Object) setOrThrow()}, - * or {@linkplain #orElseSet(Supplier) orElseSet()}. Once set, the content + * or {@linkplain #orElseSet(Supplier) orElseSet()}. Once set, the contents * can never change and can be retrieved by calling {@linkplain #orElseThrow() orElseThrow()} * , {@linkplain #orElse(Object) orElse()}, or {@linkplain #orElseSet(Supplier) orElseSet()}. *

    * Consider the following example where a stable value field "{@code logger}" is a - * shallowly immutable holder of content of type {@code Logger} and that is initially - * created as unset, which means it holds no content. Later in the example, the + * shallowly immutable holder of contents of type {@code Logger} and that is initially + * created as unset, which means it holds no contents. Later in the example, the * state of the "{@code logger}" field is checked and if it is still unset, - * the content is set: + * the contents is set: * * {@snippet lang = java: * public class Component { * - * // Creates a new unset stable value with no content + * // Creates a new unset stable value with no contents * // @link substring="of" target="#of" : * private final StableValue logger = StableValue.of(); * @@ -87,19 +87,19 @@ *} *

    * If {@code getLogger()} is called from several threads, several instances of - * {@code Logger} might be created. However, the content can only be set at most once + * {@code Logger} might be created. However, the contents can only be set at most once * meaning the first writer wins. *

    * In order to guarantee that, even under races, only one instance of {@code Logger} is * ever created, the {@linkplain #orElseSet(Supplier) orElseSet()} method can be used - * instead, where the content is lazily computed, and atomically set, via a + * instead, where the contents are lazily computed, and atomically set, via a * {@linkplain Supplier supplier}. In the example below, the supplier is provided in the * form of a lambda expression: * * {@snippet lang = java: * public class Component { * - * // Creates a new unset stable value with no content + * // Creates a new unset stable value with no contents * // @link substring="of" target="#of" : * private final StableValue logger = StableValue.of(); * @@ -115,13 +115,13 @@ *} *

    * The {@code getLogger()} method calls {@code logger.orElseSet()} on the stable value to - * retrieve its content. If the stable value is unset, then {@code orElseSet()} - * evaluates the given supplier, and sets the content to the result; the content is then + * retrieve its contents. If the stable value is unset, then {@code orElseSet()} + * evaluates the given supplier, and sets the contents to the result; the contents is then * returned to the client. In other words, {@code orElseSet()} guarantees that a - * stable value's content is set before it returns. + * stable value's contents is set before it returns. *

    * Furthermore, {@code orElseSet()} guarantees that out of one or more suppliers provided, - * only at most one is ever evaluated and that one is only ever evaluated once, + * only at most one is ever evaluated, and that one is only ever evaluated once, * even when {@code logger.orElseSet()} is invoked concurrently. This property is crucial * as evaluation of the supplier may have side effects, for example, the call above to * {@code Logger.create()} may result in storage resources being prepared. @@ -359,9 +359,9 @@ * a circularity. * *

    Thread Safety

    - * The content of a stable value is guaranteed to be set at most once. If competing - * threads are racing to set a stable value, only one update succeeds, while other updates - * are blocked until the stable value becomes set whereafter the other updates + * The contents of a stable value is guaranteed to be set at most once. If competing + * threads are racing to set a stable value, only one update succeeds, while the other + * updates are blocked until the stable value is set, whereafter the other updates * observes the stable value is set and leave the stable value unchanged. *

    * The at-most-once write operation on a stable value that succeeds @@ -383,9 +383,9 @@ * *

    * The method {@link #orElseSet(Supplier)} guarantees that the provided - * {@linkplain Supplier} is invoked successfully at most once even under race. + * {@linkplain Supplier} is invoked successfully at most once, even under race. * Invocations of {@link #setOrThrow(Object)} form a total order of zero or more - * exceptional invocations followed by zero (if the content was already set) or one + * exceptional invocations followed by zero (if the contents were already set) or one * successful invocation. Since stable functions and stable collections are built on top * of the same principles as {@linkplain StableValue#orElseSet(Supplier) orElseSet()} they * too are thread safe and guarantee at-most-once-per-input invocation. @@ -393,7 +393,7 @@ *

    Performance

    * As the contents of a stable value can never change after it has been set, a JVM * implementation may, for a set stable value, elide all future reads of that - * stable value, and instead directly use any content that it has previously observed. + * stable value, and instead directly use any contents that it has previously observed. * This is true if the reference to the stable value is a constant (e.g. in cases where * the stable value itself is stored in a {@code static final} field). Stable functions * and collections are built on top of StableValue. As such, they might also be eligible @@ -403,7 +403,7 @@ * {@code this} and consequently, it should be avoided to * (directly or indirectly) synchronize on a {@code StableValue}. Hence, * synchronizing on {@code this} may lead to deadlock. - * Except for a {@code StableValue}'s content itself, + * Except for a {@code StableValue}'s contents itself, * an {@linkplain #orElse(Object) orElse(other)} parameter, and * an {@linkplain #equals(Object) equals(obj)} parameter; all * method parameters must be non-null or a {@link NullPointerException} @@ -417,10 +417,10 @@ * of the internal stable values when called. * Stable collections have {@link Object#equals(Object)} operations that try * to minimize evaluation of the internal stable values when called. - * As objects can be set via stable values but never removed, this can be a source - * of unintended memory leaks. A stable value's content is + * As objects can be set via stable values but never removed, this can be a + * source of unintended memory leaks. A stable value's contents are * {@linkplain java.lang.ref##reachability strongly reachable}. - * Be advised that reachable stable values will hold their set content until + * Be advised that reachable stable values will hold their set contents until * the stable value itself is collected. * A {@code StableValue} that has a type parameter {@code T} that is an array * type (of arbitrary rank) will only allow the JVM to treat the @@ -430,9 +430,9 @@ * stable value can hold other stable values of arbitrary depth and still * provide transitive constantness. * - * @implNote Stable values, functions and collections are not {@link Serializable}. + * @implNote Stable values, functions, and collections are not {@link Serializable}. * - * @param type of the content + * @param type of the contents * * @since 25 */ @@ -443,42 +443,43 @@ public sealed interface StableValue // Principal methods /** - * Tries to set the content of this StableValue to the provided {@code content}. The - * content of this StableValue can only be set once, implying this method only returns - * {@code true} once. + * Tries to set the contents of this StableValue to the provided {@code contents}. + * The contents of this StableValue can only be set once, implying this method only + * returns {@code true} once. *

    - * When this method returns, the content of this StableValue is always set. + * When this method returns, the contents of this StableValue is always set. * - * @return {@code true} if the content of this StableValue was set to the - * provided {@code content}, {@code false} otherwise - * @param content to set + * @return {@code true} if the contents of this StableValue was set to the + * provided {@code contents}, {@code false} otherwise + * @param contents to set * @throws IllegalStateException if a supplier invoked by {@link #orElseSet(Supplier)} - * recursively attempts to set this stable value by calling this method. + * recursively attempts to set this stable value by calling this method + * directly or indirectly. */ - boolean trySet(T content); + boolean trySet(T contents); /** - * {@return the content if set, otherwise, returns the provided {@code other} value} + * {@return the contents if set, otherwise, returns the provided {@code other} value} * - * @param other to return if the content is not set + * @param other to return if the contents is not set */ T orElse(T other); /** - * {@return the content if set, otherwise, throws {@code NoSuchElementException}} + * {@return the contents if set, otherwise, throws {@code NoSuchElementException}} * - * @throws NoSuchElementException if no content is set + * @throws NoSuchElementException if no contents is set */ T orElseThrow(); /** - * {@return {@code true} if the content is set, {@code false} otherwise} + * {@return {@code true} if the contents is set, {@code false} otherwise} */ boolean isSet(); /** - * {@return the content; if unset, first attempts to compute and set the - * content using the provided {@code supplier}} + * {@return the contents; if unset, first attempts to compute and set the + * contents using the provided {@code supplier}} *

    * The provided {@code supplier} is guaranteed to be invoked at most once if it * completes without throwing an exception. If this method is invoked several times @@ -486,19 +487,19 @@ public sealed interface StableValue * without throwing an exception. *

    * If the supplier throws an (unchecked) exception, the exception is rethrown and no - * content is set. The most common usage is to construct a new object serving + * contents is set. The most common usage is to construct a new object serving * as a lazily computed value or memoized result, as in: * * {@snippet lang=java: * Value v = stable.orElseSet(Value::new); * } *

    - * When this method returns successfully, the content is always set. + * When this method returns successfully, the contents is always set. *

    * The provided {@code supplier} will only be invoked once even if invoked from * several threads unless the {@code supplier} throws an exception. * - * @param supplier to be used for computing the content, if not previously set + * @param supplier to be used for computing the contents, if not previously set * @throws IllegalStateException if the provided {@code supplier} recursively * attempts to set this stable value. */ @@ -507,15 +508,15 @@ public sealed interface StableValue // Convenience methods /** - * Sets the content of this StableValue to the provided {@code content}, or, if + * Sets the contents of this StableValue to the provided {@code contents}, or, if * already set, throws {@code IllegalStateException}. *

    - * When this method returns (or throws an exception), the content is always set. + * When this method returns (or throws an exception), the contents is always set. * - * @param content to set - * @throws IllegalStateException if the content was already set + * @param contents to set + * @throws IllegalStateException if the contents was already set */ - void setOrThrow(T content); + void setOrThrow(T contents); // Object methods @@ -537,23 +538,23 @@ public sealed interface StableValue /** * {@return a new unset stable value} *

    - * An unset stable value has no content. + * An unset stable value has no contents. * - * @param type of the content + * @param type of the contents */ static StableValue of() { return StableValueImpl.of(); } /** - * {@return a new pre-set stable value with the provided {@code content}} + * {@return a new pre-set stable value with the provided {@code contents}} * - * @param content to set - * @param type of the content + * @param contents to set + * @param type of the contents */ - static StableValue of(T content) { + static StableValue of(T contents) { final StableValue stableValue = StableValue.of(); - stableValue.trySet(content); + stableValue.trySet(contents); return stableValue; } @@ -572,7 +573,7 @@ static StableValue of(T content) { * computed value (if any) and will then never execute. *

    * If the provided {@code underlying} supplier throws an exception, it is rethrown - * to the initial caller and no content is recorded. + * to the initial caller and no contents is recorded. *

    * If the provided {@code underlying} supplier recursively calls the returned * supplier, an {@linkplain IllegalStateException} will be thrown. @@ -603,7 +604,7 @@ static Supplier supplier(Supplier underlying) { * the computing thread. *

    * If invoking the provided {@code underlying} function throws an exception, it is - * rethrown to the initial caller and no content is recorded. + * rethrown to the initial caller and no contents is recorded. *

    * If the provided {@code underlying} function recursively calls the returned * function for the same input, an {@linkplain IllegalStateException} will @@ -639,7 +640,7 @@ static IntFunction intFunction(int size, * computed or an exception is thrown by the computing thread. *

    * If invoking the provided {@code underlying} function throws an exception, it is - * rethrown to the initial caller and no content is recorded. + * rethrown to the initial caller and no contents is recorded. *

    * If the provided {@code underlying} function recursively calls the returned * function for the same input, an {@linkplain IllegalStateException} will 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 38ff7ada62df5..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 @@ -40,7 +40,7 @@ * @implNote This implementation can be used early in the boot sequence as it does not * rely on reflection, MethodHandles, Streams etc. * - * @param type of the content + * @param type of the contents */ public final class StableValueImpl implements StableValue { @@ -50,9 +50,9 @@ public final class StableValueImpl implements StableValue { static final Unsafe UNSAFE = Unsafe.getUnsafe(); // Unsafe offsets for direct field access - private static final long CONTENT_OFFSET = - UNSAFE.objectFieldOffset(StableValueImpl.class, "content"); + private static final long CONTENT_OFFSET = + UNSAFE.objectFieldOffset(StableValueImpl.class, "contents"); // Used to indicate a holder value is `null` (see field `value` below) // A wrapper method `nullSentinel()` is used for generic type conversion. private static final Object NULL_SENTINEL = new Object(); @@ -69,14 +69,14 @@ public final class StableValueImpl implements StableValue { // | other | Set(other) | // @Stable - private Object content; + private Object contents; // Only allow creation via the factory `StableValueImpl::newInstance` private StableValueImpl() {} @ForceInline @Override - public boolean trySet(T content) { + public boolean trySet(T contents) { if (wrappedContentAcquire() != null) { return false; } @@ -85,17 +85,17 @@ public boolean trySet(T content) { // Mutual exclusion is required here as `orElseSet` might also // attempt to modify the `wrappedValue` synchronized (this) { - return wrapAndSet(content); + return wrapAndSet(contents); } } @ForceInline @Override - public void setOrThrow(T content) { - if (!trySet(content)) { - // Neither the set content nor the provided content is revealed in the + public void setOrThrow(T contents) { + if (!trySet(contents)) { + // Neither the set contents nor the provided contents is revealed in the // exception message as it might be sensitive. - throw new IllegalStateException("The content is already set"); + throw new IllegalStateException("The contents is already set"); } } @@ -104,7 +104,7 @@ public void setOrThrow(T content) { public T orElseThrow() { final Object t = wrappedContentAcquire(); if (t == null) { - throw new NoSuchElementException("No content set"); + throw new NoSuchElementException("No contents set"); } return unwrap(t); } @@ -134,7 +134,7 @@ public T orElseSet(Supplier supplier) { private T orElseSetSlowPath(Supplier supplier) { preventReentry(); synchronized (this) { - final Object t = content; // Plain semantics suffice here + final Object t = contents; // Plain semantics suffice here if (t == null) { final T newValue = supplier.get(); // The mutex is not reentrant so we know newValue should be returned @@ -168,6 +168,8 @@ 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"); @@ -175,18 +177,18 @@ private void preventReentry() { } /** - * Wraps the provided {@code newValue} and tries to set the content. + * Wraps the provided {@code newValue} and tries to set the contents. *

    * This method ensures the {@link Stable} field is written to at most once. * * @param newValue to wrap and set - * @return if the content was set + * @return if the contents was set */ @ForceInline private boolean wrapAndSet(Object newValue) { assert Thread.holdsLock(this); // We know we hold the monitor here so plain semantic is enough - if (content == null) { + if (contents == null) { UNSAFE.putReferenceRelease(this, CONTENT_OFFSET, wrap(newValue)); return true; } diff --git a/test/jdk/java/lang/StableValue/StableValueTest.java b/test/jdk/java/lang/StableValue/StableValueTest.java index 751334b0ab383..0ed683525097f 100644 --- a/test/jdk/java/lang/StableValue/StableValueTest.java +++ b/test/jdk/java/lang/StableValue/StableValueTest.java @@ -69,7 +69,7 @@ void preSet() { assertFalse(stable.trySet(VALUE2)); var e = assertThrows(IllegalStateException.class, () -> stable.setOrThrow(VALUE2)); assertEquals( - "The content is already set", + "The contents is already set", e.getMessage()); } @@ -87,7 +87,7 @@ void setOrThrowValue() { StableValue stable = StableValue.of(); stable.setOrThrow(VALUE); var e = assertThrows(IllegalStateException.class, () -> stable.setOrThrow(VALUE2)); - assertEquals("The content is already set", e.getMessage()); + assertEquals("The contents is already set", e.getMessage()); } @Test @@ -95,7 +95,7 @@ void setOrThrowNull() { StableValue stable = StableValue.of(); stable.setOrThrow(null); var e = assertThrows(IllegalStateException.class, () -> stable.setOrThrow(null)); - assertEquals("The content is already set", e.getMessage()); + assertEquals("The contents is already set", e.getMessage()); } @Test @@ -111,7 +111,7 @@ void orElse() { void orElseThrow() { StableValue stable = StableValue.of(); var e = assertThrows(NoSuchElementException.class, stable::orElseThrow); - assertEquals("No content set", e.getMessage()); + assertEquals("No contents set", e.getMessage()); stable.trySet(VALUE); assertEquals(VALUE, stable.orElseThrow()); } diff --git a/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java b/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java index 08d876fd5369e..205e5ed3a773a 100644 --- a/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java +++ b/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java @@ -90,7 +90,7 @@ void updateStableValueContentVia_j_i_m_Unsafe() { stableValue.trySet(42); jdk.internal.misc.Unsafe unsafe = Unsafe.getUnsafe(); - long offset = unsafe.objectFieldOffset(stableValue.getClass(), "content"); + long offset = unsafe.objectFieldOffset(stableValue.getClass(), "contents"); assertTrue(offset > 0); // Unfortunately, it is possible to update the underlying data via jdk.internal.misc.Unsafe @@ -104,7 +104,7 @@ void updateStableValueContentViaSetAccessible() throws NoSuchFieldException, Ill if (Boolean.getBoolean("opens")) { // Unfortunately, add-opens allows direct access to the `value` field - Field field = StableValueImpl.class.getDeclaredField("content"); + Field field = StableValueImpl.class.getDeclaredField("contents"); field.setAccessible(true); StableValue stableValue = StableValue.of(); @@ -116,7 +116,7 @@ void updateStableValueContentViaSetAccessible() throws NoSuchFieldException, Ill field.set(stableValue, 13); assertEquals(13, stableValue.orElseThrow()); } else { - Field field = StableValueImpl.class.getDeclaredField("content"); + Field field = StableValueImpl.class.getDeclaredField("contents"); assertThrows(InaccessibleObjectException.class, ()-> field.setAccessible(true)); } } From 596b5d005e568431cce81ec51a9cde4dfe0a2828 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 23 Apr 2025 13:54:48 +0200 Subject: [PATCH 313/327] Rephrase happens-before clause --- src/java.base/share/classes/java/lang/StableValue.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index e25f181dd217f..4ef4a5fb91c86 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -367,7 +367,7 @@ * The at-most-once write operation on a stable value that succeeds * (e.g. {@linkplain #trySet(Object) trySet()}) * {@linkplain java.util.concurrent##MemoryVisibility happens-before} - * any subsequent read operation (e.g. {@linkplain #orElseThrow()}) that is successful. + * any successful read operation (e.g. {@linkplain #orElseThrow()}). * A successful write operation can be either: *

      *
    • a {@link #trySet(Object)} that returns {@code true},
    • From 538daf4fddae12e8863a1b63d04faeae19791ae8 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 23 Apr 2025 14:09:01 +0200 Subject: [PATCH 314/327] Replace 'contents' with 'result' in the docs --- src/java.base/share/classes/java/lang/StableValue.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 4ef4a5fb91c86..80734eeff1750 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -116,7 +116,7 @@ *

      * The {@code getLogger()} method calls {@code logger.orElseSet()} on the stable value to * retrieve its contents. If the stable value is unset, then {@code orElseSet()} - * evaluates the given supplier, and sets the contents to the result; the contents is then + * evaluates the given supplier, and sets the contents to the result; the result is then * returned to the client. In other words, {@code orElseSet()} guarantees that a * stable value's contents is set before it returns. *

      From 8788713661e9777f0613dea5256fb453ecaa718b Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Thu, 24 Apr 2025 11:08:45 +0200 Subject: [PATCH 315/327] Address comments --- .../share/classes/java/lang/StableValue.java | 8 +++- .../java/util/ImmutableCollections.java | 46 +++++++++++++++---- .../java/util/ReverseOrderListView.java | 30 +++++------- .../jdk/internal/javac/PreviewFeature.java | 2 +- .../jdk/internal/lang/stable/StableUtil.java | 4 +- .../java/lang/StableValue/StableListTest.java | 3 ++ 6 files changed, 60 insertions(+), 33 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 80734eeff1750..b12d6f5e92128 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -403,6 +403,7 @@ * {@code this} and consequently, it should be avoided to * (directly or indirectly) synchronize on a {@code StableValue}. Hence, * synchronizing on {@code this} may lead to deadlock. + *

      * Except for a {@code StableValue}'s contents itself, * an {@linkplain #orElse(Object) orElse(other)} parameter, and * an {@linkplain #equals(Object) equals(obj)} parameter; all @@ -412,16 +413,19 @@ * @implNote A {@code StableValue} is mainly intended to be a non-public field in * a class and is usually neither exposed directly via accessors nor passed as * a method parameter. + *

      * Stable functions and collections make reasonable efforts to provide * {@link Object#toString()} operations that do not trigger evaluation * of the internal stable values when called. * Stable collections have {@link Object#equals(Object)} operations that try * to minimize evaluation of the internal stable values when called. + *

      * As objects can be set via stable values but never removed, this can be a * source of unintended memory leaks. A stable value's contents are * {@linkplain java.lang.ref##reachability strongly reachable}. * Be advised that reachable stable values will hold their set contents until * the stable value itself is collected. + *

      * A {@code StableValue} that has a type parameter {@code T} that is an array * type (of arbitrary rank) will only allow the JVM to treat the * array reference as a stable value but not its components. @@ -429,8 +433,8 @@ * depth can be used, which provides stable components. More generally, a * stable value can hold other stable values of arbitrary depth and still * provide transitive constantness. - * - * @implNote Stable values, functions, and collections are not {@link Serializable}. + *

      + * Stable values, functions, and collections are not {@link Serializable}. * * @param type of the contents * diff --git a/src/java.base/share/classes/java/util/ImmutableCollections.java b/src/java.base/share/classes/java/util/ImmutableCollections.java index 2fb4f8577997f..162f03ad9fb40 100644 --- a/src/java.base/share/classes/java/util/ImmutableCollections.java +++ b/src/java.base/share/classes/java/util/ImmutableCollections.java @@ -46,6 +46,7 @@ import jdk.internal.lang.stable.StableUtil; import jdk.internal.lang.stable.StableValueImpl; import jdk.internal.misc.CDS; +import jdk.internal.util.ArraysSupport; import jdk.internal.util.NullableKeyValueHolder; import jdk.internal.vm.annotation.ForceInline; import jdk.internal.vm.annotation.Stable; @@ -872,11 +873,38 @@ private T[] copyInto(Object[] a) { return (T[]) a; } + @Override + public List reversed() { + return new StableReverseOrderListView<>(this); + } + @Override public String toString() { return StableUtil.renderElements(this, "StableList", delegates); } + private static final class StableReverseOrderListView extends ReverseOrderListView.Rand { + + public StableReverseOrderListView(List base) { + super(base, false); + } + + // This method does not evaluate the elements + @Override + public String toString() { + final StableValueImpl[] delegates = ((StableList)base).delegates; + final StableValueImpl[] reversed = ArraysSupport.reverse( + Arrays.copyOf(delegates, delegates.length)); + return StableUtil.renderElements(base, "Collection", reversed); + } + + @Override + public List reversed() { + return base; + } + + } + } // ---------- Set Implementations ---------- @@ -1613,22 +1641,22 @@ public Collection values() { return new StableMapValues(); } - final class StableMapValues extends AbstractCollection { + final class StableMapValues extends AbstractImmutableCollection { @Override public Iterator iterator() { return new ValueIterator(); } @Override public int size() { return StableMap.this.size(); } @Override public boolean isEmpty() { return StableMap.this.isEmpty();} - @Override public void clear() { StableMap.this.clear(); } @Override public boolean contains(Object v) { return StableMap.this.containsValue(v); } + private static final IntFunction[]> GENERATOR = new IntFunction[]>() { + @Override + public StableValueImpl[] apply(int len) { + return new StableValueImpl[len]; + } + }; + @Override public String toString() { - final StableValueImpl[] values = delegate.values().toArray(new IntFunction[]>() { - @SuppressWarnings({"unchecked"}) - @Override - public StableValueImpl[] apply(int len) { - return (StableValueImpl[]) new StableValueImpl[len]; - } - }); + final StableValueImpl[] values = delegate.values().toArray(GENERATOR); return StableUtil.renderElements(StableMap.this, "StableMap", values); } } diff --git a/src/java.base/share/classes/java/util/ReverseOrderListView.java b/src/java.base/share/classes/java/util/ReverseOrderListView.java index 9f4195d606314..7bf0f9f34a03f 100644 --- a/src/java.base/share/classes/java/util/ReverseOrderListView.java +++ b/src/java.base/share/classes/java/util/ReverseOrderListView.java @@ -33,8 +33,6 @@ import java.util.stream.Stream; import java.util.stream.StreamSupport; -import jdk.internal.lang.stable.StableUtil; -import jdk.internal.lang.stable.StableValueImpl; import jdk.internal.util.ArraysSupport; /** @@ -299,24 +297,18 @@ public T[] toArray(IntFunction generator) { // copied from AbstractCollection public String toString() { - if (base instanceof ImmutableCollections.StableList stableList) { - final StableValueImpl[] reversed = ArraysSupport.reverse( - Arrays.copyOf(stableList.delegates, stableList.delegates.length)); - return StableUtil.renderElements(base, "Collection", reversed); - } else { - Iterator it = iterator(); + Iterator it = iterator(); + if (!it.hasNext()) + return "[]"; + + StringBuilder sb = new StringBuilder(); + sb.append('['); + for (; ; ) { + E e = it.next(); + sb.append(e == this ? "(this Collection)" : e); if (!it.hasNext()) - return "[]"; - - StringBuilder sb = new StringBuilder(); - sb.append('['); - for (; ; ) { - E e = it.next(); - sb.append(e == this ? "(this Collection)" : e); - if (!it.hasNext()) - return sb.append(']').toString(); - sb.append(',').append(' '); - } + return sb.append(']').toString(); + sb.append(',').append(' '); } } diff --git a/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java b/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java index e5e8a6a65efcd..2ed6dde794a4c 100644 --- a/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java +++ b/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java @@ -80,9 +80,9 @@ public enum Feature { MODULE_IMPORTS, @JEP(number=478, title="Key Derivation Function API", status="Preview") KEY_DERIVATION, - LANGUAGE_MODEL, @JEP(number = 502, title = "Stable Values", status = "Preview") STABLE_VALUES, + LANGUAGE_MODEL, /** * A key for testing. */ 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 b2040c5f95217..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 @@ -36,13 +36,13 @@ private StableUtil() {} public static String renderElements(Object self, String selfName, - StableValueImpl[] delegates) { + StableValueImpl[] delegates) { return renderElements(self, selfName, delegates, 0, delegates.length); } public static String renderElements(Object self, String selfName, - StableValueImpl[] delegates, + StableValueImpl[] delegates, int offset, int length) { final StringJoiner sj = new StringJoiner(", ", "[", "]"); diff --git a/test/jdk/java/lang/StableValue/StableListTest.java b/test/jdk/java/lang/StableValue/StableListTest.java index 05a06ad1b335d..f0ae081c76cb3 100644 --- a/test/jdk/java/lang/StableValue/StableListTest.java +++ b/test/jdk/java/lang/StableValue/StableListTest.java @@ -264,8 +264,11 @@ void reversed() { assertEquals(0, reversed.getLast()); var reversed2 = reversed.reversed(); + assertInstanceOf(RandomAccess.class, reversed2); assertEquals(0, reversed2.getFirst()); assertEquals(SIZE - 1, reversed2.getLast()); + // Make sure we get back a non-reversed implementation + assertEquals("java.util.ImmutableCollections$StableList", reversed2.getClass().getName()); } @Test From bcc022fe85fb38602ad321ab167345800b1fc678 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Thu, 24 Apr 2025 11:16:51 +0200 Subject: [PATCH 316/327] Revert unwanted changes --- .../share/classes/java/util/ReverseOrderListView.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/java.base/share/classes/java/util/ReverseOrderListView.java b/src/java.base/share/classes/java/util/ReverseOrderListView.java index 7bf0f9f34a03f..f48b41920c930 100644 --- a/src/java.base/share/classes/java/util/ReverseOrderListView.java +++ b/src/java.base/share/classes/java/util/ReverseOrderListView.java @@ -32,7 +32,6 @@ import java.util.function.UnaryOperator; import java.util.stream.Stream; import java.util.stream.StreamSupport; - import jdk.internal.util.ArraysSupport; /** @@ -298,15 +297,15 @@ public T[] toArray(IntFunction generator) { // copied from AbstractCollection public String toString() { Iterator it = iterator(); - if (!it.hasNext()) + if (! it.hasNext()) return "[]"; StringBuilder sb = new StringBuilder(); sb.append('['); - for (; ; ) { + for (;;) { E e = it.next(); sb.append(e == this ? "(this Collection)" : e); - if (!it.hasNext()) + if (! it.hasNext()) return sb.append(']').toString(); sb.append(',').append(' '); } From 4839d1867e6cbb977cdef584d753d2d6141a5bad Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Thu, 24 Apr 2025 12:33:49 +0200 Subject: [PATCH 317/327] Make public constuctor private --- src/java.base/share/classes/java/util/ImmutableCollections.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/java.base/share/classes/java/util/ImmutableCollections.java b/src/java.base/share/classes/java/util/ImmutableCollections.java index 162f03ad9fb40..9becf1671761e 100644 --- a/src/java.base/share/classes/java/util/ImmutableCollections.java +++ b/src/java.base/share/classes/java/util/ImmutableCollections.java @@ -885,7 +885,7 @@ public String toString() { private static final class StableReverseOrderListView extends ReverseOrderListView.Rand { - public StableReverseOrderListView(List base) { + private StableReverseOrderListView(List base) { super(base, false); } From b712a6e83832d675d35b15762687b7012bee4c17 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 28 Apr 2025 16:04:21 +0200 Subject: [PATCH 318/327] Address comment from the first PR --- .../share/classes/java/lang/StableValue.java | 15 +- .../java/util/ImmutableCollections.java | 175 ++++++++++++++---- .../java/util/ReverseOrderListView.java | 3 + .../lang/stable/StableEnumFunction.java | 10 +- .../internal/lang/stable/StableFunction.java | 2 +- .../lang/stable/StableIntFunction.java | 3 - .../internal/lang/stable/StableSupplier.java | 2 +- .../jdk/internal/lang/stable/StableUtil.java | 6 +- .../internal/lang/stable/StableValueImpl.java | 20 +- .../lang/StableValue/StableFunctionTest.java | 15 +- .../java/lang/StableValue/StableListTest.java | 116 +++++++++--- .../lang/StableValue/StableValueTest.java | 1 + 12 files changed, 273 insertions(+), 95 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index b12d6f5e92128..8514e5c11ec0f 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -444,8 +444,6 @@ public sealed interface StableValue permits StableValueImpl { - // Principal methods - /** * Tries to set the contents of this StableValue to the provided {@code contents}. * The contents of this StableValue can only be set once, implying this method only @@ -509,8 +507,6 @@ public sealed interface StableValue */ T orElseSet(Supplier supplier); - // Convenience methods - /** * Sets the contents of this StableValue to the provided {@code contents}, or, if * already set, throws {@code IllegalStateException}. @@ -519,6 +515,9 @@ public sealed interface StableValue * * @param contents to set * @throws IllegalStateException if the contents was already set + * @throws IllegalStateException if a supplier invoked by {@link #orElseSet(Supplier)} + * recursively attempts to set this stable value by calling this method + * directly or indirectly. */ void setOrThrow(T contents); @@ -573,8 +572,8 @@ static StableValue of(T contents) { * at most once even in a multi-threaded environment. Competing threads invoking the * returned supplier's {@linkplain Supplier#get() get()} method when a value is * already under computation will block until a value is computed or an exception is - * thrown by the computing thread. The computing threads will then observe the newly - * computed value (if any) and will then never execute. + * thrown by the computing thread. The competing threads will then observe the newly + * computed value (if any) and will then never execute the {@code underlying} supplier. *

      * If the provided {@code underlying} supplier throws an exception, it is rethrown * to the initial caller and no contents is recorded. @@ -727,8 +726,8 @@ static List list(int size, * is rethrown to the initial caller and no value associated with the provided key * is recorded. *

      - * Any direct {@link Map#values()} or {@link Map#entrySet()} views - * of the returned map are also stable. + * Any {@link Map#values()} or {@link Map#entrySet()} views of the returned map are + * also stable. *

      * The returned map is unmodifiable and does not implement the * {@linkplain Collection##optional-operations optional operations} in the diff --git a/src/java.base/share/classes/java/util/ImmutableCollections.java b/src/java.base/share/classes/java/util/ImmutableCollections.java index 9becf1671761e..9e21cd40e5e8b 100644 --- a/src/java.base/share/classes/java/util/ImmutableCollections.java +++ b/src/java.base/share/classes/java/util/ImmutableCollections.java @@ -137,9 +137,11 @@ public List listFromTrustedArrayNullsAllowed(Object[] array) { return ImmutableCollections.listFromTrustedArrayNullsAllowed(array); } public List stableList(int size, IntFunction mapper) { - return ImmutableCollections.stableList(size, mapper); + // A stable list is not Serializable so, we cannot return `List.of()` if `size == 0` + return new StableList<>(size, mapper); } public Map stableMap(Set keys, Function mapper) { + // A stable map is not Serializable so, we cannot return `Map.of()` if `keys.isEmpty()` return new StableMap<>(keys, mapper); } }); @@ -264,11 +266,6 @@ static List listFromTrustedArrayNullsAllowed(Object... input) { } } - static List stableList(int size, IntFunction mapper) { - // A lazy list is not Serializable so, we cannot return `List.of()` if size == 0 - return new StableList<>(size, mapper); - } - // ---------- List Implementations ---------- @jdk.internal.ValueBased @@ -454,17 +451,17 @@ public void add(E e) { } } - static final class SubList extends AbstractImmutableList + static sealed class SubList extends AbstractImmutableList implements RandomAccess { @Stable - private final AbstractImmutableList root; + final AbstractImmutableList root; @Stable - private final int offset; + final int offset; @Stable - private final int size; + final int size; private SubList(AbstractImmutableList root, int offset, int size) { assert root instanceof List12 || root instanceof ListN || root instanceof StableList; @@ -518,8 +515,7 @@ private void rangeCheck(int index) { } private boolean allowNulls() { - return root instanceof ListN listN && listN.allowNulls - || root instanceof StableList; + return root instanceof ListN listN && listN.allowNulls; } @Override @@ -572,14 +568,6 @@ public T[] toArray(T[] a) { return array; } - @Override - public String toString() { - if (root instanceof StableList stableList) { - return StableUtil.renderElements(root, "StableList", stableList.delegates, offset, size); - } else { - return super.toString(); - } - } } @jdk.internal.ValueBased @@ -878,11 +866,55 @@ public List reversed() { return new StableReverseOrderListView<>(this); } + @Override + public List subList(int fromIndex, int toIndex) { + int size = size(); + subListRangeCheck(fromIndex, toIndex, size); + return StableSubList.fromList(this, fromIndex, toIndex); + } + @Override public String toString() { return StableUtil.renderElements(this, "StableList", delegates); } + private static final class StableSubList extends SubList { + + private StableSubList(AbstractImmutableList root, int offset, int size) { + super(root, offset, size); + } + + @Override + public List reversed() { + return new StableReverseOrderListView<>(this); + } + + @Override + public List subList(int fromIndex, int toIndex) { + int size = size(); + subListRangeCheck(fromIndex, toIndex, size); + return StableSubList.fromSubList(this, fromIndex, toIndex); + } + + @Override + public String toString() { + final StableList deepRoot = deepRoot(root); + final StableValueImpl[] delegates = deepRoot.delegates; + // Todo: Provide a copy free solution + final StableValueImpl[] reversed = Arrays.copyOfRange(delegates, this.offset, this.size - offset); + return StableUtil.renderElements(this, "StableCollection", reversed); + } + + static SubList fromList(AbstractImmutableList list, int fromIndex, int toIndex) { + return new StableSubList<>(list, fromIndex, toIndex - fromIndex); + } + + static SubList fromSubList(SubList parent, int fromIndex, int toIndex) { + return new StableSubList<>(parent.root, parent.offset + fromIndex, toIndex - fromIndex); + } + + } + private static final class StableReverseOrderListView extends ReverseOrderListView.Rand { private StableReverseOrderListView(List base) { @@ -892,10 +924,12 @@ private StableReverseOrderListView(List base) { // This method does not evaluate the elements @Override public String toString() { - final StableValueImpl[] delegates = ((StableList)base).delegates; + final StableList deepRoot = deepRoot(base); + final StableValueImpl[] delegates = deepRoot.delegates; + // Todo: Provide a copy free solution final StableValueImpl[] reversed = ArraysSupport.reverse( Arrays.copyOf(delegates, delegates.length)); - return StableUtil.renderElements(base, "Collection", reversed); + return StableUtil.renderElements(this, "Collection", reversed); } @Override @@ -903,6 +937,25 @@ public List reversed() { return base; } + @Override + public List subList(int fromIndex, int toIndex) { + int size = base.size(); + subListRangeCheck(fromIndex, toIndex, size); + return new StableReverseOrderListView<>(base.subList(size - toIndex, size - fromIndex)); + } + + } + + static StableList deepRoot(List list) { + // Avoid spinning code for a pattern matching switch, instead use an if rake + if (list instanceof StableList sl) { + return sl; + } else if (list instanceof StableList.StableReverseOrderListView rev) { + return deepRoot(rev.base); + } else if (list instanceof StableList.StableSubList sub) { + return deepRoot(sub.root); + } + throw new InternalError("Should not reach here: " + list.getClass().getName()); } } @@ -1560,7 +1613,7 @@ static final class StableMap @Override public boolean containsKey(Object o) { return delegate.containsKey(o); } @Override public int size() { return delegate.size(); } - @Override public Set> entrySet() { return new StableMapEntrySet(); } + @Override public Set> entrySet() { return StableMapEntrySet.of(this); } @ForceInline @Override @@ -1582,32 +1635,49 @@ public V getOrDefault(Object key, V defaultValue) { } @jdk.internal.ValueBased - final class StableMapEntrySet extends AbstractImmutableSet> { + static final class StableMapEntrySet extends AbstractImmutableSet> { + + // Use a separate field for the outer class in order to facilitate + // a @Stable annotation. + @Stable + private final StableMap outer; @Stable private final Set>> delegateEntrySet; - StableMapEntrySet() { - this.delegateEntrySet = delegate.entrySet(); + private StableMapEntrySet(StableMap outer) { + this.outer = outer; + this.delegateEntrySet = outer.delegate.entrySet(); } - @Override public Iterator> iterator() { return new LazyMapIterator(); } + @Override public Iterator> iterator() { return LazyMapIterator.of(this); } @Override public int size() { return delegateEntrySet.size(); } - @Override public int hashCode() { return StableMap.this.hashCode(); } + @Override public int hashCode() { return outer.hashCode(); } @Override public String toString() { return StableUtil.renderMappings(this, "StableSet", delegateEntrySet, false); } + // For @ValueBased + static private StableMapEntrySet of(StableMap outer) { + return new StableMapEntrySet<>(outer); + } + @jdk.internal.ValueBased - final class LazyMapIterator implements Iterator> { + static final class LazyMapIterator implements Iterator> { + + // Use a separate field for the outer class in order to facilitate + // a @Stable annotation. + @Stable + private final StableMapEntrySet outer; @Stable private final Iterator>> delegateIterator; - LazyMapIterator() { - this.delegateIterator = delegateEntrySet.iterator(); + private LazyMapIterator(StableMapEntrySet outer) { + this.outer = outer; + this.delegateIterator = outer.delegateEntrySet.iterator(); } @Override public boolean hasNext() { return delegateIterator.hasNext(); } @@ -1617,7 +1687,7 @@ public Entry next() { final Map.Entry> inner = delegateIterator.next(); final K k = inner.getKey(); return new NullableKeyValueHolder<>(k, inner.getValue().orElseSet(new Supplier() { - @Override public V get() { return mapper.apply(k); }})); + @Override public V get() { return outer.outer.mapper.apply(k); }})); } @Override @@ -1628,24 +1698,41 @@ public void forEachRemaining(Consumer> action) { public void accept(Entry> inner) { final K k = inner.getKey(); action.accept(new NullableKeyValueHolder<>(k, inner.getValue().orElseSet(new Supplier() { - @Override public V get() { return mapper.apply(k); }}))); + @Override public V get() { return outer.outer.mapper.apply(k); }}))); } }; delegateIterator.forEachRemaining(innerAction); } + + // For @ValueBased + static private LazyMapIterator of(StableMapEntrySet outer) { + return new LazyMapIterator<>(outer); + } + } } @Override public Collection values() { - return new StableMapValues(); + return StableMapValues.of(this); } - final class StableMapValues extends AbstractImmutableCollection { - @Override public Iterator iterator() { return new ValueIterator(); } - @Override public int size() { return StableMap.this.size(); } - @Override public boolean isEmpty() { return StableMap.this.isEmpty();} - @Override public boolean contains(Object v) { return StableMap.this.containsValue(v); } + @jdk.internal.ValueBased + static final class StableMapValues extends AbstractImmutableCollection { + + // Use a separate field for the outer class in order to facilitate + // a @Stable annotation. + @Stable + private final StableMap outer; + + private StableMapValues(StableMap outer) { + this.outer = outer; + } + + @Override public Iterator iterator() { return outer.new ValueIterator(); } + @Override public int size() { return outer.size(); } + @Override public boolean isEmpty() { return outer.isEmpty();} + @Override public boolean contains(Object v) { return outer.containsValue(v); } private static final IntFunction[]> GENERATOR = new IntFunction[]>() { @Override @@ -1656,9 +1743,15 @@ public StableValueImpl[] apply(int len) { @Override public String toString() { - final StableValueImpl[] values = delegate.values().toArray(GENERATOR); - return StableUtil.renderElements(StableMap.this, "StableMap", values); + final StableValueImpl[] values = outer.delegate.values().toArray(GENERATOR); + return StableUtil.renderElements(this, "StableCollection", values); } + + // For @ValueBased + private static StableMapValues of(StableMap outer) { + return new StableMapValues<>(outer); + } + } @Override diff --git a/src/java.base/share/classes/java/util/ReverseOrderListView.java b/src/java.base/share/classes/java/util/ReverseOrderListView.java index f48b41920c930..67c2c787b3cf8 100644 --- a/src/java.base/share/classes/java/util/ReverseOrderListView.java +++ b/src/java.base/share/classes/java/util/ReverseOrderListView.java @@ -33,13 +33,16 @@ import java.util.stream.Stream; import java.util.stream.StreamSupport; import jdk.internal.util.ArraysSupport; +import jdk.internal.vm.annotation.Stable; /** * Provides a reverse-ordered view of a List. Not serializable. */ class ReverseOrderListView implements List { + @Stable final List base; + @Stable final boolean modifiable; public static List of(List list, boolean modifiable) { diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java index 88be2cea1b6bc..26b2cbc72d667 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java @@ -46,7 +46,10 @@ * @implNote This implementation can be used early in the boot sequence as it does not * rely on reflection, MethodHandles, Streams etc. * + * @param enumType the class type of the Enum * @param firstOrdinal the lowest ordinal used + * @param member an int predicate that can be used to test if an enum is a member + * of the valid inputs (as there might be "holes") * @param delegates a delegate array of inputs to StableValue mappings * @param original the original Function * @param the type of the input to the function @@ -66,8 +69,7 @@ public R apply(E value) { final int index = value.ordinal() - firstOrdinal; final StableValueImpl delegate; // Since we did the member.test above, we know the index is in bounds - delegate = delegates[index]; - return delegate.orElseSet(new Supplier() { + return delegates[index].orElseSet(new Supplier() { @Override public R get() { return original.apply(value); }}); } @@ -84,8 +86,8 @@ public boolean equals(Object obj) { @Override public String toString() { + final Collection>> entries = new ArrayList<>(delegates.length); final E[] enumElements = enumType.getEnumConstants(); - final Collection>> entries = new ArrayList<>(enumElements.length); int ordinal = firstOrdinal; for (int i = 0; i < delegates.length; i++, ordinal++) { if (member.test(ordinal)) { @@ -99,7 +101,7 @@ public String toString() { public static , R> Function of(Set inputs, Function original) { // The input set is not empty - final Class enumType = (Class)inputs.iterator().next().getClass(); + final Class enumType = ((E) inputs.iterator().next()).getDeclaringClass(); final BitSet bitSet = new BitSet(enumType.getEnumConstants().length); int min = Integer.MAX_VALUE; int max = Integer.MIN_VALUE; diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableFunction.java index 1b10593e5e883..e36b4e9b25a0a 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableFunction.java @@ -32,7 +32,7 @@ import java.util.function.Function; import java.util.function.Supplier; -// Note: It would be possible to just use `LazyMap::get` with some additional logic +// Note: It would be possible to just use `StableMap::get` with some additional logic // instead of this class but explicitly providing a class like this provides better // debug capability, exception handling, and may provide better performance. /** diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableIntFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableIntFunction.java index 8bb046a762d94..a921a4de87b2d 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableIntFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableIntFunction.java @@ -31,9 +31,6 @@ import java.util.function.IntFunction; import java.util.function.Supplier; -// Note: It would be possible to just use `LazyList::get` instead of this -// class but explicitly providing a class like this provides better -// debug capability, exception handling, and may provide better performance. /** * Implementation of a stable IntFunction. *

      diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableSupplier.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableSupplier.java index bdb40648db6f0..631a41c5710ef 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableSupplier.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableSupplier.java @@ -58,7 +58,7 @@ public boolean equals(Object obj) { @Override public String toString() { - final Object t = delegate.wrappedContentAcquire(); + final Object t = delegate.wrappedContentsAcquire(); return t == this ? "(this StableSupplier)" : StableValueImpl.renderWrapped(t); } 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..74104ddbb4951 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 @@ -47,7 +47,7 @@ public static String renderElements(Object self, int length) { final StringJoiner sj = new StringJoiner(", ", "[", "]"); for (int i = 0; i < length; i++) { - final Object value = delegates[i + offset].wrappedContentAcquire(); + final Object value = delegates[i + offset].wrappedContentsAcquire(); if (value == self) { sj.add("(this " + selfName + ")"); } else { @@ -63,10 +63,10 @@ public static String renderMappings(Object self, boolean curly) { final StringJoiner sj = new StringJoiner(", ", curly ? "{" : "[", curly ? "}" : "]"); for (var e : delegates) { - final Object value = e.getValue().wrappedContentAcquire(); + final Object value = e.getValue().wrappedContentsAcquire(); final String valueString; if (value == self) { - valueString = ("(this ") + selfName + ")"; + valueString = "(this " + selfName + ")"; } else { valueString = StableValueImpl.renderWrapped(value); } 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..01564f72e4e5f 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 @@ -53,8 +53,8 @@ public final class StableValueImpl implements StableValue { private static final long CONTENT_OFFSET = UNSAFE.objectFieldOffset(StableValueImpl.class, "contents"); + // Used to indicate a holder value is `null` (see field `value` below) - // A wrapper method `nullSentinel()` is used for generic type conversion. private static final Object NULL_SENTINEL = new Object(); // Generally, fields annotated with `@Stable` are accessed by the JVM using special @@ -77,13 +77,13 @@ private StableValueImpl() {} @ForceInline @Override public boolean trySet(T contents) { - if (wrappedContentAcquire() != null) { + if (wrappedContentsAcquire() != null) { return false; } // Prevent reentry via an orElseSet(supplier) preventReentry(); // Mutual exclusion is required here as `orElseSet` might also - // attempt to modify the `wrappedValue` + // attempt to modify `this.contents` synchronized (this) { return wrapAndSet(contents); } @@ -102,7 +102,7 @@ public void setOrThrow(T contents) { @ForceInline @Override public T orElseThrow() { - final Object t = wrappedContentAcquire(); + final Object t = wrappedContentsAcquire(); if (t == null) { throw new NoSuchElementException("No contents set"); } @@ -112,21 +112,21 @@ public T orElseThrow() { @ForceInline @Override public T orElse(T other) { - final Object t = wrappedContentAcquire(); + final Object t = wrappedContentsAcquire(); return (t == null) ? other : unwrap(t); } @ForceInline @Override public boolean isSet() { - return wrappedContentAcquire() != null; + return wrappedContentsAcquire() != null; } @ForceInline @Override public T orElseSet(Supplier supplier) { Objects.requireNonNull(supplier); - final Object t = wrappedContentAcquire(); + final Object t = wrappedContentsAcquire(); return (t == null) ? orElseSetSlowPath(supplier) : unwrap(t); } @@ -149,7 +149,7 @@ private T orElseSetSlowPath(Supplier supplier) { @Override public String toString() { - final Object t = wrappedContentAcquire(); + final Object t = wrappedContentsAcquire(); return t == this ? "(this StableValue)" : renderWrapped(t); @@ -158,7 +158,7 @@ public String toString() { // Internal methods shared with other internal classes @ForceInline - public Object wrappedContentAcquire() { + public Object wrappedContentsAcquire() { return UNSAFE.getReferenceAcquire(this, CONTENT_OFFSET); } @@ -185,7 +185,7 @@ private void preventReentry() { * @return if the contents was set */ @ForceInline - private boolean wrapAndSet(Object newValue) { + private boolean wrapAndSet(T newValue) { assert Thread.holdsLock(this); // We know we hold the monitor here so plain semantic is enough if (contents == null) { diff --git a/test/jdk/java/lang/StableValue/StableFunctionTest.java b/test/jdk/java/lang/StableValue/StableFunctionTest.java index 4476c04695783..07f51470a0b3d 100644 --- a/test/jdk/java/lang/StableValue/StableFunctionTest.java +++ b/test/jdk/java/lang/StableValue/StableFunctionTest.java @@ -52,7 +52,13 @@ enum Value { ZERO(0), ILLEGAL_BEFORE(-1), // Valid values - THIRTEEN(13), + THIRTEEN(13) { + @Override + public String toString() { + // getEnumConstants will be `null` for this enum as it is overridden + return super.toString()+" (Overridden)"; + } + }, ILLEGAL_BETWEEN(-2), FORTY_TWO(42), // Illegal values (not in the input set) @@ -196,6 +202,13 @@ void usesOptimizedVersion() { assertEquals("jdk.internal.lang.stable.StableFunction", emptyFunction.getClass().getName()); } + @Test + void overriddenEnum() { + final var overridden = Value.THIRTEEN; + Function enumFunction = StableValue.function(EnumSet.of(overridden), Value::asInt); + assertEquals(MAPPER.apply(overridden), enumFunction.apply(overridden)); + } + private static Stream> nonEmptySets() { return Stream.of( Set.of(Value.FORTY_TWO, Value.THIRTEEN), diff --git a/test/jdk/java/lang/StableValue/StableListTest.java b/test/jdk/java/lang/StableValue/StableListTest.java index f0ae081c76cb3..e6437456bd700 100644 --- a/test/jdk/java/lang/StableValue/StableListTest.java +++ b/test/jdk/java/lang/StableValue/StableListTest.java @@ -35,7 +35,6 @@ import org.junit.jupiter.params.provider.MethodSource; import java.io.Serializable; -import java.util.Arrays; import java.util.Comparator; import java.util.IdentityHashMap; import java.util.List; @@ -46,7 +45,9 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; +import java.util.function.Function; import java.util.function.IntFunction; +import java.util.function.UnaryOperator; import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; @@ -242,18 +243,8 @@ void subList2() { assertEquals(eq.toString(), lazy.toString()); } - @Test - void subListToString() { - subListToString0(newList()); - subListToString0(newList().subList(1, SIZE)); - subListToString0(newList().subList(1, SIZE).subList(0, SIZE - 2)); - } - - void subListToString0(List subList) { + void assertUnevaluated(List subList) { assertEquals(asString(".unset", subList), subList.toString()); - - var first = subList.getFirst(); - assertEquals(asString(first.toString(), subList), subList.toString()); } @Test @@ -271,18 +262,30 @@ void reversed() { assertEquals("java.util.ImmutableCollections$StableList", reversed2.getClass().getName()); } + // This test makes sure successive view operations retains the property + // of being a Stable view. @Test - void reversedToString() { - var reversed = newList().reversed(); - subListToString0(reversed); - } - - @Test - void subListReversedToString() { - var list = newList().subList(1, SIZE - 1).reversed(); - // This combination is not lazy. There has to be a limit somewhere. - var regularList = newRegularList().subList(1, SIZE - 1).reversed(); - assertEquals(regularList.toString(), list.toString()); + void viewsStable() { + viewOperations().forEach(op0 -> { + viewOperations().forEach( op1 -> { + viewOperations().forEach(op2 -> { + var list = newList(); + var view1 = op0.apply(list); + var view2 = op1.apply(view1); + var view3 = op2.apply(view2); + var className3 = className(view3); + var transitions = className(list) + ", " + + op0 + " -> " + className(view1) + ", " + + op1 + " -> " + className(view2) + ", " + + op2 + " -> " + className3; + assertTrue(className3.contains("Stable"), transitions); + assertUnevaluated(list); + assertUnevaluated(view1); + assertUnevaluated(view2); + assertUnevaluated(view3); + }); + }); + }); } @Test @@ -362,6 +365,40 @@ void distinct() { assertEquals(SIZE, idMap.size()); } + @Test + void childObjectOpsLazy() { + viewOperations().forEach(op0 -> { + viewOperations().forEach(op1 -> { + viewOperations().forEach(op2 -> { + childOperations().forEach(co -> { + var list = newList(); + var view1 = op0.apply(list); + var view2 = op1.apply(view1); + var view3 = op2.apply(view2); + var child = co.apply(view3); + var childClassName = className(child); + var transitions = className(list) + ", " + + op0 + " -> " + className(view1) + ", " + + op1 + " -> " + className(view2) + ", " + + op2 + " -> " + className(view3) + ", " + + co + " -> " + childClassName; + + // None of these operations should trigger evaluation + var childToString = child.toString(); + int childHashCode = child.hashCode(); + boolean childEqualToNewObj = child.equals(new Object()); + + assertUnevaluated(list); + assertUnevaluated(view1); + assertUnevaluated(view2); + assertUnevaluated(view3); + }); + }); + }); + }); + fail(); + } + // Support constructs record Operation(String name, @@ -370,6 +407,36 @@ record Operation(String name, @Override public String toString() { return name; } } + record UnaryOperation(String name, + UnaryOperator> operator) implements UnaryOperator> { + @Override public List apply(List list) { return operator.apply(list); } + @Override public String toString() { return name; } + } + + record ListFunction(String name, + Function, Object> function) implements Function, Object> { + @Override public Object apply(List list) { return function.apply(list); } + @Override public String toString() { return name; } + } + + static Stream viewOperations() { + return Stream.of( + // We need identity to capture all combinations + new UnaryOperation("identity", l -> l), + new UnaryOperation("reversed", List::reversed), + new UnaryOperation("subList", l -> l.subList(0, l.size())) + ); + } + + static Stream childOperations() { + return Stream.of( + // We need identity to capture all combinations + new ListFunction("iterator", List::iterator), + new ListFunction("listIterator", List::listIterator), + new ListFunction("listIterator", List::stream) + ); + } + static Stream nullAverseOperations() { return Stream.of( new Operation("forEach", l -> l.forEach(null)), @@ -433,4 +500,7 @@ static String asString(String first, List list) { .collect(Collectors.joining(", ")) + "]"; } + static String className(Object o) { + return o.getClass().getName(); + } } diff --git a/test/jdk/java/lang/StableValue/StableValueTest.java b/test/jdk/java/lang/StableValue/StableValueTest.java index 0ed683525097f..c06d785134fc4 100644 --- a/test/jdk/java/lang/StableValue/StableValueTest.java +++ b/test/jdk/java/lang/StableValue/StableValueTest.java @@ -29,6 +29,7 @@ import org.junit.jupiter.api.Test; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; From 25a8de2e9c96109dfe6f44ec3bb4d99bb4570bc3 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 28 Apr 2025 16:06:15 +0200 Subject: [PATCH 319/327] Remove forgotten line --- test/jdk/java/lang/StableValue/StableListTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/test/jdk/java/lang/StableValue/StableListTest.java b/test/jdk/java/lang/StableValue/StableListTest.java index e6437456bd700..ae9077b29f4c7 100644 --- a/test/jdk/java/lang/StableValue/StableListTest.java +++ b/test/jdk/java/lang/StableValue/StableListTest.java @@ -396,7 +396,6 @@ void childObjectOpsLazy() { }); }); }); - fail(); } // Support constructs From 6d9de0cb8cd41fa9c01579f5144c745b6243f5a7 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 29 Apr 2025 14:23:52 +0200 Subject: [PATCH 320/327] Update link in docs --- src/java.base/share/classes/java/lang/StableValue.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 8514e5c11ec0f..291386a832946 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -384,8 +384,8 @@ *

      * The method {@link #orElseSet(Supplier)} guarantees that the provided * {@linkplain Supplier} is invoked successfully at most once, even under race. - * Invocations of {@link #setOrThrow(Object)} form a total order of zero or more - * exceptional invocations followed by zero (if the contents were already set) or one + * Invocations of {@link #orElseSet(Supplier)} (Object)} form a total order of zero or + * more exceptional invocations followed by zero (if the contents were already set) or one * successful invocation. Since stable functions and stable collections are built on top * of the same principles as {@linkplain StableValue#orElseSet(Supplier) orElseSet()} they * too are thread safe and guarantee at-most-once-per-input invocation. From a543675152a43f71ebbb0ff1f9ca542f534d328f Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 30 Apr 2025 08:42:20 +0200 Subject: [PATCH 321/327] Address comments --- src/java.base/share/classes/java/lang/StableValue.java | 6 +++--- test/jdk/java/lang/StableValue/StableValueTest.java | 9 ++++----- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 291386a832946..4abcafd13b493 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -613,9 +613,9 @@ static Supplier supplier(Supplier underlying) { * function for the same input, an {@linkplain IllegalStateException} will * be thrown. * - * @param size the size of the allowed inputs in the continuous - * interval {@code [0, size)} - * @param underlying IntFunction used to compute cached values + * @param size the upper bound of the range {@code [0, size)} indicating + * the allowed inputs + * @param underlying {@code IntFunction} used to compute cached values * @param the type of results delivered by the returned IntFunction * @throws IllegalArgumentException if the provided {@code size} is negative. */ diff --git a/test/jdk/java/lang/StableValue/StableValueTest.java b/test/jdk/java/lang/StableValue/StableValueTest.java index c06d785134fc4..4290c8716a046 100644 --- a/test/jdk/java/lang/StableValue/StableValueTest.java +++ b/test/jdk/java/lang/StableValue/StableValueTest.java @@ -356,12 +356,14 @@ void raceMixed() { void race(BiPredicate, Integer> winnerPredicate) { int noThreads = 10; - CountDownLatch starter = new CountDownLatch(1); + CountDownLatch starter = new CountDownLatch(noThreads); StableValue stable = StableValue.of(); Map winners = new ConcurrentHashMap<>(); List threads = IntStream.range(0, noThreads).mapToObj(i -> new Thread(() -> { try { - // Ready, set ... + // Ready ... + starter.countDown(); + // ... set ... starter.await(); // Here we go! winners.put(i, winnerPredicate.test(stable, i)); @@ -371,9 +373,6 @@ void race(BiPredicate, Integer> winnerPredicate) { })) .toList(); threads.forEach(Thread::start); - LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1)); - // Start the race - starter.countDown(); threads.forEach(StableValueTest::join); // There can only be one winner assertEquals(1, winners.values().stream().filter(b -> b).count()); From e3edbf2cacbc9c88c7913db4cc6a01f6093bc4bb Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 30 Apr 2025 16:12:04 +0200 Subject: [PATCH 322/327] Remove unused method and add comment --- .../java/lang/stable/StableMethodHandleBenchmark.java | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableMethodHandleBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableMethodHandleBenchmark.java index 5017fb4e11a47..95721d88cec57 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/StableMethodHandleBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableMethodHandleBenchmark.java @@ -65,7 +65,8 @@ public class StableMethodHandleBenchmark { private static final MethodHandle FINAL_MH = identityHandle(); private static final StableValue STABLE_MH; - private static MethodHandle mh = identityHandle(); + + private static /* intentionally not final */ MethodHandle mh = identityHandle(); private static final Dcl DCL = new Dcl<>(StableMethodHandleBenchmark::identityHandle); private static final AtomicReference ATOMIC_REFERENCE = new AtomicReference<>(identityHandle()); private static final Map MAP = new ConcurrentHashMap<>(); @@ -112,14 +113,6 @@ public int stableMh() throws Throwable { return (int) STABLE_MH.orElseThrow().invokeExact(1); } - Object cp() { - CodeBuilder cob = null; - ConstantPoolBuilder cp = ConstantPoolBuilder.of(); - cob.ldc(cp.constantDynamicEntry(cp.bsmEntry(cp.methodHandleEntry(BSM_CLASS_DATA), List.of()), - cp.nameAndTypeEntry(DEFAULT_NAME, CD_MethodHandle))); - return null; - } - static MethodHandle identityHandle() { var lookup = MethodHandles.lookup(); try { From 49624824646830d67efb4db61003df7aa476ae4f Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 5 May 2025 16:17:13 +0200 Subject: [PATCH 323/327] Address comments in PR --- .../classes/java/util/ImmutableCollections.java | 13 +++++-------- .../jdk/internal/lang/stable/StableValueImpl.java | 6 +++--- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/java.base/share/classes/java/util/ImmutableCollections.java b/src/java.base/share/classes/java/util/ImmutableCollections.java index 9e21cd40e5e8b..76016d1c5d6b8 100644 --- a/src/java.base/share/classes/java/util/ImmutableCollections.java +++ b/src/java.base/share/classes/java/util/ImmutableCollections.java @@ -868,7 +868,7 @@ public List reversed() { @Override public List subList(int fromIndex, int toIndex) { - int size = size(); + final int size = size(); subListRangeCheck(fromIndex, toIndex, size); return StableSubList.fromList(this, fromIndex, toIndex); } @@ -891,15 +891,13 @@ public List reversed() { @Override public List subList(int fromIndex, int toIndex) { - int size = size(); - subListRangeCheck(fromIndex, toIndex, size); + subListRangeCheck(fromIndex, toIndex, size()); return StableSubList.fromSubList(this, fromIndex, toIndex); } @Override public String toString() { - final StableList deepRoot = deepRoot(root); - final StableValueImpl[] delegates = deepRoot.delegates; + final StableValueImpl[] delegates = deepRoot(root).delegates; // Todo: Provide a copy free solution final StableValueImpl[] reversed = Arrays.copyOfRange(delegates, this.offset, this.size - offset); return StableUtil.renderElements(this, "StableCollection", reversed); @@ -924,8 +922,7 @@ private StableReverseOrderListView(List base) { // This method does not evaluate the elements @Override public String toString() { - final StableList deepRoot = deepRoot(base); - final StableValueImpl[] delegates = deepRoot.delegates; + final StableValueImpl[] delegates = deepRoot(base).delegates; // Todo: Provide a copy free solution final StableValueImpl[] reversed = ArraysSupport.reverse( Arrays.copyOf(delegates, delegates.length)); @@ -939,7 +936,7 @@ public List reversed() { @Override public List subList(int fromIndex, int toIndex) { - int size = base.size(); + final int size = base.size(); subListRangeCheck(fromIndex, toIndex, size); return new StableReverseOrderListView<>(base.subList(size - toIndex, size - fromIndex)); } 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 01564f72e4e5f..d886c6851caeb 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 @@ -51,7 +51,7 @@ public final class StableValueImpl implements StableValue { // Unsafe offsets for direct field access - private static final long CONTENT_OFFSET = + private static final long CONTENTS_OFFSET = UNSAFE.objectFieldOffset(StableValueImpl.class, "contents"); // Used to indicate a holder value is `null` (see field `value` below) @@ -159,7 +159,7 @@ public String toString() { @ForceInline public Object wrappedContentsAcquire() { - return UNSAFE.getReferenceAcquire(this, CONTENT_OFFSET); + return UNSAFE.getReferenceAcquire(this, CONTENTS_OFFSET); } static String renderWrapped(Object t) { @@ -189,7 +189,7 @@ private boolean wrapAndSet(T newValue) { assert Thread.holdsLock(this); // We know we hold the monitor here so plain semantic is enough if (contents == null) { - UNSAFE.putReferenceRelease(this, CONTENT_OFFSET, wrap(newValue)); + UNSAFE.putReferenceRelease(this, CONTENTS_OFFSET, wrap(newValue)); return true; } return false; From cc9a7dc17a97f42e63a0a6ee21f3e9688d454853 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 5 May 2025 16:18:51 +0200 Subject: [PATCH 324/327] Simplify furhter --- .../share/classes/java/util/ImmutableCollections.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/java.base/share/classes/java/util/ImmutableCollections.java b/src/java.base/share/classes/java/util/ImmutableCollections.java index 76016d1c5d6b8..ccdb693a36661 100644 --- a/src/java.base/share/classes/java/util/ImmutableCollections.java +++ b/src/java.base/share/classes/java/util/ImmutableCollections.java @@ -897,9 +897,8 @@ public List subList(int fromIndex, int toIndex) { @Override public String toString() { - final StableValueImpl[] delegates = deepRoot(root).delegates; // Todo: Provide a copy free solution - final StableValueImpl[] reversed = Arrays.copyOfRange(delegates, this.offset, this.size - offset); + final StableValueImpl[] reversed = Arrays.copyOfRange(deepRoot(root).delegates, this.offset, this.size - offset); return StableUtil.renderElements(this, "StableCollection", reversed); } From 0a5009d33de3ac7575d0ae6c5f40668632a9d9c7 Mon Sep 17 00:00:00 2001 From: Per-Ake Minborg Date: Mon, 5 May 2025 19:16:00 +0200 Subject: [PATCH 325/327] Update src/java.base/share/classes/java/lang/StableValue.java Co-authored-by: Chen Liang --- src/java.base/share/classes/java/lang/StableValue.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java index 4abcafd13b493..1daeb05ea3343 100644 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -384,7 +384,7 @@ *

      * The method {@link #orElseSet(Supplier)} guarantees that the provided * {@linkplain Supplier} is invoked successfully at most once, even under race. - * Invocations of {@link #orElseSet(Supplier)} (Object)} form a total order of zero or + * Invocations of {@link #orElseSet(Supplier)} form a total order of zero or * more exceptional invocations followed by zero (if the contents were already set) or one * successful invocation. Since stable functions and stable collections are built on top * of the same principles as {@linkplain StableValue#orElseSet(Supplier) orElseSet()} they From 3eebd504b071fdbaf16b0929ac7d8e6a438612ba Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Fri, 9 May 2025 14:07:04 +0200 Subject: [PATCH 326/327] Fix an issue with toString on nested constructs --- .../java/util/ImmutableCollections.java | 63 +++++++++++-------- .../java/lang/StableValue/StableListTest.java | 18 ++++++ 2 files changed, 55 insertions(+), 26 deletions(-) diff --git a/src/java.base/share/classes/java/util/ImmutableCollections.java b/src/java.base/share/classes/java/util/ImmutableCollections.java index ccdb693a36661..e71c86cf30c9d 100644 --- a/src/java.base/share/classes/java/util/ImmutableCollections.java +++ b/src/java.base/share/classes/java/util/ImmutableCollections.java @@ -785,8 +785,15 @@ public int lastIndexOf(Object o) { } } + @FunctionalInterface + interface HasStableDelegates { + StableValueImpl[] delegates(); + } + @jdk.internal.ValueBased - static final class StableList extends AbstractImmutableList { + static final class StableList + extends AbstractImmutableList + implements HasStableDelegates { @Stable private final IntFunction mapper; @@ -870,7 +877,7 @@ public List reversed() { public List subList(int fromIndex, int toIndex) { final int size = size(); subListRangeCheck(fromIndex, toIndex, size); - return StableSubList.fromList(this, fromIndex, toIndex); + return StableSubList.fromStableList(this, fromIndex, toIndex); } @Override @@ -878,7 +885,13 @@ public String toString() { return StableUtil.renderElements(this, "StableList", delegates); } - private static final class StableSubList extends SubList { + @Override + public StableValueImpl[] delegates() { + return delegates; + } + + private static final class StableSubList extends SubList + implements HasStableDelegates { private StableSubList(AbstractImmutableList root, int offset, int size) { super(root, offset, size); @@ -892,27 +905,34 @@ public List reversed() { @Override public List subList(int fromIndex, int toIndex) { subListRangeCheck(fromIndex, toIndex, size()); - return StableSubList.fromSubList(this, fromIndex, toIndex); + return StableSubList.fromStableSubList(this, fromIndex, toIndex); } @Override public String toString() { - // Todo: Provide a copy free solution - final StableValueImpl[] reversed = Arrays.copyOfRange(deepRoot(root).delegates, this.offset, this.size - offset); - return StableUtil.renderElements(this, "StableCollection", reversed); + return StableUtil.renderElements(this, "StableCollection", delegates()); } - static SubList fromList(AbstractImmutableList list, int fromIndex, int toIndex) { + @Override + public StableValueImpl[] delegates() { + @SuppressWarnings("unchecked") + final var rootDelegates = ((HasStableDelegates) root).delegates(); + return Arrays.copyOfRange(rootDelegates, offset, offset + size); + } + + static SubList fromStableList(StableList list, int fromIndex, int toIndex) { return new StableSubList<>(list, fromIndex, toIndex - fromIndex); } - static SubList fromSubList(SubList parent, int fromIndex, int toIndex) { + static SubList fromStableSubList(StableSubList parent, int fromIndex, int toIndex) { return new StableSubList<>(parent.root, parent.offset + fromIndex, toIndex - fromIndex); } } - private static final class StableReverseOrderListView extends ReverseOrderListView.Rand { + private static final class StableReverseOrderListView + extends ReverseOrderListView.Rand + implements HasStableDelegates { private StableReverseOrderListView(List base) { super(base, false); @@ -921,11 +941,7 @@ private StableReverseOrderListView(List base) { // This method does not evaluate the elements @Override public String toString() { - final StableValueImpl[] delegates = deepRoot(base).delegates; - // Todo: Provide a copy free solution - final StableValueImpl[] reversed = ArraysSupport.reverse( - Arrays.copyOf(delegates, delegates.length)); - return StableUtil.renderElements(this, "Collection", reversed); + return StableUtil.renderElements(this, "StableCollection", delegates()); } @Override @@ -940,18 +956,13 @@ public List subList(int fromIndex, int toIndex) { return new StableReverseOrderListView<>(base.subList(size - toIndex, size - fromIndex)); } - } - - static StableList deepRoot(List list) { - // Avoid spinning code for a pattern matching switch, instead use an if rake - if (list instanceof StableList sl) { - return sl; - } else if (list instanceof StableList.StableReverseOrderListView rev) { - return deepRoot(rev.base); - } else if (list instanceof StableList.StableSubList sub) { - return deepRoot(sub.root); + @Override + public StableValueImpl[] delegates() { + @SuppressWarnings("unchecked") + final var baseDelegates = ((HasStableDelegates) base).delegates(); + return ArraysSupport.reverse( + Arrays.copyOf(baseDelegates, baseDelegates.length)); } - throw new InternalError("Should not reach here: " + list.getClass().getName()); } } diff --git a/test/jdk/java/lang/StableValue/StableListTest.java b/test/jdk/java/lang/StableValue/StableListTest.java index ae9077b29f4c7..6b5400918722d 100644 --- a/test/jdk/java/lang/StableValue/StableListTest.java +++ b/test/jdk/java/lang/StableValue/StableListTest.java @@ -262,6 +262,24 @@ void reversed() { assertEquals("java.util.ImmutableCollections$StableList", reversed2.getClass().getName()); } + @Test + void sublistReversedToString() { + var actual = StableValue.list(4, IDENTITY); + var expected = List.of(0, 1, 2, 3); + for (UnaryOperation op : List.of( + new UnaryOperation("subList", l -> l.subList(1, 3)), + new UnaryOperation("reversed", List::reversed))) { + actual = op.apply(actual); + expected = op.apply(expected); + } + // Touch one of the elements + actual.getLast(); + + var actualToString = actual.toString(); + var expectedToString = expected.toString().replace("2", ".unset"); + assertEquals(expectedToString, actualToString); + } + // This test makes sure successive view operations retains the property // of being a Stable view. @Test From a2826336972afa144131d7be68dd0300de84b56f Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 12 May 2025 09:38:46 +0200 Subject: [PATCH 327/327] Address comments --- .../classes/java/util/ImmutableCollections.java | 14 ++++---------- .../classes/java/util/ReverseOrderListView.java | 2 +- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/src/java.base/share/classes/java/util/ImmutableCollections.java b/src/java.base/share/classes/java/util/ImmutableCollections.java index e71c86cf30c9d..660e5506ffdba 100644 --- a/src/java.base/share/classes/java/util/ImmutableCollections.java +++ b/src/java.base/share/classes/java/util/ImmutableCollections.java @@ -137,11 +137,11 @@ public List listFromTrustedArrayNullsAllowed(Object[] array) { return ImmutableCollections.listFromTrustedArrayNullsAllowed(array); } public List stableList(int size, IntFunction mapper) { - // A stable list is not Serializable so, we cannot return `List.of()` if `size == 0` + // A stable list is not Serializable, so we cannot return `List.of()` if `size == 0` return new StableList<>(size, mapper); } public Map stableMap(Set keys, Function mapper) { - // A stable map is not Serializable so, we cannot return `Map.of()` if `keys.isEmpty()` + // A stable map is not Serializable, so we cannot return `Map.of()` if `keys.isEmpty()` return new StableMap<>(keys, mapper); } }); @@ -875,8 +875,7 @@ public List reversed() { @Override public List subList(int fromIndex, int toIndex) { - final int size = size(); - subListRangeCheck(fromIndex, toIndex, size); + subListRangeCheck(fromIndex, toIndex, size()); return StableSubList.fromStableList(this, fromIndex, toIndex); } @@ -944,11 +943,6 @@ public String toString() { return StableUtil.renderElements(this, "StableCollection", delegates()); } - @Override - public List reversed() { - return base; - } - @Override public List subList(int fromIndex, int toIndex) { final int size = base.size(); @@ -1667,7 +1661,7 @@ public String toString() { } // For @ValueBased - static private StableMapEntrySet of(StableMap outer) { + private static StableMapEntrySet of(StableMap outer) { return new StableMapEntrySet<>(outer); } diff --git a/src/java.base/share/classes/java/util/ReverseOrderListView.java b/src/java.base/share/classes/java/util/ReverseOrderListView.java index 9d3f0a746645e..75d69f38aaf7b 100644 --- a/src/java.base/share/classes/java/util/ReverseOrderListView.java +++ b/src/java.base/share/classes/java/util/ReverseOrderListView.java @@ -43,7 +43,7 @@ class ReverseOrderListView implements List { @Stable final List base; @Stable - final boolean modifiable; + final Boolean modifiable; public static List of(List list, boolean modifiable) { if (list instanceof RandomAccess) {