8156915: introduce MethodHandle factory for array length

Reviewed-by: sundar
This commit is contained in:
Michael Haupt 2016-05-18 10:42:29 +02:00
parent 2bb441c404
commit c4976196f5
4 changed files with 197 additions and 33 deletions

View File

@ -706,6 +706,9 @@ class InvokerBytecodeGenerator {
case ARRAY_STORE:
emitArrayStore(name);
continue;
case ARRAY_LENGTH:
emitArrayLength(name);
continue;
case IDENTITY:
assert(name.arguments.length == 1);
emitPushArguments(name);
@ -740,15 +743,16 @@ class InvokerBytecodeGenerator {
return classFile;
}
void emitArrayLoad(Name name) { emitArrayOp(name, Opcodes.AALOAD); }
void emitArrayStore(Name name) { emitArrayOp(name, Opcodes.AASTORE); }
void emitArrayLoad(Name name) { emitArrayOp(name, Opcodes.AALOAD); }
void emitArrayStore(Name name) { emitArrayOp(name, Opcodes.AASTORE); }
void emitArrayLength(Name name) { emitArrayOp(name, Opcodes.ARRAYLENGTH); }
void emitArrayOp(Name name, int arrayOpcode) {
assert arrayOpcode == Opcodes.AALOAD || arrayOpcode == Opcodes.AASTORE;
assert arrayOpcode == Opcodes.AALOAD || arrayOpcode == Opcodes.AASTORE || arrayOpcode == Opcodes.ARRAYLENGTH;
Class<?> elementType = name.function.methodType().parameterType(0).getComponentType();
assert elementType != null;
emitPushArguments(name);
if (elementType.isPrimitive()) {
if (arrayOpcode != Opcodes.ARRAYLENGTH && elementType.isPrimitive()) {
Wrapper w = Wrapper.forPrimitiveType(elementType);
arrayOpcode = arrayInsnOpcode(arrayTypeCode(w), arrayOpcode);
}

View File

@ -66,25 +66,28 @@ import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP;
/// Factory methods to create method handles:
static MethodHandle makeArrayElementAccessor(Class<?> arrayClass, boolean isSetter) {
if (arrayClass == Object[].class)
return (isSetter ? ArrayAccessor.OBJECT_ARRAY_SETTER : ArrayAccessor.OBJECT_ARRAY_GETTER);
static MethodHandle makeArrayElementAccessor(Class<?> arrayClass, ArrayAccess access) {
if (arrayClass == Object[].class) {
return ArrayAccess.objectAccessor(access);
}
if (!arrayClass.isArray())
throw newIllegalArgumentException("not an array: "+arrayClass);
MethodHandle[] cache = ArrayAccessor.TYPED_ACCESSORS.get(arrayClass);
int cacheIndex = (isSetter ? ArrayAccessor.SETTER_INDEX : ArrayAccessor.GETTER_INDEX);
int cacheIndex = ArrayAccess.cacheIndex(access);
MethodHandle mh = cache[cacheIndex];
if (mh != null) return mh;
mh = ArrayAccessor.getAccessor(arrayClass, isSetter);
MethodType correctType = ArrayAccessor.correctType(arrayClass, isSetter);
mh = ArrayAccessor.getAccessor(arrayClass, access);
MethodType correctType = ArrayAccessor.correctType(arrayClass, access);
if (mh.type() != correctType) {
assert(mh.type().parameterType(0) == Object[].class);
assert((isSetter ? mh.type().parameterType(2) : mh.type().returnType()) == Object.class);
assert(isSetter || correctType.parameterType(0).getComponentType() == correctType.returnType());
/* if access == SET */ assert(access != ArrayAccess.SET || mh.type().parameterType(2) == Object.class);
/* if access == GET */ assert(access != ArrayAccess.GET ||
(mh.type().returnType() == Object.class &&
correctType.parameterType(0).getComponentType() == correctType.returnType()));
// safe to view non-strictly, because element type follows from array type
mh = mh.viewAsType(correctType, false);
}
mh = makeIntrinsic(mh, (isSetter ? Intrinsic.ARRAY_STORE : Intrinsic.ARRAY_LOAD));
mh = makeIntrinsic(mh, ArrayAccess.intrinsic(access));
// Atomically update accessor cache.
synchronized(cache) {
if (cache[cacheIndex] == null) {
@ -97,9 +100,52 @@ import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP;
return mh;
}
enum ArrayAccess {
GET, SET, LENGTH;
// As ArrayAccess and ArrayAccessor have a circular dependency, the ArrayAccess properties cannot be stored in
// final fields.
static String opName(ArrayAccess a) {
switch (a) {
case GET: return "getElement";
case SET: return "setElement";
case LENGTH: return "length";
}
throw new AssertionError();
}
static MethodHandle objectAccessor(ArrayAccess a) {
switch (a) {
case GET: return ArrayAccessor.OBJECT_ARRAY_GETTER;
case SET: return ArrayAccessor.OBJECT_ARRAY_SETTER;
case LENGTH: return ArrayAccessor.OBJECT_ARRAY_LENGTH;
}
throw new AssertionError();
}
static int cacheIndex(ArrayAccess a) {
switch (a) {
case GET: return ArrayAccessor.GETTER_INDEX;
case SET: return ArrayAccessor.SETTER_INDEX;
case LENGTH: return ArrayAccessor.LENGTH_INDEX;
}
throw new AssertionError();
}
static Intrinsic intrinsic(ArrayAccess a) {
switch (a) {
case GET: return Intrinsic.ARRAY_LOAD;
case SET: return Intrinsic.ARRAY_STORE;
case LENGTH: return Intrinsic.ARRAY_LENGTH;
}
throw new AssertionError();
}
}
static final class ArrayAccessor {
/// Support for array element access
static final int GETTER_INDEX = 0, SETTER_INDEX = 1, INDEX_LIMIT = 2;
/// Support for array element and length access
static final int GETTER_INDEX = 0, SETTER_INDEX = 1, LENGTH_INDEX = 2, INDEX_LIMIT = 3;
static final ClassValue<MethodHandle[]> TYPED_ACCESSORS
= new ClassValue<MethodHandle[]>() {
@Override
@ -107,14 +153,16 @@ import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP;
return new MethodHandle[INDEX_LIMIT];
}
};
static final MethodHandle OBJECT_ARRAY_GETTER, OBJECT_ARRAY_SETTER;
static final MethodHandle OBJECT_ARRAY_GETTER, OBJECT_ARRAY_SETTER, OBJECT_ARRAY_LENGTH;
static {
MethodHandle[] cache = TYPED_ACCESSORS.get(Object[].class);
cache[GETTER_INDEX] = OBJECT_ARRAY_GETTER = makeIntrinsic(getAccessor(Object[].class, false), Intrinsic.ARRAY_LOAD);
cache[SETTER_INDEX] = OBJECT_ARRAY_SETTER = makeIntrinsic(getAccessor(Object[].class, true), Intrinsic.ARRAY_STORE);
cache[GETTER_INDEX] = OBJECT_ARRAY_GETTER = makeIntrinsic(getAccessor(Object[].class, ArrayAccess.GET), Intrinsic.ARRAY_LOAD);
cache[SETTER_INDEX] = OBJECT_ARRAY_SETTER = makeIntrinsic(getAccessor(Object[].class, ArrayAccess.SET), Intrinsic.ARRAY_STORE);
cache[LENGTH_INDEX] = OBJECT_ARRAY_LENGTH = makeIntrinsic(getAccessor(Object[].class, ArrayAccess.LENGTH), Intrinsic.ARRAY_LENGTH);
assert(InvokerBytecodeGenerator.isStaticallyInvocable(ArrayAccessor.OBJECT_ARRAY_GETTER.internalMemberName()));
assert(InvokerBytecodeGenerator.isStaticallyInvocable(ArrayAccessor.OBJECT_ARRAY_SETTER.internalMemberName()));
assert(InvokerBytecodeGenerator.isStaticallyInvocable(ArrayAccessor.OBJECT_ARRAY_LENGTH.internalMemberName()));
}
static int getElementI(int[] a, int i) { return a[i]; }
@ -137,31 +185,47 @@ import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP;
static void setElementC(char[] a, int i, char x) { a[i] = x; }
static void setElementL(Object[] a, int i, Object x) { a[i] = x; }
static String name(Class<?> arrayClass, boolean isSetter) {
static int lengthI(int[] a) { return a.length; }
static int lengthJ(long[] a) { return a.length; }
static int lengthF(float[] a) { return a.length; }
static int lengthD(double[] a) { return a.length; }
static int lengthZ(boolean[] a) { return a.length; }
static int lengthB(byte[] a) { return a.length; }
static int lengthS(short[] a) { return a.length; }
static int lengthC(char[] a) { return a.length; }
static int lengthL(Object[] a) { return a.length; }
static String name(Class<?> arrayClass, ArrayAccess access) {
Class<?> elemClass = arrayClass.getComponentType();
if (elemClass == null) throw newIllegalArgumentException("not an array", arrayClass);
return (!isSetter ? "getElement" : "setElement") + Wrapper.basicTypeChar(elemClass);
return ArrayAccess.opName(access) + Wrapper.basicTypeChar(elemClass);
}
static MethodType type(Class<?> arrayClass, boolean isSetter) {
static MethodType type(Class<?> arrayClass, ArrayAccess access) {
Class<?> elemClass = arrayClass.getComponentType();
Class<?> arrayArgClass = arrayClass;
if (!elemClass.isPrimitive()) {
arrayArgClass = Object[].class;
elemClass = Object.class;
}
return !isSetter ?
MethodType.methodType(elemClass, arrayArgClass, int.class) :
MethodType.methodType(void.class, arrayArgClass, int.class, elemClass);
switch (access) {
case GET: return MethodType.methodType(elemClass, arrayArgClass, int.class);
case SET: return MethodType.methodType(void.class, arrayArgClass, int.class, elemClass);
case LENGTH: return MethodType.methodType(int.class, arrayArgClass);
}
throw new IllegalStateException("should not reach here");
}
static MethodType correctType(Class<?> arrayClass, boolean isSetter) {
static MethodType correctType(Class<?> arrayClass, ArrayAccess access) {
Class<?> elemClass = arrayClass.getComponentType();
return !isSetter ?
MethodType.methodType(elemClass, arrayClass, int.class) :
MethodType.methodType(void.class, arrayClass, int.class, elemClass);
switch (access) {
case GET: return MethodType.methodType(elemClass, arrayClass, int.class);
case SET: return MethodType.methodType(void.class, arrayClass, int.class, elemClass);
case LENGTH: return MethodType.methodType(int.class, arrayClass);
}
throw new IllegalStateException("should not reach here");
}
static MethodHandle getAccessor(Class<?> arrayClass, boolean isSetter) {
String name = name(arrayClass, isSetter);
MethodType type = type(arrayClass, isSetter);
static MethodHandle getAccessor(Class<?> arrayClass, ArrayAccess access) {
String name = name(arrayClass, access);
MethodType type = type(arrayClass, access);
try {
return IMPL_LOOKUP.findStatic(ArrayAccessor.class, name, type);
} catch (ReflectiveOperationException ex) {
@ -1282,6 +1346,7 @@ import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP;
NEW_ARRAY,
ARRAY_LOAD,
ARRAY_STORE,
ARRAY_LENGTH,
IDENTITY,
ZERO,
NONE // no intrinsic associated

View File

@ -2244,6 +2244,20 @@ return mh1;
return ani.asType(ani.type().changeReturnType(arrayClass));
}
/**
* Produces a method handle returning the length of an array.
* The type of the method handle will have {@code int} as return type,
* and its sole argument will be the array type.
* @param arrayClass an array type
* @return a method handle which can retrieve the length of an array of the given array type
* @throws NullPointerException if the argument is {@code null}
* @throws IllegalArgumentException if arrayClass is not an array type
*/
public static
MethodHandle arrayLength(Class<?> arrayClass) throws IllegalArgumentException {
return MethodHandleImpl.makeArrayElementAccessor(arrayClass, MethodHandleImpl.ArrayAccess.LENGTH);
}
/**
* Produces a method handle giving read access to elements of an array.
* The type of the method handle will have a return type of the array's
@ -2256,7 +2270,7 @@ return mh1;
*/
public static
MethodHandle arrayElementGetter(Class<?> arrayClass) throws IllegalArgumentException {
return MethodHandleImpl.makeArrayElementAccessor(arrayClass, false);
return MethodHandleImpl.makeArrayElementAccessor(arrayClass, MethodHandleImpl.ArrayAccess.GET);
}
/**
@ -2271,7 +2285,7 @@ return mh1;
*/
public static
MethodHandle arrayElementSetter(Class<?> arrayClass) throws IllegalArgumentException {
return MethodHandleImpl.makeArrayElementAccessor(arrayClass, true);
return MethodHandleImpl.makeArrayElementAccessor(arrayClass, MethodHandleImpl.ArrayAccess.SET);
}
/**

View File

@ -0,0 +1,81 @@
/*
* Copyright (c) 2016, 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.
*/
/* @test
* @run testng/othervm -ea -esa test.java.lang.invoke.ArrayLengthTest
*/
package test.java.lang.invoke;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import static org.testng.AssertJUnit.*;
import org.testng.annotations.*;
public class ArrayLengthTest {
@DataProvider
Object[][] arrayClasses() {
return new Object[][] {
{int[].class},
{long[].class},
{float[].class},
{double[].class},
{boolean[].class},
{byte[].class},
{short[].class},
{char[].class},
{Object[].class},
{StringBuffer[].class}
};
}
@Test(dataProvider = "arrayClasses")
public void testArrayLength(Class<?> arrayClass) throws Throwable {
MethodHandle arrayLength = MethodHandles.arrayLength(arrayClass);
assertEquals(int.class, arrayLength.type().returnType());
assertEquals(arrayClass, arrayLength.type().parameterType(0));
Object array = MethodHandles.arrayConstructor(arrayClass).invoke(10);
assertEquals(10, arrayLength.invoke(array));
}
@Test(dataProvider = "arrayClasses", expectedExceptions = NullPointerException.class)
public void testArrayLengthInvokeNPE(Class<?> arrayClass) throws Throwable {
MethodHandle arrayLength = MethodHandles.arrayLength(arrayClass);
arrayLength.invoke(null);
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testArrayLengthNoArray() {
MethodHandles.arrayLength(String.class);
}
@Test(expectedExceptions = NullPointerException.class)
public void testArrayLengthNPE() {
MethodHandles.arrayLength(null);
}
}