From 63c46847bdfe5fdc7894aeb0a95d2d6212ac7379 Mon Sep 17 00:00:00 2001 From: liach <liach@openjdk.org> Date: Wed, 12 Feb 2025 18:01:48 -0600 Subject: [PATCH 1/4] 8348606: [lworld] Substitutability test may perform heap allocations --- .../java/lang/invoke/MethodHandleImpl.java | 6 +- .../java/lang/runtime/ValueObjectMethods.java | 31 ++-- .../internal/access/JavaLangInvokeAccess.java | 13 +- .../jdk/internal/value/LayoutIteration.java | 168 ++++++++++++++++++ .../valuetypes/LayoutIterationTest.java | 89 ++++++++++ 5 files changed, 285 insertions(+), 22 deletions(-) create mode 100644 src/java.base/share/classes/jdk/internal/value/LayoutIteration.java create mode 100644 test/jdk/valhalla/valuetypes/LayoutIterationTest.java diff --git a/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java b/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java index c6c9d05ec31..5a2220fd9ed 100644 --- a/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java +++ b/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 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 @@ -1642,6 +1642,10 @@ public MethodHandle serializableConstructor(Class<?> decl, Constructor<?> ctorTo return IMPL_LOOKUP.serializableConstructor(decl, ctorToCall); } + @Override + public MethodHandle assertAsType(MethodHandle original, MethodType assertedType) { + return original.viewAsType(assertedType, false); + } }); } diff --git a/src/java.base/share/classes/java/lang/runtime/ValueObjectMethods.java b/src/java.base/share/classes/java/lang/runtime/ValueObjectMethods.java index 450703a36ca..a2927c73bd4 100644 --- a/src/java.base/share/classes/java/lang/runtime/ValueObjectMethods.java +++ b/src/java.base/share/classes/java/lang/runtime/ValueObjectMethods.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -45,12 +45,14 @@ import java.util.Objects; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Stream; import jdk.internal.access.JavaLangInvokeAccess; import jdk.internal.access.SharedSecrets; +import jdk.internal.value.LayoutIteration; import sun.invoke.util.Wrapper; import static java.lang.invoke.MethodHandles.constant; @@ -92,19 +94,12 @@ static class MethodHandleBuilder { static Stream<MethodHandle> getterStream(Class<?> type, Comparator<MethodHandle> comparator) { // filter static fields - Stream<MethodHandle> s = Arrays.stream(type.getDeclaredFields()) - .filter(f -> !Modifier.isStatic(f.getModifiers())) - .map(f -> { - try { - return JLIA.unreflectField(f, false); - } catch (IllegalAccessException e) { - throw newLinkageError(e); - } - }); + List<MethodHandle> mhs = LayoutIteration.ELEMENTS.get(type); if (comparator != null) { - s = s.sorted(comparator); + mhs = new ArrayList<>(mhs); + mhs.sort(comparator); } - return s; + return mhs.stream(); } static MethodHandle hashCodeForType(Class<?> type) { @@ -141,13 +136,11 @@ static Class<?> fieldType(MethodHandle getter) { } private static List<Class<?>> valueTypeFields(Class<?> type) { - List<Class<?>> result = new ArrayList<>(); - Arrays.stream(type.getDeclaredFields()) - .filter(f -> !Modifier.isStatic(f.getModifiers())) - .map(f -> f.getType()) - .filter(ft -> ft.isValue() && !result.contains(ft)) - .forEach(result::add); - return result; + return LayoutIteration.ELEMENTS.get(type).stream() + .<Class<?>>map(mh -> mh.type().returnType()) + .filter(Class::isValue) + .distinct() + .toList(); } /* diff --git a/src/java.base/share/classes/jdk/internal/access/JavaLangInvokeAccess.java b/src/java.base/share/classes/jdk/internal/access/JavaLangInvokeAccess.java index 0caf66bbc42..4cb49de31c2 100644 --- a/src/java.base/share/classes/jdk/internal/access/JavaLangInvokeAccess.java +++ b/src/java.base/share/classes/jdk/internal/access/JavaLangInvokeAccess.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 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 @@ -28,7 +28,6 @@ import jdk.internal.foreign.abi.NativeEntryPoint; import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles.Lookup; import java.lang.invoke.MethodType; import java.lang.invoke.VarHandle; import java.lang.reflect.Constructor; @@ -176,4 +175,14 @@ public interface JavaLangInvokeAccess { * This method should only be used by ReflectionFactory::newConstructorForSerialization. */ MethodHandle serializableConstructor(Class<?> decl, Constructor<?> ctorToCall) throws IllegalAccessException; + + /** + * Asserts a method handle to be another type without the conversion adaptions. + * Useful to avoid many redundant casts. + * + * @param original original MH + * @param assertedType the asserted type the origina MH can execute as + * @return the cheap view without extra adaptions + */ + MethodHandle assertAsType(MethodHandle original, MethodType assertedType); } diff --git a/src/java.base/share/classes/jdk/internal/value/LayoutIteration.java b/src/java.base/share/classes/jdk/internal/value/LayoutIteration.java new file mode 100644 index 00000000000..c99c2469ce4 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/value/LayoutIteration.java @@ -0,0 +1,168 @@ +/* + * 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.value; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; + +import jdk.internal.access.JavaLangInvokeAccess; +import jdk.internal.access.SharedSecrets; +import jdk.internal.misc.Unsafe; +import sun.invoke.util.Wrapper; + +/** + * Iterates the layout elements of a value type. + * <p> + * In the long run, we should do this: + * Iterate the layout, create a mask masking bytes used by Object/abstract value + * class reference fields. Do a byte-wise compare and get the mask of value + * mismatch; if the mask's all clear, fine; if the mask has bits beyond our + * mask, fail; otherwise, compare reference fields indicated by the mismatch + * mask. There may be paddings to ignore, too, depends... + */ +public final class LayoutIteration { + // Initializer in static initializers below, order dependent + public static final ClassValue<List<MethodHandle>> ELEMENTS; + + /** + * {@return a list of method handles accessing the basic elements} + * Basic elements are 8 primitives and pointers (to identity or value objects). + * Primitives and pointers are distinguished by the MH return types. + * The MH types are {@code flatType -> fieldType}. + * + * @param flatType the class that has a flat layout + * @return the accessors + * @throws IllegalArgumentException if argument has no flat layout + */ + public static List<MethodHandle> computeElementGetters(Class<?> flatType) { + if (!canHaveFlatLayout(flatType)) + throw new IllegalArgumentException(flatType + " cannot be flat"); + var sink = new Sink(flatType); + iterateFields(U.valueHeaderSize(flatType), flatType, sink); + return List.copyOf(sink.getters); + } + + // Ensures the given class has a potential a flat layout + private static boolean canHaveFlatLayout(Class<?> flatType) { + return flatType.isValue() && Modifier.isFinal(flatType.getModifiers()); + } + + private static final class Sink { + final Class<?> receiverType; + final List<MethodHandle> getters = new ArrayList<>(); + + Sink(Class<?> receiverType) { + this.receiverType = receiverType; + } + + void accept(long offsetNoHeader, Class<?> itemType) { + Wrapper w = itemType.isPrimitive() ? Wrapper.forPrimitiveType(itemType) : Wrapper.OBJECT; + System.out.println(receiverType + " offset " + offsetNoHeader + " " + itemType); + var mh = MethodHandles.insertArguments(FIELD_GETTERS.get(w.ordinal()), 1, offsetNoHeader); + assert mh.type() == MethodType.methodType(w.primitiveType(), Object.class); + mh = JLIA.assertAsType(mh, MethodType.methodType(itemType, receiverType)); + getters.add(mh); + } + } + + // Sink is good for one to many mappings + private static void iterateFields(long enclosingOffset, Class<?> currentClass, Sink sink) { + assert canHaveFlatLayout(currentClass) : currentClass + " cannot be flat"; + long memberOffsetDelta = enclosingOffset - U.valueHeaderSize(currentClass); + for (Field f : currentClass.getDeclaredFields()) { + if (Modifier.isStatic(f.getModifiers())) + continue; + var type = f.getType(); + long memberOffset = U.objectFieldOffset(f) + memberOffsetDelta; + if (!U.isFlatField(f)) { + sink.accept(memberOffset, type); + } else { + iterateFields(memberOffset, type, sink); + } + } + } + + private static boolean getBoolean(Object o, long offset) { + return U.getBoolean(o, offset); + } + private static byte getByte(Object o, long offset) { + return U.getByte(o, offset); + } + private static short getShort(Object o, long offset) { + return U.getShort(o, offset); + } + private static char getCharacter(Object o, long offset) { + return U.getChar(o, offset); + } + private static int getInteger(Object o, long offset) { + return U.getInt(o, offset); + } + private static long getLong(Object o, long offset) { + return U.getLong(o, offset); + } + private static float getFloat(Object o, long offset) { + return U.getFloat(o, offset); + } + private static double getDouble(Object o, long offset) { + return U.getDouble(o, offset); + } + public static Object getObject(Object o, long offset) { + return U.getReference(o, offset); + } + + private static final Unsafe U = Unsafe.getUnsafe(); + private static final JavaLangInvokeAccess JLIA = SharedSecrets.getJavaLangInvokeAccess(); + private static final List<MethodHandle> FIELD_GETTERS; + static { + MethodHandle[] fieldGetters = new MethodHandle[9]; + var lookup = MethodHandles.lookup(); + var type = MethodType.methodType(void.class, Object.class, long.class); + try { + for (Wrapper w : Wrapper.values()) { + if (w != Wrapper.VOID) { + fieldGetters[w.ordinal()] = lookup.findStatic(LayoutIteration.class, + "get" + w.wrapperSimpleName(), type.changeReturnType(w.primitiveType())); + } + } + } catch (ReflectiveOperationException ex) { + throw new ExceptionInInitializerError(ex); + } + FIELD_GETTERS = List.of(fieldGetters); + ELEMENTS = new ClassValue<>() { + @Override + protected List<MethodHandle> computeValue(Class<?> type) { + return computeElementGetters(type); + } + }; + } + + private LayoutIteration() {} +} diff --git a/test/jdk/valhalla/valuetypes/LayoutIterationTest.java b/test/jdk/valhalla/valuetypes/LayoutIterationTest.java new file mode 100644 index 00000000000..b0aadd7e794 --- /dev/null +++ b/test/jdk/valhalla/valuetypes/LayoutIterationTest.java @@ -0,0 +1,89 @@ +/* + * 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. + */ + + +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import jdk.internal.value.LayoutIteration; +import jdk.internal.vm.annotation.ImplicitlyConstructible; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/* + * @test + * @summary test LayoutIteration + * @enablePreview + * @modules java.base/jdk.internal.vm.annotation + * java.base/jdk.internal.value + * @run junit/othervm LayoutIterationTest + */ +class LayoutIterationTest { + + @LooselyConsistentValue + @ImplicitlyConstructible + static value class One { + int a; + short b; + + One(int a, short b) { + this.a = a; + this.b = b; + } + } + + @LooselyConsistentValue + static value class Two { + @NullRestricted + One one = new One(5, (short) 3); + One anotherOne = new One(4, (short) 2); + long l = 5L; + } + + @Test + void test() { + Two t = new Two(); + Set<Class<?>> classes = LayoutIteration.computeElementGetters(One.class).stream() + .map(mh -> mh.type().returnType()).collect(Collectors.toSet()); + assertEquals(Set.of(int.class, short.class), classes); + Map<Class<?>, Object> values = LayoutIteration.computeElementGetters(Two.class).stream() + .collect(Collectors.toMap(mh -> mh.type().returnType(), mh -> { + try { + return (Object) mh.invoke(t); + } catch (Throwable ex) { + return Assertions.fail(ex); + } + })); + assertEquals(Map.of( + int.class, t.one.a, + short.class, t.one.b, + One.class, t.anotherOne, + long.class, t.l + ), values); + } +} From 4d6a7d5c795441193628e90bcc00a15dd1cdf72e Mon Sep 17 00:00:00 2001 From: liach <liach@openjdk.org> Date: Wed, 12 Feb 2025 19:13:45 -0600 Subject: [PATCH 2/4] Fix redundant debug and failing IO test --- .../share/classes/jdk/internal/value/LayoutIteration.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/java.base/share/classes/jdk/internal/value/LayoutIteration.java b/src/java.base/share/classes/jdk/internal/value/LayoutIteration.java index c99c2469ce4..946f27c6b3b 100644 --- a/src/java.base/share/classes/jdk/internal/value/LayoutIteration.java +++ b/src/java.base/share/classes/jdk/internal/value/LayoutIteration.java @@ -85,7 +85,6 @@ private static final class Sink { void accept(long offsetNoHeader, Class<?> itemType) { Wrapper w = itemType.isPrimitive() ? Wrapper.forPrimitiveType(itemType) : Wrapper.OBJECT; - System.out.println(receiverType + " offset " + offsetNoHeader + " " + itemType); var mh = MethodHandles.insertArguments(FIELD_GETTERS.get(w.ordinal()), 1, offsetNoHeader); assert mh.type() == MethodType.methodType(w.primitiveType(), Object.class); mh = JLIA.assertAsType(mh, MethodType.methodType(itemType, receiverType)); From 0e02b94f159eb99e19642dc75ae63053f3562dbc Mon Sep 17 00:00:00 2001 From: liach <liach@openjdk.org> Date: Wed, 9 Apr 2025 07:38:07 -0500 Subject: [PATCH 3/4] Fix failing compiler tests, thanks to @TobiHartmann --- src/hotspot/share/opto/library_call.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/hotspot/share/opto/library_call.cpp b/src/hotspot/share/opto/library_call.cpp index ce8c943f4f0..69d6d33d911 100644 --- a/src/hotspot/share/opto/library_call.cpp +++ b/src/hotspot/share/opto/library_call.cpp @@ -2546,6 +2546,13 @@ bool LibraryCallKit::inline_unsafe_access(bool is_store, const BasicType type, c if (field != nullptr) { bt = type2field[field->type()->basic_type()]; } + if (bt != alias_type->basic_type()) { + // Type mismatch. Is it an access to a nested flat field? + field = k->get_field_by_offset(off, false); + if (field != nullptr) { + bt = type2field[field->type()->basic_type()]; + } + } assert(bt == alias_type->basic_type() || is_flat, "should match"); } else { bt = alias_type->basic_type(); From 921a8d59961f46b0e29f5bd08644556d0eff3360 Mon Sep 17 00:00:00 2001 From: liach <liach@openjdk.org> Date: Wed, 9 Apr 2025 07:48:27 -0500 Subject: [PATCH 4/4] Remove obsolete annotation --- test/jdk/valhalla/valuetypes/LayoutIterationTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/jdk/valhalla/valuetypes/LayoutIterationTest.java b/test/jdk/valhalla/valuetypes/LayoutIterationTest.java index b0aadd7e794..65f5b90176e 100644 --- a/test/jdk/valhalla/valuetypes/LayoutIterationTest.java +++ b/test/jdk/valhalla/valuetypes/LayoutIterationTest.java @@ -27,7 +27,6 @@ import java.util.stream.Collectors; import jdk.internal.value.LayoutIteration; -import jdk.internal.vm.annotation.ImplicitlyConstructible; import jdk.internal.vm.annotation.LooselyConsistentValue; import jdk.internal.vm.annotation.NullRestricted; import org.junit.jupiter.api.Assertions; @@ -46,7 +45,6 @@ class LayoutIterationTest { @LooselyConsistentValue - @ImplicitlyConstructible static value class One { int a; short b; @@ -54,6 +52,7 @@ static value class One { One(int a, short b) { this.a = a; this.b = b; + super(); } }