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 getterStream(Class type, Comparator comparator) { // filter static fields - Stream 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 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> valueTypeFields(Class type) { - List> 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() + .>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..946f27c6b3b --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/value/LayoutIteration.java @@ -0,0 +1,167 @@ +/* + * 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. + *

+ * 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> 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 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 getters = new ArrayList<>(); + + Sink(Class receiverType) { + this.receiverType = receiverType; + } + + void accept(long offsetNoHeader, Class itemType) { + Wrapper w = itemType.isPrimitive() ? Wrapper.forPrimitiveType(itemType) : Wrapper.OBJECT; + 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 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 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> classes = LayoutIteration.computeElementGetters(One.class).stream() + .map(mh -> mh.type().returnType()).collect(Collectors.toSet()); + assertEquals(Set.of(int.class, short.class), classes); + Map, 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); + } +}