提交 715ab60a 编写于 作者: J jrose

8185993: MethodHandle.invokeWithArguments jumbo-arity

Reviewed-by: psandoz, vlivanov
上级 e120cd7f
......@@ -344,13 +344,21 @@ public class CallSite {
break;
default:
final int NON_SPREAD_ARG_COUNT = 3; // (caller, name, type)
if (NON_SPREAD_ARG_COUNT + argv.length > MethodType.MAX_MH_ARITY)
throw new BootstrapMethodError("too many bootstrap method arguments");
MethodType invocationType = MethodType.genericMethodType(NON_SPREAD_ARG_COUNT + argv.length);
MethodHandle typedBSM = bootstrapMethod.asType(invocationType);
MethodHandle spreader = invocationType.invokers().spreadInvoker(NON_SPREAD_ARG_COUNT);
binding = spreader.invokeExact(typedBSM, (Object) caller, (Object) name, (Object) type, argv);
final int MAX_SAFE_SIZE = MethodType.MAX_MH_ARITY / 2 - NON_SPREAD_ARG_COUNT;
if (argv.length >= MAX_SAFE_SIZE) {
// to be on the safe side, use invokeWithArguments which handles jumbo lists
Object[] newargv = new Object[NON_SPREAD_ARG_COUNT + argv.length];
newargv[0] = caller;
newargv[1] = name;
newargv[2] = type;
System.arraycopy(argv, 0, newargv, NON_SPREAD_ARG_COUNT, argv.length);
binding = bootstrapMethod.invokeWithArguments(newargv);
} else {
MethodType invocationType = MethodType.genericMethodType(NON_SPREAD_ARG_COUNT + argv.length);
MethodHandle typedBSM = bootstrapMethod.asType(invocationType);
MethodHandle spreader = invocationType.invokers().spreadInvoker(NON_SPREAD_ARG_COUNT);
binding = spreader.invokeExact(typedBSM, (Object) caller, (Object) name, (Object) type, argv);
}
}
}
if (binding instanceof CallSite) {
......
......@@ -584,10 +584,10 @@ public abstract class MethodHandle {
/*non-public*/ static native @PolymorphicSignature Object linkToInterface(Object... args) throws Throwable;
/**
* Performs a variable arity invocation, passing the arguments in the given list
* Performs a variable arity invocation, passing the arguments in the given array
* to the method handle, as if via an inexact {@link #invoke invoke} from a call site
* which mentions only the type {@code Object}, and whose arity is the length
* of the argument list.
* which mentions only the type {@code Object}, and whose actual argument count is the length
* of the argument array.
* <p>
* Specifically, execution proceeds as if by the following steps,
* although the methods are not guaranteed to be called if the JVM
......@@ -595,36 +595,104 @@ public abstract class MethodHandle {
* <ul>
* <li>Determine the length of the argument array as {@code N}.
* For a null reference, {@code N=0}. </li>
* <li>Determine the general type {@code TN} of {@code N} arguments as
* as {@code TN=MethodType.genericMethodType(N)}.</li>
* <li>Collect the {@code N} elements of the array as a logical
* argument list, each argument statically typed as an {@code Object}. </li>
* <li>Determine, as {@code M}, the parameter count of the type of this
* method handle. </li>
* <li>Determine the general type {@code TN} of {@code N} arguments or
* {@code M} arguments, if smaller than {@code N}, as
* {@code TN=MethodType.genericMethodType(Math.min(N, M))}.</li>
* <li>If {@code N} is greater than {@code M}, perform the following
* checks and actions to shorten the logical argument list: <ul>
* <li>Check that this method handle has variable arity with a
* {@linkplain MethodType#lastParameterType trailing parameter}
* of some array type {@code A[]}. If not, fail with a
* {@code WrongMethodTypeException}. </li>
* <li>Collect the trailing elements (there are {@code N-M+1} of them)
* from the logical argument list into a single array of
* type {@code A[]}, using {@code asType} conversions to
* convert each trailing argument to type {@code A}. </li>
* <li>If any of these conversions proves impossible, fail with either
* a {@code ClassCastException} if any trailing element cannot be
* cast to {@code A} or a {@code NullPointerException} if any
* trailing element is {@code null} and {@code A} is not a reference
* type. </li>
* <li>Replace the logical arguments gathered into the array of
* type {@code A[]} with the array itself, thus shortening
* the argument list to length {@code M}. This final argument
* retains the static type {@code A[]}.</li>
* <li>Adjust the type {@code TN} by changing the {@code N}th
* parameter type from {@code Object} to {@code A[]}.
* </ul>
* <li>Force the original target method handle {@code MH0} to the
* required type, as {@code MH1 = MH0.asType(TN)}. </li>
* <li>Spread the array into {@code N} separate arguments {@code A0, ...}. </li>
* <li>Spread the argument list into {@code N} separate arguments {@code A0, ...}. </li>
* <li>Invoke the type-adjusted method handle on the unpacked arguments:
* MH1.invokeExact(A0, ...). </li>
* <li>Take the return value as an {@code Object} reference. </li>
* </ul>
* <p>
* If the target method handle has variable arity, and the argument list is longer
* than that arity, the excess arguments, starting at the position of the trailing
* array argument, will be gathered (if possible, as if by {@code asType} conversions)
* into an array of the appropriate type, and invocation will proceed on the
* shortened argument list.
* In this way, <em>jumbo argument lists</em> which would spread into more
* than 254 slots can still be processed uniformly.
* <p>
* Unlike the {@link #invoke(Object...) generic} invocation mode, which can
* "recycle" an array argument, passing it directly to the target method,
* this invocation mode <em>always</em> creates a new array parameter, even
* if the original array passed to {@code invokeWithArguments} would have
* been acceptable as a direct argument to the target method.
* Even if the number {@code M} of actual arguments is the arity {@code N},
* and the last argument is dynamically a suitable array of type {@code A[]},
* it will still be boxed into a new one-element array, since the call
* site statically types the argument as {@code Object}, not an array type.
* This is not a special rule for this method, but rather a regular effect
* of the {@linkplain #asVarargsCollector rules for variable-arity invocation}.
* <p>
* Because of the action of the {@code asType} step, the following argument
* conversions are applied as necessary:
* <ul>
* <li>reference casting
* <li>unboxing
* <li>widening primitive conversions
* <li>variable arity conversion
* </ul>
* <p>
* The result returned by the call is boxed if it is a primitive,
* or forced to null if the return type is void.
* <p>
* This call is equivalent to the following code:
* Unlike the signature polymorphic methods {@code invokeExact} and {@code invoke},
* {@code invokeWithArguments} can be accessed normally via the Core Reflection API and JNI.
* It can therefore be used as a bridge between native or reflective code and method handles.
* @apiNote
* This call is approximately equivalent to the following code:
* <blockquote><pre>{@code
* // for jumbo argument lists, adapt varargs explicitly:
* int N = (arguments == null? 0: arguments.length);
* int M = this.type.parameterCount();
* int MAX_SAFE = 127; // 127 longs require 254 slots, which is OK
* if (N > MAX_SAFE && N > M && this.isVarargsCollector()) {
* Class<?> arrayType = this.type().lastParameterType();
* Class<?> elemType = arrayType.getComponentType();
* if (elemType != null) {
* Object args2 = Array.newInstance(elemType, M);
* MethodHandle arraySetter = MethodHandles.arrayElementSetter(arrayType);
* for (int i = 0; i < M; i++) {
* arraySetter.invoke(args2, i, arguments[M-1 + i]);
* }
* arguments = Arrays.copyOf(arguments, M);
* arguments[M-1] = args2;
* return this.asFixedArity().invokeWithArguments(arguments);
* }
* } // done with explicit varargs processing
*
* // Handle fixed arity and non-jumbo variable arity invocation.
* MethodHandle invoker = MethodHandles.spreadInvoker(this.type(), 0);
* Object result = invoker.invokeExact(this, arguments);
* }</pre></blockquote>
* <p>
* Unlike the signature polymorphic methods {@code invokeExact} and {@code invoke},
* {@code invokeWithArguments} can be accessed normally via the Core Reflection API and JNI.
* It can therefore be used as a bridge between native or reflective code and method handles.
*
* @param arguments the arguments to pass to the target
* @return the result returned by the target
......@@ -634,20 +702,24 @@ public abstract class MethodHandle {
* @see MethodHandles#spreadInvoker
*/
public Object invokeWithArguments(Object... arguments) throws Throwable {
// Note: Jumbo argument lists are handled in the variable-arity subclass.
MethodType invocationType = MethodType.genericMethodType(arguments == null ? 0 : arguments.length);
return invocationType.invokers().spreadInvoker(0).invokeExact(asType(invocationType), arguments);
}
/**
* Performs a variable arity invocation, passing the arguments in the given array
* Performs a variable arity invocation, passing the arguments in the given list
* to the method handle, as if via an inexact {@link #invoke invoke} from a call site
* which mentions only the type {@code Object}, and whose arity is the length
* of the argument array.
* which mentions only the type {@code Object}, and whose actual argument count is the length
* of the argument list.
* <p>
* This method is also equivalent to the following code:
* <blockquote><pre>{@code
* invokeWithArguments(arguments.toArray())
* }</pre></blockquote>
* <p>
* Jumbo-sized lists are acceptable if this method handle has variable arity.
* See {@link #invokeWithArguments(Object[])} for details.
*
* @param arguments the arguments to pass to the target
* @return the result returned by the target
......@@ -987,6 +1059,16 @@ assertEquals("[A, B, C]", (String) caToString2.invokeExact('A', "BC".toCharArray
* disturbing its variable arity property:
* {@code mh.asType(mh.type().changeParameterType(0,int.class))
* .withVarargs(mh.isVarargsCollector())}
* <p>
* This call is approximately equivalent to the following code:
* <blockquote><pre>{@code
* if (makeVarargs == isVarargsCollector())
* return this;
* else if (makeVarargs)
* return asVarargsCollector(type().lastParameterType());
* else
* return return asFixedArity();
* }</pre></blockquote>
* @param makeVarargs true if the return method handle should have variable arity behavior
* @return a method handle of the same type, with possibly adjusted variable arity behavior
* @throws IllegalArgumentException if {@code makeVarargs} is true and
......@@ -995,11 +1077,10 @@ assertEquals("[A, B, C]", (String) caToString2.invokeExact('A', "BC".toCharArray
* @see #asVarargsCollector
* @see #asFixedArity
*/
public MethodHandle withVarargs(boolean makeVarargs) {
if (!makeVarargs) {
return asFixedArity();
} else if (!isVarargsCollector()) {
return asVarargsCollector(type().lastParameterType());
public MethodHandle withVarargs(boolean makeVarargs) {
assert(!isVarargsCollector()); // subclass responsibility
if (makeVarargs) {
return asVarargsCollector(type().lastParameterType());
} else {
return this;
}
......@@ -1026,8 +1107,9 @@ assertEquals("[A, B, C]", (String) caToString2.invokeExact('A', "BC".toCharArray
* <p>
* (The array may also be a shared constant when {@code arrayLength} is zero.)
* <p>
* (<em>Note:</em> The {@code arrayType} is often identical to the last
* parameter type of the original target.
* (<em>Note:</em> The {@code arrayType} is often identical to the
* {@linkplain MethodType#lastParameterType last parameter type}
* of the original target.
* It is an explicit argument for symmetry with {@code asSpreader}, and also
* to allow the target to use a simple {@code Object} as its last parameter type.)
* <p>
......@@ -1168,7 +1250,9 @@ assertEquals("[123]", (String) longsToString.invokeExact((long)123));
* {@code invoke} and {@code asType} requests can lead to
* trailing positional arguments being collected into target's
* trailing parameter.
* Also, the last parameter type of the adapter will be
* Also, the
* {@linkplain MethodType#lastParameterType last parameter type}
* of the adapter will be
* {@code arrayType}, even if the target has a different
* last parameter type.
* <p>
......
......@@ -523,6 +523,12 @@ import static jdk.internal.org.objectweb.asm.Opcodes.*;
return asFixedArity();
}
@Override
public MethodHandle withVarargs(boolean makeVarargs) {
if (makeVarargs) return this;
return asFixedArity();
}
@Override
public MethodHandle asTypeUncached(MethodType newType) {
MethodType type = this.type();
......@@ -561,6 +567,49 @@ import static jdk.internal.org.objectweb.asm.Opcodes.*;
: Arrays.asList(this, newType);
return true;
}
@Override
public Object invokeWithArguments(Object... arguments) throws Throwable {
MethodType type = this.type();
int argc;
final int MAX_SAFE = 127; // 127 longs require 254 slots, which is safe to spread
if (arguments == null
|| (argc = arguments.length) <= MAX_SAFE
|| argc < type.parameterCount()) {
return super.invokeWithArguments(arguments);
}
// a jumbo invocation requires more explicit reboxing of the trailing arguments
int uncollected = type.parameterCount() - 1;
Class<?> elemType = arrayType.getComponentType();
int collected = argc - uncollected;
Object collArgs = (elemType == Object.class)
? new Object[collected] : Array.newInstance(elemType, collected);
if (!elemType.isPrimitive()) {
// simple cast: just do some casting
try {
System.arraycopy(arguments, uncollected, collArgs, 0, collected);
} catch (ArrayStoreException ex) {
return super.invokeWithArguments(arguments);
}
} else {
// corner case of flat array requires reflection (or specialized copy loop)
MethodHandle arraySetter = MethodHandles.arrayElementSetter(arrayType);
try {
for (int i = 0; i < collected; i++) {
arraySetter.invoke(collArgs, i, arguments[uncollected + i]);
}
} catch (WrongMethodTypeException|ClassCastException ex) {
return super.invokeWithArguments(arguments);
}
}
// chop the jumbo list down to size and call in non-varargs mode
Object[] newArgs = new Object[uncollected + 1];
System.arraycopy(arguments, 0, newArgs, 0, uncollected);
newArgs[uncollected] = collArgs;
return asFixedArity().invokeWithArguments(newArgs);
}
}
/** Factory method: Spread selected argument. */
......
/*
* Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2008, 2017, 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
......@@ -759,7 +759,23 @@ class MethodType implements java.io.Serializable {
return Collections.unmodifiableList(Arrays.asList(ptypes.clone()));
}
/*non-public*/ Class<?> lastParameterType() {
/**
* Returns the last parameter type of this method type.
* If this type has no parameters, the sentinel value
* {@code void.class} is returned instead.
* @apiNote
* <p>
* The sentinel value is chosen so that reflective queries can be
* made directly against the result value.
* The sentinel value cannot be confused with a real parameter,
* since {@code void} is never acceptable as a parameter type.
* For variable arity invocation modes, the expression
* {@link Class#getComponentType lastParameterType().getComponentType()}
* is useful to query the type of the "varargs" parameter.
* @return the last parameter type if any, else {@code void.class}
* @since 10
*/
public Class<?> lastParameterType() {
int len = ptypes.length;
return len == 0 ? void.class : ptypes[len-1];
}
......
......@@ -81,12 +81,19 @@
* in which dynamic call site occurs </li>
* <li>a {@code String}, the method name mentioned in the call site </li>
* <li>a {@code MethodType}, the resolved type descriptor of the call </li>
* <li>optionally, between 1 and 251 additional static arguments taken from the constant pool </li>
* <li>optionally, any number of additional static arguments taken from the constant pool </li>
* </ul>
* Invocation is as if by
* {@link java.lang.invoke.MethodHandle#invoke MethodHandle.invoke}.
* The returned result must be a {@link java.lang.invoke.CallSite CallSite}
* (or a subclass), otherwise a
* <p>
* In all cases, bootstrap method invocation is as if by
* {@link java.lang.invoke.MethodHandle#invokeWithArguments MethodHandle.invokeWithArguments},
* (This is also equivalent to
* {@linkplain java.lang.invoke.MethodHandle#invoke generic invocation}
* if the number of arguments is small enough.)
* <p>
* For an {@code invokedynamic} instruction, the
* returned result must be convertible to a non-null reference to a
* {@link java.lang.invoke.CallSite CallSite}.
* If the returned result cannot be converted to the expected type,
* {@link java.lang.BootstrapMethodError BootstrapMethodError} is thrown.
* The type of the call site's target must be exactly equal to the type
* derived from the dynamic call site's type descriptor and passed to
......@@ -150,10 +157,12 @@
* If the {@code invokedynamic} instruction specifies one or more static arguments,
* those values will be passed as additional arguments to the method handle.
* (Note that because there is a limit of 255 arguments to any method,
* at most 251 extra arguments can be supplied, since the bootstrap method
* at most 251 extra arguments can be supplied to a non-varargs bootstrap method,
* since the bootstrap method
* handle itself and its first three arguments must also be stacked.)
* The bootstrap method will be invoked as if by either {@code MethodHandle.invoke}
* or {@code invokeWithArguments}. (There is no way to tell the difference.)
* The bootstrap method will be invoked as if by {@code MethodHandle.invokeWithArguments}.
* A variable-arity bootstrap method can accept thousands of static arguments,
* subject only by limits imposed by the class-file format.
* <p>
* The normal argument conversion rules for {@code MethodHandle.invoke} apply to all stacked arguments.
* For example, if a pushed value is a primitive type, it may be converted to a reference by boxing conversion.
......@@ -194,9 +203,9 @@
* </tbody>
* </table>
* The last example assumes that the extra arguments are of type
* {@code CONSTANT_String} and {@code CONSTANT_Integer}, respectively.
* {@code String} and {@code Integer} (or {@code int}), respectively.
* The second-to-last example assumes that all extra arguments are of type
* {@code CONSTANT_String}.
* {@code String}.
* The other examples work with all types of extra arguments.
* <p>
* As noted above, the actual method type of the bootstrap method can vary.
......@@ -220,7 +229,7 @@
* to safely and compactly encode metadata.
* In principle, the name and extra arguments are redundant,
* since each call site could be given its own unique bootstrap method.
* Such a practice is likely to produce large class files and constant pools.
* Such a practice would be likely to produce large class files and constant pools.
*
* @author John Rose, JSR 292 EG
* @since 1.7
......
/*
* Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 2017, 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
......@@ -22,13 +22,15 @@
*/
/* @test
* @summary High arity invocations, up to the maximum of 255 arguments
* @summary High arity invocations
* @compile BigArityTest.java
* @run junit/othervm/timeout=2500 -XX:+IgnoreUnrecognizedVMOptions -XX:-VerifyDependencies -esa -DBigArityTest.ITERATION_COUNT=1 test.java.lang.invoke.BigArityTest
*/
package test.java.lang.invoke;
import org.junit.Test;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
......@@ -37,8 +39,8 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Objects;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
public class BigArityTest {
......@@ -63,12 +65,70 @@ public class BigArityTest {
static Object hashArguments(Object... args) {
return Objects.hash(args);
}
static Object hashArguments(int... args) {
Object[] wrappedArgs = new Object[args.length];
for (int i = 0; i < args.length; i++) {
wrappedArgs[i] = args[i];
}
return hashArguments(wrappedArgs);
}
static Object hashArguments(long... args) {
Object[] wrappedArgs = new Object[args.length];
for (int i = 0; i < args.length; i++) {
wrappedArgs[i] = (int) args[i];
}
return hashArguments(wrappedArgs);
}
static Object hashArguments1(Object o, Object... args) {
Object[] arr = new Object[args.length + 1];
arr[0] = 0;
System.arraycopy(args, 0, arr, 1, args.length);
return Objects.hash(arr);
}
static Object hashArguments1(int i0, int... args) {
Object[] wrappedArgs = new Object[args.length + 1];
wrappedArgs[0] = i0;
for (int i = 0; i < args.length; i++) {
wrappedArgs[i + 1] = args[i];
}
return hashArguments(wrappedArgs);
}
static Object hashArguments1(long l0, long... args) {
Object[] wrappedArgs = new Object[args.length + 1];
wrappedArgs[0] = l0;
for (int i = 0; i < args.length; i++) {
wrappedArgs[i + 1] = (int) args[i];
}
return hashArguments(wrappedArgs);
}
static final MethodHandle MH_hashArguments_VA;
static final MethodHandle MH_hashArguments_IVA;
static final MethodHandle MH_hashArguments_JVA;
static final MethodHandle MH_hashArguments1_VA;
static final MethodHandle MH_hashArguments1_IVA;
static final MethodHandle MH_hashArguments1_JVA;
static {
try {
MH_hashArguments_VA =
MethodHandles.lookup().unreflect(
BigArityTest.class.getDeclaredMethod("hashArguments", Object[].class));
MH_hashArguments_IVA =
MethodHandles.lookup().unreflect(
BigArityTest.class.getDeclaredMethod("hashArguments", int[].class));
MH_hashArguments_JVA =
MethodHandles.lookup().unreflect(
BigArityTest.class.getDeclaredMethod("hashArguments", long[].class));
MH_hashArguments1_VA =
MethodHandles.lookup().unreflect(
BigArityTest.class.getDeclaredMethod("hashArguments1", Object.class, Object[].class));
MH_hashArguments1_IVA =
MethodHandles.lookup().unreflect(
BigArityTest.class.getDeclaredMethod("hashArguments1", int.class, int[].class));
MH_hashArguments1_JVA =
MethodHandles.lookup().unreflect(
BigArityTest.class.getDeclaredMethod("hashArguments1", long.class, long[].class));
} catch (ReflectiveOperationException ex) {
throw new Error(ex);
}
......@@ -345,6 +405,28 @@ public class BigArityTest {
assertEquals("arity=MAX_ARITY", r0, r);
}
@Test
public void testInvokeWithArgumentsJumbo() throws Throwable {
System.out.println("testing invokeWithArguments on jumbo arities");
ArrayList<Integer> arities = new ArrayList<>();
for (int arity = 125; arity < 1000; arity += (arity < MAX_ARITY+10 ? 1 : arity/7)) {
arities.add(arity);
Object[] args = testArgs(arity);
Object r0 = Objects.hash(args);
assertEquals("jumbo arity="+arity, r0, MH_hashArguments_VA.invokeWithArguments(args));
assertEquals("jumbo arity="+arity, r0, MH_hashArguments1_VA.invokeWithArguments(args));
// use primitive typed argument lists also:
assertEquals("jumbo int arity="+arity, r0, MH_hashArguments_IVA.invokeWithArguments(args));
assertEquals("jumbo int arity="+arity, r0, MH_hashArguments1_IVA.invokeWithArguments(args));
assertEquals("jumbo long arity="+arity, r0, MH_hashArguments_JVA.invokeWithArguments(args));
assertEquals("jumbo long arity="+arity, r0, MH_hashArguments1_JVA.invokeWithArguments(args));
}
System.out.println(" jumbo arities = "+arities);
}
static Object[] cat(Object a, Object[] b) {
int alen = 1, blen = b.length;
Object[] c = new Object[alen + blen];
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册