Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

8348606: [lworld] Substitutability test may perform heap allocations #1364

Draft
wants to merge 3 commits into
base: lworld
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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);
}
});
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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();
}

/*
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
167 changes: 167 additions & 0 deletions src/java.base/share/classes/jdk/internal/value/LayoutIteration.java
Original file line number Diff line number Diff line change
@@ -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.
* <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;
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() {}
}
89 changes: 89 additions & 0 deletions test/jdk/valhalla/valuetypes/LayoutIterationTest.java
Original file line number Diff line number Diff line change
@@ -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);
}
}