8356080: Address post-integration comments for Stable Values

Reviewed-by: liach
This commit is contained in:
Per Minborg 2025-05-13 13:40:48 +00:00
parent fa419489d3
commit 066477de80
14 changed files with 388 additions and 131 deletions

View File

@ -384,8 +384,8 @@ import java.util.function.Supplier;
* <p> * <p>
* The method {@link #orElseSet(Supplier)} guarantees that the provided * 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 * Invocations of {@link #orElseSet(Supplier)} form a total order of zero or
* exceptional invocations followed by zero (if the contents were already set) or one * 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 * successful invocation. Since stable functions and stable collections are built on top
* of the same principles as {@linkplain StableValue#orElseSet(Supplier) orElseSet()} they * of the same principles as {@linkplain StableValue#orElseSet(Supplier) orElseSet()} they
* too are thread safe and guarantee at-most-once-per-input invocation. * too are thread safe and guarantee at-most-once-per-input invocation.
@ -444,8 +444,6 @@ import java.util.function.Supplier;
public sealed interface StableValue<T> public sealed interface StableValue<T>
permits StableValueImpl { permits StableValueImpl {
// Principal methods
/** /**
* Tries to set the contents of this StableValue to the provided {@code contents}. * 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 * The contents of this StableValue can only be set once, implying this method only
@ -509,8 +507,6 @@ public sealed interface StableValue<T>
*/ */
T orElseSet(Supplier<? extends T> supplier); T orElseSet(Supplier<? extends T> supplier);
// Convenience methods
/** /**
* Sets the contents of this StableValue to the provided {@code contents}, or, if * Sets the contents of this StableValue to the provided {@code contents}, or, if
* already set, throws {@code IllegalStateException}. * already set, throws {@code IllegalStateException}.
@ -519,6 +515,9 @@ public sealed interface StableValue<T>
* *
* @param contents to set * @param contents to set
* @throws IllegalStateException if the contents was already 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); void setOrThrow(T contents);
@ -573,8 +572,8 @@ public sealed interface StableValue<T>
* at most once even in a multi-threaded environment. Competing threads invoking the * 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 * 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 * 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 * thrown by the computing thread. The competing threads will then observe the newly
* computed value (if any) and will then never execute. * computed value (if any) and will then never execute the {@code underlying} supplier.
* <p> * <p>
* If the provided {@code underlying} supplier throws an exception, it is rethrown * If the provided {@code underlying} supplier throws an exception, it is rethrown
* to the initial caller and no contents is recorded. * to the initial caller and no contents is recorded.
@ -614,9 +613,9 @@ public sealed interface StableValue<T>
* function for the same input, an {@linkplain IllegalStateException} will * function for the same input, an {@linkplain IllegalStateException} will
* be thrown. * be thrown.
* *
* @param size the size of the allowed inputs in the continuous * @param size the upper bound of the range {@code [0, size)} indicating
* interval {@code [0, size)} * the allowed inputs
* @param underlying IntFunction used to compute cached values * @param underlying {@code IntFunction} used to compute cached values
* @param <R> the type of results delivered by the returned IntFunction * @param <R> the type of results delivered by the returned IntFunction
* @throws IllegalArgumentException if the provided {@code size} is negative. * @throws IllegalArgumentException if the provided {@code size} is negative.
*/ */
@ -684,7 +683,7 @@ public sealed interface StableValue<T>
* If invoking the provided {@code mapper} function throws an exception, it * 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. * is rethrown to the initial caller and no value for the element is recorded.
* <p> * <p>
* Any direct {@link List#subList(int, int) subList} or {@link List#reversed()} views * Any {@link List#subList(int, int) subList} or {@link List#reversed()} views
* of the returned list are also stable. * of the returned list are also stable.
* <p> * <p>
* The returned list and its {@link List#subList(int, int) subList} or * The returned list and its {@link List#subList(int, int) subList} or
@ -727,8 +726,8 @@ public sealed interface StableValue<T>
* is rethrown to the initial caller and no value associated with the provided key * is rethrown to the initial caller and no value associated with the provided key
* is recorded. * is recorded.
* <p> * <p>
* Any direct {@link Map#values()} or {@link Map#entrySet()} views * Any {@link Map#values()} or {@link Map#entrySet()} views of the returned map are
* of the returned map are also stable. * also stable.
* <p> * <p>
* The returned map is unmodifiable and does not implement the * The returned map is unmodifiable and does not implement the
* {@linkplain Collection##optional-operations optional operations} in the * {@linkplain Collection##optional-operations optional operations} in the

View File

@ -47,7 +47,6 @@ import jdk.internal.lang.stable.StableUtil;
import jdk.internal.lang.stable.StableValueImpl; import jdk.internal.lang.stable.StableValueImpl;
import jdk.internal.misc.CDS; import jdk.internal.misc.CDS;
import jdk.internal.util.ArraysSupport; import jdk.internal.util.ArraysSupport;
import jdk.internal.util.NullableKeyValueHolder;
import jdk.internal.vm.annotation.ForceInline; import jdk.internal.vm.annotation.ForceInline;
import jdk.internal.vm.annotation.Stable; import jdk.internal.vm.annotation.Stable;
@ -137,9 +136,11 @@ class ImmutableCollections {
return ImmutableCollections.listFromTrustedArrayNullsAllowed(array); return ImmutableCollections.listFromTrustedArrayNullsAllowed(array);
} }
public <E> List<E> stableList(int size, IntFunction<? extends E> mapper) { public <E> List<E> stableList(int size, IntFunction<? extends E> 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 <K, V> Map<K, V> stableMap(Set<K> keys, Function<? super K, ? extends V> mapper) { public <K, V> Map<K, V> stableMap(Set<K> keys, Function<? super K, ? extends V> mapper) {
// A stable map is not Serializable, so we cannot return `Map.of()` if `keys.isEmpty()`
return new StableMap<>(keys, mapper); return new StableMap<>(keys, mapper);
} }
}); });
@ -264,11 +265,6 @@ class ImmutableCollections {
} }
} }
static <E> List<E> stableList(int size, IntFunction<? extends E> mapper) {
// A lazy list is not Serializable so, we cannot return `List.of()` if size == 0
return new StableList<>(size, mapper);
}
// ---------- List Implementations ---------- // ---------- List Implementations ----------
@jdk.internal.ValueBased @jdk.internal.ValueBased
@ -454,17 +450,17 @@ class ImmutableCollections {
} }
} }
static final class SubList<E> extends AbstractImmutableList<E> static sealed class SubList<E> extends AbstractImmutableList<E>
implements RandomAccess { implements RandomAccess {
@Stable @Stable
private final AbstractImmutableList<E> root; final AbstractImmutableList<E> root;
@Stable @Stable
private final int offset; final int offset;
@Stable @Stable
private final int size; final int size;
private SubList(AbstractImmutableList<E> root, int offset, int size) { private SubList(AbstractImmutableList<E> root, int offset, int size) {
assert root instanceof List12 || root instanceof ListN || root instanceof StableList; assert root instanceof List12 || root instanceof ListN || root instanceof StableList;
@ -517,9 +513,8 @@ class ImmutableCollections {
} }
} }
private boolean allowNulls() { boolean allowNulls() {
return root instanceof ListN<?> listN && listN.allowNulls return root instanceof ListN<?> listN && listN.allowNulls;
|| root instanceof StableList<E>;
} }
@Override @Override
@ -572,14 +567,6 @@ class ImmutableCollections {
return array; return array;
} }
@Override
public String toString() {
if (root instanceof StableList<E> stableList) {
return StableUtil.renderElements(root, "StableList", stableList.delegates, offset, size);
} else {
return super.toString();
}
}
} }
@jdk.internal.ValueBased @jdk.internal.ValueBased
@ -797,8 +784,15 @@ class ImmutableCollections {
} }
} }
@FunctionalInterface
interface HasStableDelegates<E> {
StableValueImpl<E>[] delegates();
}
@jdk.internal.ValueBased @jdk.internal.ValueBased
static final class StableList<E> extends AbstractImmutableList<E> { static final class StableList<E>
extends AbstractImmutableList<E>
implements HasStableDelegates<E> {
@Stable @Stable
private final IntFunction<? extends E> mapper; private final IntFunction<? extends E> mapper;
@ -879,11 +873,69 @@ class ImmutableCollections {
} }
@Override @Override
public String toString() { public List<E> subList(int fromIndex, int toIndex) {
return StableUtil.renderElements(this, "StableList", delegates); subListRangeCheck(fromIndex, toIndex, size());
return StableSubList.fromStableList(this, fromIndex, toIndex);
} }
private static final class StableReverseOrderListView<E> extends ReverseOrderListView.Rand<E> { @Override
public String toString() {
return StableUtil.renderElements(this, "StableCollection", delegates);
}
@Override
public StableValueImpl<E>[] delegates() {
return delegates;
}
private static final class StableSubList<E> extends SubList<E>
implements HasStableDelegates<E> {
private StableSubList(AbstractImmutableList<E> root, int offset, int size) {
super(root, offset, size);
}
@Override
public List<E> reversed() {
return new StableReverseOrderListView<>(this);
}
@Override
public List<E> subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size());
return StableSubList.fromStableSubList(this, fromIndex, toIndex);
}
@Override
public String toString() {
return StableUtil.renderElements(this, "StableCollection", delegates());
}
@Override
boolean allowNulls() {
return true;
}
@Override
public StableValueImpl<E>[] delegates() {
@SuppressWarnings("unchecked")
final var rootDelegates = ((HasStableDelegates<E>) root).delegates();
return Arrays.copyOfRange(rootDelegates, offset, offset + size);
}
static <E> SubList<E> fromStableList(StableList<E> list, int fromIndex, int toIndex) {
return new StableSubList<>(list, fromIndex, toIndex - fromIndex);
}
static <E> SubList<E> fromStableSubList(StableSubList<E> parent, int fromIndex, int toIndex) {
return new StableSubList<>(parent.root, parent.offset + fromIndex, toIndex - fromIndex);
}
}
private static final class StableReverseOrderListView<E>
extends ReverseOrderListView.Rand<E>
implements HasStableDelegates<E> {
private StableReverseOrderListView(List<E> base) { private StableReverseOrderListView(List<E> base) {
super(base, false); super(base, false);
@ -892,17 +944,23 @@ class ImmutableCollections {
// This method does not evaluate the elements // This method does not evaluate the elements
@Override @Override
public String toString() { public String toString() {
final StableValueImpl<E>[] delegates = ((StableList<E>)base).delegates; return StableUtil.renderElements(this, "StableCollection", delegates());
final StableValueImpl<E>[] reversed = ArraysSupport.reverse(
Arrays.copyOf(delegates, delegates.length));
return StableUtil.renderElements(base, "Collection", reversed);
} }
@Override @Override
public List<E> reversed() { public List<E> subList(int fromIndex, int toIndex) {
return base; final int size = base.size();
subListRangeCheck(fromIndex, toIndex, size);
return new StableReverseOrderListView<>(base.subList(size - toIndex, size - fromIndex));
} }
@Override
public StableValueImpl<E>[] delegates() {
@SuppressWarnings("unchecked")
final var baseDelegates = ((HasStableDelegates<E>) base).delegates();
return ArraysSupport.reverse(
Arrays.copyOf(baseDelegates, baseDelegates.length));
}
} }
} }
@ -1560,7 +1618,7 @@ class ImmutableCollections {
@Override public boolean containsKey(Object o) { return delegate.containsKey(o); } @Override public boolean containsKey(Object o) { return delegate.containsKey(o); }
@Override public int size() { return delegate.size(); } @Override public int size() { return delegate.size(); }
@Override public Set<Map.Entry<K, V>> entrySet() { return new StableMapEntrySet(); } @Override public Set<Map.Entry<K, V>> entrySet() { return StableMapEntrySet.of(this); }
@ForceInline @ForceInline
@Override @Override
@ -1582,32 +1640,49 @@ class ImmutableCollections {
} }
@jdk.internal.ValueBased @jdk.internal.ValueBased
final class StableMapEntrySet extends AbstractImmutableSet<Map.Entry<K, V>> { static final class StableMapEntrySet<K, V> extends AbstractImmutableSet<Map.Entry<K, V>> {
// Use a separate field for the outer class in order to facilitate
// a @Stable annotation.
@Stable
private final StableMap<K, V> outer;
@Stable @Stable
private final Set<Map.Entry<K, StableValueImpl<V>>> delegateEntrySet; private final Set<Map.Entry<K, StableValueImpl<V>>> delegateEntrySet;
StableMapEntrySet() { private StableMapEntrySet(StableMap<K, V> outer) {
this.delegateEntrySet = delegate.entrySet(); this.outer = outer;
this.delegateEntrySet = outer.delegate.entrySet();
} }
@Override public Iterator<Map.Entry<K, V>> iterator() { return new LazyMapIterator(); } @Override public Iterator<Map.Entry<K, V>> iterator() { return LazyMapIterator.of(this); }
@Override public int size() { return delegateEntrySet.size(); } @Override public int size() { return delegateEntrySet.size(); }
@Override public int hashCode() { return StableMap.this.hashCode(); } @Override public int hashCode() { return outer.hashCode(); }
@Override @Override
public String toString() { public String toString() {
return StableUtil.renderMappings(this, "StableSet", delegateEntrySet, false); return StableUtil.renderMappings(this, "StableCollection", delegateEntrySet, false);
}
// For @ValueBased
private static <K, V> StableMapEntrySet<K, V> of(StableMap<K, V> outer) {
return new StableMapEntrySet<>(outer);
} }
@jdk.internal.ValueBased @jdk.internal.ValueBased
final class LazyMapIterator implements Iterator<Map.Entry<K, V>> { static final class LazyMapIterator<K, V> implements Iterator<Map.Entry<K, V>> {
// Use a separate field for the outer class in order to facilitate
// a @Stable annotation.
@Stable
private final StableMapEntrySet<K, V> outer;
@Stable @Stable
private final Iterator<Map.Entry<K, StableValueImpl<V>>> delegateIterator; private final Iterator<Map.Entry<K, StableValueImpl<V>>> delegateIterator;
LazyMapIterator() { private LazyMapIterator(StableMapEntrySet<K, V> outer) {
this.delegateIterator = delegateEntrySet.iterator(); this.outer = outer;
this.delegateIterator = outer.delegateEntrySet.iterator();
} }
@Override public boolean hasNext() { return delegateIterator.hasNext(); } @Override public boolean hasNext() { return delegateIterator.hasNext(); }
@ -1616,8 +1691,8 @@ class ImmutableCollections {
public Entry<K, V> next() { public Entry<K, V> next() {
final Map.Entry<K, StableValueImpl<V>> inner = delegateIterator.next(); final Map.Entry<K, StableValueImpl<V>> inner = delegateIterator.next();
final K k = inner.getKey(); final K k = inner.getKey();
return new NullableKeyValueHolder<>(k, inner.getValue().orElseSet(new Supplier<V>() { return new StableEntry<>(k, inner.getValue(), new Supplier<V>() {
@Override public V get() { return mapper.apply(k); }})); @Override public V get() { return outer.outer.mapper.apply(k); }});
} }
@Override @Override
@ -1627,25 +1702,60 @@ class ImmutableCollections {
@Override @Override
public void accept(Entry<K, StableValueImpl<V>> inner) { public void accept(Entry<K, StableValueImpl<V>> inner) {
final K k = inner.getKey(); final K k = inner.getKey();
action.accept(new NullableKeyValueHolder<>(k, inner.getValue().orElseSet(new Supplier<V>() { action.accept(new StableEntry<>(k, inner.getValue(), new Supplier<V>() {
@Override public V get() { return mapper.apply(k); }}))); @Override public V get() { return outer.outer.mapper.apply(k); }}));
} }
}; };
delegateIterator.forEachRemaining(innerAction); delegateIterator.forEachRemaining(innerAction);
} }
// For @ValueBased
private static <K, V> LazyMapIterator<K, V> of(StableMapEntrySet<K, V> outer) {
return new LazyMapIterator<>(outer);
}
} }
} }
private record StableEntry<K, V>(K getKey, // trick
StableValueImpl<V> stableValue,
Supplier<? extends V> supplier) implements Map.Entry<K, V> {
@Override public V setValue(V value) { throw uoe(); }
@Override public V getValue() { return stableValue.orElseSet(supplier); }
@Override public int hashCode() { return hash(getKey()) ^ hash(getValue()); }
@Override public String toString() { return getKey() + "=" + stableValue.toString(); }
@Override public boolean equals(Object o) {
return o instanceof Map.Entry<?, ?> e
&& Objects.equals(getKey(), e.getKey())
// Invoke `getValue()` as late as possible to avoid evaluation
&& Objects.equals(getValue(), e.getValue());
}
private int hash(Object obj) { return (obj == null) ? 0 : obj.hashCode(); }
}
@Override @Override
public Collection<V> values() { public Collection<V> values() {
return new StableMapValues(); return StableMapValues.of(this);
} }
final class StableMapValues extends AbstractImmutableCollection<V> { @jdk.internal.ValueBased
@Override public Iterator<V> iterator() { return new ValueIterator(); } static final class StableMapValues<V> extends AbstractImmutableCollection<V> {
@Override public int size() { return StableMap.this.size(); }
@Override public boolean isEmpty() { return StableMap.this.isEmpty();} // Use a separate field for the outer class in order to facilitate
@Override public boolean contains(Object v) { return StableMap.this.containsValue(v); } // a @Stable annotation.
@Stable
private final StableMap<?, V> outer;
private StableMapValues(StableMap<?, V> outer) {
this.outer = outer;
}
@Override public Iterator<V> 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<StableValueImpl<?>[]> GENERATOR = new IntFunction<StableValueImpl<?>[]>() { private static final IntFunction<StableValueImpl<?>[]> GENERATOR = new IntFunction<StableValueImpl<?>[]>() {
@Override @Override
@ -1656,9 +1766,15 @@ class ImmutableCollections {
@Override @Override
public String toString() { public String toString() {
final StableValueImpl<?>[] values = delegate.values().toArray(GENERATOR); final StableValueImpl<?>[] values = outer.delegate.values().toArray(GENERATOR);
return StableUtil.renderElements(StableMap.this, "StableMap", values); return StableUtil.renderElements(this, "StableCollection", values);
} }
// For @ValueBased
private static <V> StableMapValues<V> of(StableMap<?, V> outer) {
return new StableMapValues<>(outer);
}
} }
@Override @Override

View File

@ -33,14 +33,17 @@ import java.util.function.UnaryOperator;
import java.util.stream.Stream; import java.util.stream.Stream;
import java.util.stream.StreamSupport; import java.util.stream.StreamSupport;
import jdk.internal.util.ArraysSupport; import jdk.internal.util.ArraysSupport;
import jdk.internal.vm.annotation.Stable;
/** /**
* Provides a reverse-ordered view of a List. Not serializable. * Provides a reverse-ordered view of a List. Not serializable.
*/ */
class ReverseOrderListView<E> implements List<E> { class ReverseOrderListView<E> implements List<E> {
@Stable
final List<E> base; final List<E> base;
final boolean modifiable; @Stable
final Boolean modifiable;
public static <T> List<T> of(List<T> list, boolean modifiable) { public static <T> List<T> of(List<T> list, boolean modifiable) {
if (list instanceof RandomAccess) { if (list instanceof RandomAccess) {

View File

@ -46,7 +46,10 @@ import java.util.function.Supplier;
* @implNote This implementation can be used early in the boot sequence as it does not * @implNote This implementation can be used early in the boot sequence as it does not
* rely on reflection, MethodHandles, Streams etc. * rely on reflection, MethodHandles, Streams etc.
* *
* @param enumType the class type of the Enum
* @param firstOrdinal the lowest ordinal used * @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 delegates a delegate array of inputs to StableValue mappings
* @param original the original Function * @param original the original Function
* @param <E> the type of the input to the function * @param <E> the type of the input to the function
@ -64,10 +67,8 @@ public record StableEnumFunction<E extends Enum<E>, R>(Class<E> enumType,
throw new IllegalArgumentException("Input not allowed: " + value); throw new IllegalArgumentException("Input not allowed: " + value);
} }
final int index = value.ordinal() - firstOrdinal; final int index = value.ordinal() - firstOrdinal;
final StableValueImpl<R> delegate;
// Since we did the member.test above, we know the index is in bounds // Since we did the member.test above, we know the index is in bounds
delegate = delegates[index]; return delegates[index].orElseSet(new Supplier<R>() {
return delegate.orElseSet(new Supplier<R>() {
@Override public R get() { return original.apply(value); }}); @Override public R get() { return original.apply(value); }});
} }
@ -84,8 +85,8 @@ public record StableEnumFunction<E extends Enum<E>, R>(Class<E> enumType,
@Override @Override
public String toString() { public String toString() {
final Collection<Map.Entry<E, StableValueImpl<R>>> entries = new ArrayList<>(delegates.length);
final E[] enumElements = enumType.getEnumConstants(); final E[] enumElements = enumType.getEnumConstants();
final Collection<Map.Entry<E, StableValueImpl<R>>> entries = new ArrayList<>(enumElements.length);
int ordinal = firstOrdinal; int ordinal = firstOrdinal;
for (int i = 0; i < delegates.length; i++, ordinal++) { for (int i = 0; i < delegates.length; i++, ordinal++) {
if (member.test(ordinal)) { if (member.test(ordinal)) {
@ -99,7 +100,7 @@ public record StableEnumFunction<E extends Enum<E>, R>(Class<E> enumType,
public static <T, E extends Enum<E>, R> Function<T, R> of(Set<? extends T> inputs, public static <T, E extends Enum<E>, R> Function<T, R> of(Set<? extends T> inputs,
Function<? super T, ? extends R> original) { Function<? super T, ? extends R> original) {
// The input set is not empty // The input set is not empty
final Class<E> enumType = (Class<E>)inputs.iterator().next().getClass(); final Class<E> enumType = ((E) inputs.iterator().next()).getDeclaringClass();
final BitSet bitSet = new BitSet(enumType.getEnumConstants().length); final BitSet bitSet = new BitSet(enumType.getEnumConstants().length);
int min = Integer.MAX_VALUE; int min = Integer.MAX_VALUE;
int max = Integer.MIN_VALUE; int max = Integer.MIN_VALUE;

View File

@ -32,7 +32,7 @@ import java.util.Set;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Supplier; 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 // instead of this class but explicitly providing a class like this provides better
// debug capability, exception handling, and may provide better performance. // debug capability, exception handling, and may provide better performance.
/** /**

View File

@ -31,9 +31,6 @@ import jdk.internal.vm.annotation.Stable;
import java.util.function.IntFunction; import java.util.function.IntFunction;
import java.util.function.Supplier; 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. * Implementation of a stable IntFunction.
* <p> * <p>

View File

@ -58,7 +58,7 @@ public record StableSupplier<T>(StableValueImpl<T> delegate,
@Override @Override
public String toString() { public String toString() {
final Object t = delegate.wrappedContentAcquire(); final Object t = delegate.wrappedContentsAcquire();
return t == this ? "(this StableSupplier)" : StableValueImpl.renderWrapped(t); return t == this ? "(this StableSupplier)" : StableValueImpl.renderWrapped(t);
} }

View File

@ -47,7 +47,7 @@ public final class StableUtil {
int length) { int length) {
final StringJoiner sj = new StringJoiner(", ", "[", "]"); final StringJoiner sj = new StringJoiner(", ", "[", "]");
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
final Object value = delegates[i + offset].wrappedContentAcquire(); final Object value = delegates[i + offset].wrappedContentsAcquire();
if (value == self) { if (value == self) {
sj.add("(this " + selfName + ")"); sj.add("(this " + selfName + ")");
} else { } else {
@ -63,10 +63,10 @@ public final class StableUtil {
boolean curly) { boolean curly) {
final StringJoiner sj = new StringJoiner(", ", curly ? "{" : "[", curly ? "}" : "]"); final StringJoiner sj = new StringJoiner(", ", curly ? "{" : "[", curly ? "}" : "]");
for (var e : delegates) { for (var e : delegates) {
final Object value = e.getValue().wrappedContentAcquire(); final Object value = e.getValue().wrappedContentsAcquire();
final String valueString; final String valueString;
if (value == self) { if (value == self) {
valueString = ("(this ") + selfName + ")"; valueString = "(this " + selfName + ")";
} else { } else {
valueString = StableValueImpl.renderWrapped(value); valueString = StableValueImpl.renderWrapped(value);
} }

View File

@ -51,10 +51,10 @@ public final class StableValueImpl<T> implements StableValue<T> {
// Unsafe offsets for direct field access // Unsafe offsets for direct field access
private static final long CONTENT_OFFSET = private static final long CONTENTS_OFFSET =
UNSAFE.objectFieldOffset(StableValueImpl.class, "contents"); 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. // Used to indicate a holder value is `null` (see field `contents` below)
private static final Object NULL_SENTINEL = new Object(); private static final Object NULL_SENTINEL = new Object();
// Generally, fields annotated with `@Stable` are accessed by the JVM using special // Generally, fields annotated with `@Stable` are accessed by the JVM using special
@ -77,13 +77,13 @@ public final class StableValueImpl<T> implements StableValue<T> {
@ForceInline @ForceInline
@Override @Override
public boolean trySet(T contents) { public boolean trySet(T contents) {
if (wrappedContentAcquire() != null) { if (wrappedContentsAcquire() != null) {
return false; return false;
} }
// Prevent reentry via an orElseSet(supplier) // Prevent reentry via an orElseSet(supplier)
preventReentry(); preventReentry();
// Mutual exclusion is required here as `orElseSet` might also // Mutual exclusion is required here as `orElseSet` might also
// attempt to modify the `wrappedValue` // attempt to modify `this.contents`
synchronized (this) { synchronized (this) {
return wrapAndSet(contents); return wrapAndSet(contents);
} }
@ -102,7 +102,7 @@ public final class StableValueImpl<T> implements StableValue<T> {
@ForceInline @ForceInline
@Override @Override
public T orElseThrow() { public T orElseThrow() {
final Object t = wrappedContentAcquire(); final Object t = wrappedContentsAcquire();
if (t == null) { if (t == null) {
throw new NoSuchElementException("No contents set"); throw new NoSuchElementException("No contents set");
} }
@ -112,21 +112,21 @@ public final class StableValueImpl<T> implements StableValue<T> {
@ForceInline @ForceInline
@Override @Override
public T orElse(T other) { public T orElse(T other) {
final Object t = wrappedContentAcquire(); final Object t = wrappedContentsAcquire();
return (t == null) ? other : unwrap(t); return (t == null) ? other : unwrap(t);
} }
@ForceInline @ForceInline
@Override @Override
public boolean isSet() { public boolean isSet() {
return wrappedContentAcquire() != null; return wrappedContentsAcquire() != null;
} }
@ForceInline @ForceInline
@Override @Override
public T orElseSet(Supplier<? extends T> supplier) { public T orElseSet(Supplier<? extends T> supplier) {
Objects.requireNonNull(supplier); Objects.requireNonNull(supplier);
final Object t = wrappedContentAcquire(); final Object t = wrappedContentsAcquire();
return (t == null) ? orElseSetSlowPath(supplier) : unwrap(t); return (t == null) ? orElseSetSlowPath(supplier) : unwrap(t);
} }
@ -149,7 +149,7 @@ public final class StableValueImpl<T> implements StableValue<T> {
@Override @Override
public String toString() { public String toString() {
final Object t = wrappedContentAcquire(); final Object t = wrappedContentsAcquire();
return t == this return t == this
? "(this StableValue)" ? "(this StableValue)"
: renderWrapped(t); : renderWrapped(t);
@ -158,8 +158,8 @@ public final class StableValueImpl<T> implements StableValue<T> {
// Internal methods shared with other internal classes // Internal methods shared with other internal classes
@ForceInline @ForceInline
public Object wrappedContentAcquire() { public Object wrappedContentsAcquire() {
return UNSAFE.getReferenceAcquire(this, CONTENT_OFFSET); return UNSAFE.getReferenceAcquire(this, CONTENTS_OFFSET);
} }
static String renderWrapped(Object t) { static String renderWrapped(Object t) {
@ -185,11 +185,11 @@ public final class StableValueImpl<T> implements StableValue<T> {
* @return if the contents was set * @return if the contents was set
*/ */
@ForceInline @ForceInline
private boolean wrapAndSet(Object newValue) { private boolean wrapAndSet(T newValue) {
assert Thread.holdsLock(this); assert Thread.holdsLock(this);
// We know we hold the monitor here so plain semantic is enough // We know we hold the monitor here so plain semantic is enough
if (contents == null) { if (contents == null) {
UNSAFE.putReferenceRelease(this, CONTENT_OFFSET, wrap(newValue)); UNSAFE.putReferenceRelease(this, CONTENTS_OFFSET, wrap(newValue));
return true; return true;
} }
return false; return false;

View File

@ -52,7 +52,13 @@ final class StableFunctionTest {
ZERO(0), ZERO(0),
ILLEGAL_BEFORE(-1), ILLEGAL_BEFORE(-1),
// Valid values // 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), ILLEGAL_BETWEEN(-2),
FORTY_TWO(42), FORTY_TWO(42),
// Illegal values (not in the input set) // Illegal values (not in the input set)
@ -196,6 +202,13 @@ final class StableFunctionTest {
assertEquals("jdk.internal.lang.stable.StableFunction", emptyFunction.getClass().getName()); assertEquals("jdk.internal.lang.stable.StableFunction", emptyFunction.getClass().getName());
} }
@Test
void overriddenEnum() {
final var overridden = Value.THIRTEEN;
Function<Value, Integer> enumFunction = StableValue.function(EnumSet.of(overridden), Value::asInt);
assertEquals(MAPPER.apply(overridden), enumFunction.apply(overridden));
}
private static Stream<Set<Value>> nonEmptySets() { private static Stream<Set<Value>> nonEmptySets() {
return Stream.of( return Stream.of(
Set.of(Value.FORTY_TWO, Value.THIRTEEN), Set.of(Value.FORTY_TWO, Value.THIRTEEN),

View File

@ -35,7 +35,6 @@ import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.MethodSource;
import java.io.Serializable; import java.io.Serializable;
import java.util.Arrays;
import java.util.Comparator; import java.util.Comparator;
import java.util.IdentityHashMap; import java.util.IdentityHashMap;
import java.util.List; import java.util.List;
@ -46,7 +45,9 @@ import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.IntFunction; import java.util.function.IntFunction;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.IntStream; import java.util.stream.IntStream;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -242,18 +243,8 @@ final class StableListTest {
assertEquals(eq.toString(), lazy.toString()); assertEquals(eq.toString(), lazy.toString());
} }
@Test void assertUnevaluated(List<Integer> subList) {
void subListToString() {
subListToString0(newList());
subListToString0(newList().subList(1, SIZE));
subListToString0(newList().subList(1, SIZE).subList(0, SIZE - 2));
}
void subListToString0(List<Integer> subList) {
assertEquals(asString(".unset", subList), subList.toString()); assertEquals(asString(".unset", subList), subList.toString());
var first = subList.getFirst();
assertEquals(asString(first.toString(), subList), subList.toString());
} }
@Test @Test
@ -272,17 +263,47 @@ final class StableListTest {
} }
@Test @Test
void reversedToString() { void sublistReversedToString() {
var reversed = newList().reversed(); var actual = StableValue.list(4, IDENTITY);
subListToString0(reversed); 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 @Test
void subListReversedToString() { void viewsStable() {
var list = newList().subList(1, SIZE - 1).reversed(); viewOperations().forEach(op0 -> {
// This combination is not lazy. There has to be a limit somewhere. viewOperations().forEach( op1 -> {
var regularList = newRegularList().subList(1, SIZE - 1).reversed(); viewOperations().forEach(op2 -> {
assertEquals(regularList.toString(), list.toString()); 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 @Test
@ -294,6 +315,23 @@ final class StableListTest {
assertEquals("Recursive initialization of a stable value is illegal", x.getMessage()); assertEquals("Recursive initialization of a stable value is illegal", x.getMessage());
} }
@Test
void indexOfNullInViews() {
final int size = 5;
final int middle = 2;
viewOperations().forEach(op0 -> {
viewOperations().forEach( op1 -> {
viewOperations().forEach(op2 -> {
var list = StableValue.list(size, x -> x == middle ? null : x);;
var view1 = op0.apply(list);
var view2 = op1.apply(view1);
var view3 = op2.apply(view2);
assertEquals(middle, view3.indexOf(null));
});
});
});
}
// Immutability // Immutability
@ParameterizedTest @ParameterizedTest
@ -362,6 +400,39 @@ final class StableListTest {
assertEquals(SIZE, idMap.size()); 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);
});
});
});
});
}
// Support constructs // Support constructs
record Operation(String name, record Operation(String name,
@ -370,6 +441,36 @@ final class StableListTest {
@Override public String toString() { return name; } @Override public String toString() { return name; }
} }
record UnaryOperation(String name,
UnaryOperator<List<Integer>> operator) implements UnaryOperator<List<Integer>> {
@Override public List<Integer> apply(List<Integer> list) { return operator.apply(list); }
@Override public String toString() { return name; }
}
record ListFunction(String name,
Function<List<Integer>, Object> function) implements Function<List<Integer>, Object> {
@Override public Object apply(List<Integer> list) { return function.apply(list); }
@Override public String toString() { return name; }
}
static Stream<UnaryOperation> 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<ListFunction> 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<Operation> nullAverseOperations() { static Stream<Operation> nullAverseOperations() {
return Stream.of( return Stream.of(
new Operation("forEach", l -> l.forEach(null)), new Operation("forEach", l -> l.forEach(null)),
@ -433,4 +534,7 @@ final class StableListTest {
.collect(Collectors.joining(", ")) + "]"; .collect(Collectors.joining(", ")) + "]";
} }
static String className(Object o) {
return o.getClass().getName();
}
} }

View File

@ -248,6 +248,37 @@ final class StableMapTest {
assertEquals(KEYS, encountered); assertEquals(KEYS, encountered);
} }
@Test
void stableEntry() {
var map = newMap();
var entry = map.entrySet().stream()
.filter(e -> e.getKey().equals(KEY))
.findAny()
.orElseThrow();
assertEquals(KEY + "=.unset", entry.toString());
var otherDifferent = Map.entry(-1, -1);
assertNotEquals(entry, otherDifferent);
assertEquals(KEY + "=.unset", entry.toString());
var otherEqual = Map.entry(KEY, KEY);
assertEquals(entry, otherEqual);
assertEquals(KEY + "=" + KEY, entry.toString());
assertEquals(entry.hashCode(), otherEqual.hashCode());
}
@Test
void stableForEachEntry() {
var map = newMap();
// Only touch the key.
map.entrySet().iterator().forEachRemaining(Map.Entry::getKey);
map.entrySet().iterator()
.forEachRemaining(e -> assertTrue(e.toString().contains(".unset")));
// Only touch the value.
map.entrySet().iterator().forEachRemaining(Map.Entry::getValue);
map.entrySet().iterator()
.forEachRemaining(e -> assertFalse(e.toString().contains(".unset")));
}
// Immutability // Immutability
@ParameterizedTest @ParameterizedTest
@MethodSource("unsupportedOperations") @MethodSource("unsupportedOperations")

View File

@ -29,6 +29,7 @@
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
@ -355,12 +356,14 @@ final class StableValueTest {
void race(BiPredicate<StableValue<Integer>, Integer> winnerPredicate) { void race(BiPredicate<StableValue<Integer>, Integer> winnerPredicate) {
int noThreads = 10; int noThreads = 10;
CountDownLatch starter = new CountDownLatch(1); CountDownLatch starter = new CountDownLatch(noThreads);
StableValue<Integer> stable = StableValue.of(); StableValue<Integer> stable = StableValue.of();
Map<Integer, Boolean> winners = new ConcurrentHashMap<>(); Map<Integer, Boolean> winners = new ConcurrentHashMap<>();
List<Thread> threads = IntStream.range(0, noThreads).mapToObj(i -> new Thread(() -> { List<Thread> threads = IntStream.range(0, noThreads).mapToObj(i -> new Thread(() -> {
try { try {
// Ready, set ... // Ready ...
starter.countDown();
// ... set ...
starter.await(); starter.await();
// Here we go! // Here we go!
winners.put(i, winnerPredicate.test(stable, i)); winners.put(i, winnerPredicate.test(stable, i));
@ -370,9 +373,6 @@ final class StableValueTest {
})) }))
.toList(); .toList();
threads.forEach(Thread::start); threads.forEach(Thread::start);
LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1));
// Start the race
starter.countDown();
threads.forEach(StableValueTest::join); threads.forEach(StableValueTest::join);
// There can only be one winner // There can only be one winner
assertEquals(1, winners.values().stream().filter(b -> b).count()); assertEquals(1, winners.values().stream().filter(b -> b).count());

View File

@ -65,7 +65,8 @@ public class StableMethodHandleBenchmark {
private static final MethodHandle FINAL_MH = identityHandle(); private static final MethodHandle FINAL_MH = identityHandle();
private static final StableValue<MethodHandle> STABLE_MH; private static final StableValue<MethodHandle> STABLE_MH;
private static MethodHandle mh = identityHandle();
private static /* intentionally not final */ MethodHandle mh = identityHandle();
private static final Dcl<MethodHandle> DCL = new Dcl<>(StableMethodHandleBenchmark::identityHandle); private static final Dcl<MethodHandle> DCL = new Dcl<>(StableMethodHandleBenchmark::identityHandle);
private static final AtomicReference<MethodHandle> ATOMIC_REFERENCE = new AtomicReference<>(identityHandle()); private static final AtomicReference<MethodHandle> ATOMIC_REFERENCE = new AtomicReference<>(identityHandle());
private static final Map<String, MethodHandle> MAP = new ConcurrentHashMap<>(); private static final Map<String, MethodHandle> MAP = new ConcurrentHashMap<>();
@ -112,14 +113,6 @@ public class StableMethodHandleBenchmark {
return (int) STABLE_MH.orElseThrow().invokeExact(1); 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() { static MethodHandle identityHandle() {
var lookup = MethodHandles.lookup(); var lookup = MethodHandles.lookup();
try { try {