|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
package java.lang.invoke; |
|
|
|
import sun.invoke.util.Wrapper; |
|
|
|
import java.lang.invoke.AbstractConstantGroup.BSCIWithCache; |
|
import java.util.Arrays; |
|
|
|
import static java.lang.invoke.BootstrapCallInfo.makeBootstrapCallInfo; |
|
import static java.lang.invoke.ConstantGroup.makeConstantGroup; |
|
import static java.lang.invoke.MethodHandleNatives.*; |
|
import static java.lang.invoke.MethodHandleStatics.TRACE_METHOD_LINKAGE; |
|
import static java.lang.invoke.MethodHandles.Lookup; |
|
import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP; |
|
|
|
final class BootstrapMethodInvoker { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
static <T> T invoke(Class<T> resultType, |
|
MethodHandle bootstrapMethod, |
|
|
|
String name, Object type, |
|
|
|
Object info, |
|
|
|
Class<?> callerClass) { |
|
MethodHandles.Lookup caller = IMPL_LOOKUP.in(callerClass); |
|
Object result; |
|
boolean pullMode = isPullModeBSM(bootstrapMethod); |
|
boolean vmIsPushing = !staticArgumentsPulled(info); |
|
MethodHandle pullModeBSM; |
|
|
|
if (vmIsPushing) { |
|
|
|
pullModeBSM = null; |
|
if (pullMode) { |
|
bootstrapMethod = pushMePullYou(bootstrapMethod, true); |
|
} |
|
} else { |
|
|
|
pullModeBSM = pullMode ? bootstrapMethod : |
|
pushMePullYou(bootstrapMethod, false); |
|
bootstrapMethod = null; |
|
} |
|
try { |
|
// As an optimization we special case various known BSMs, |
|
// such as LambdaMetafactory::metafactory and |
|
// StringConcatFactory::makeConcatWithConstants. |
|
// |
|
// By providing static type information or even invoking |
|
// exactly, we avoid emitting code to perform runtime |
|
|
|
if (info == null) { |
|
|
|
if (type instanceof Class<?> c) { |
|
result = bootstrapMethod.invoke(caller, name, c); |
|
} else { |
|
result = bootstrapMethod.invoke(caller, name, (MethodType)type); |
|
} |
|
} |
|
else if (!info.getClass().isArray()) { |
|
// VM is allowed to pass up a single BSM arg directly |
|
|
|
// Call to StringConcatFactory::makeConcatWithConstants |
|
|
|
if (isStringConcatFactoryBSM(bootstrapMethod.type())) { |
|
result = (CallSite)bootstrapMethod |
|
.invokeExact(caller, name, (MethodType)type, |
|
(String)info, new Object[0]); |
|
} else { |
|
info = maybeReBox(info); |
|
if (type instanceof Class<?> c) { |
|
result = bootstrapMethod.invoke(caller, name, c, info); |
|
} else { |
|
result = bootstrapMethod.invoke(caller, name, (MethodType)type, info); |
|
} |
|
} |
|
} |
|
else if (info.getClass() == int[].class) { |
|
// VM is allowed to pass up a pair {argc, index} |
|
// referring to 'argc' BSM args at some place 'index' |
|
// in the guts of the VM (associated with callerClass). |
|
// The format of this index pair is private to the |
|
// handshake between the VM and this class only. |
|
// This supports "pulling" of arguments. |
|
// The VM is allowed to do this for any reason. |
|
|
|
BootstrapCallInfo<Object> bsci |
|
= new VM_BSCI<>(bootstrapMethod, name, type, caller, (int[])info); |
|
|
|
result = pullModeBSM.invoke(caller, bsci); |
|
} |
|
else { |
|
|
|
Object[] argv = (Object[]) info; |
|
|
|
MethodType bsmType = bootstrapMethod.type(); |
|
if (isLambdaMetafactoryIndyBSM(bsmType) && argv.length == 3) { |
|
result = (CallSite)bootstrapMethod |
|
.invokeExact(caller, name, (MethodType)type, (MethodType)argv[0], |
|
(MethodHandle)argv[1], (MethodType)argv[2]); |
|
} else if (isLambdaMetafactoryCondyBSM(bsmType) && argv.length == 3) { |
|
result = bootstrapMethod |
|
.invokeExact(caller, name, (Class<?>)type, (MethodType)argv[0], |
|
(MethodHandle)argv[1], (MethodType)argv[2]); |
|
} else if (isStringConcatFactoryBSM(bsmType) && argv.length >= 1) { |
|
String recipe = (String)argv[0]; |
|
Object[] shiftedArgs = Arrays.copyOfRange(argv, 1, argv.length); |
|
maybeReBoxElements(shiftedArgs); |
|
result = (CallSite)bootstrapMethod.invokeExact(caller, name, (MethodType)type, recipe, shiftedArgs); |
|
} else if (isLambdaMetafactoryAltMetafactoryBSM(bsmType)) { |
|
maybeReBoxElements(argv); |
|
result = (CallSite)bootstrapMethod.invokeExact(caller, name, (MethodType)type, argv); |
|
} else { |
|
maybeReBoxElements(argv); |
|
if (type instanceof Class<?> c) { |
|
result = switch (argv.length) { |
|
case 0 -> bootstrapMethod.invoke(caller, name, c); |
|
case 1 -> bootstrapMethod.invoke(caller, name, c, argv[0]); |
|
case 2 -> bootstrapMethod.invoke(caller, name, c, argv[0], argv[1]); |
|
case 3 -> bootstrapMethod.invoke(caller, name, c, argv[0], argv[1], argv[2]); |
|
case 4 -> bootstrapMethod.invoke(caller, name, c, argv[0], argv[1], argv[2], argv[3]); |
|
case 5 -> bootstrapMethod.invoke(caller, name, c, argv[0], argv[1], argv[2], argv[3], argv[4]); |
|
case 6 -> bootstrapMethod.invoke(caller, name, c, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5]); |
|
default -> invokeWithManyArguments(bootstrapMethod, caller, name, type, argv); |
|
}; |
|
} else { |
|
MethodType mt = (MethodType) type; |
|
result = switch (argv.length) { |
|
case 0 -> bootstrapMethod.invoke(caller, name, mt); |
|
case 1 -> bootstrapMethod.invoke(caller, name, mt, argv[0]); |
|
case 2 -> bootstrapMethod.invoke(caller, name, mt, argv[0], argv[1]); |
|
case 3 -> bootstrapMethod.invoke(caller, name, mt, argv[0], argv[1], argv[2]); |
|
case 4 -> bootstrapMethod.invoke(caller, name, mt, argv[0], argv[1], argv[2], argv[3]); |
|
case 5 -> bootstrapMethod.invoke(caller, name, mt, argv[0], argv[1], argv[2], argv[3], argv[4]); |
|
case 6 -> bootstrapMethod.invoke(caller, name, mt, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5]); |
|
default -> invokeWithManyArguments(bootstrapMethod, caller, name, type, argv); |
|
}; |
|
} |
|
} |
|
} |
|
return widenAndCast(result, resultType); |
|
} |
|
catch (Error e) { |
|
// Pass through an Error, including BootstrapMethodError, any other |
|
// form of linkage error, such as IllegalAccessError if the bootstrap |
|
// method is inaccessible, or say ThreadDeath/OutOfMemoryError |
|
// See the "Linking Exceptions" section for the invokedynamic |
|
|
|
throw e; |
|
} |
|
catch (Throwable ex) { |
|
|
|
throw new BootstrapMethodError("bootstrap method initialization exception", ex); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
static <T> T widenAndCast(Object result, Class<T> resultType) throws Throwable { |
|
if (!resultType.isPrimitive()) { |
|
return resultType.cast(result); |
|
} |
|
|
|
Class<T> wrapperType = Wrapper.asWrapperType(resultType); |
|
if (wrapperType.isInstance(result)) { |
|
@SuppressWarnings("unchecked") |
|
T wrapper = (T) result; |
|
return wrapper; |
|
} |
|
// Non-reference conversions are more than just plain casts. |
|
// By pushing the value through a funnel of the form (T x)->x, |
|
// the boxed result can be widened as needed. See MH::asType. |
|
|
|
MethodHandle funnel = MethodHandles.identity(resultType); |
|
result = funnel.invoke(result); |
|
|
|
return wrapperType.cast(result); |
|
} |
|
|
|
private static Object invokeWithManyArguments(MethodHandle bootstrapMethod, Lookup caller, |
|
String name, Object type, Object[] argv) throws Throwable { |
|
final int NON_SPREAD_ARG_COUNT = 3; |
|
final int MAX_SAFE_SIZE = MethodType.MAX_MH_ARITY / 2 - NON_SPREAD_ARG_COUNT; |
|
if (argv.length >= MAX_SAFE_SIZE) { |
|
|
|
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); |
|
return 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); |
|
return spreader.invokeExact(typedBSM, (Object) caller, (Object) name, type, argv); |
|
} |
|
} |
|
|
|
private static final MethodType LMF_INDY_MT = MethodType.methodType(CallSite.class, |
|
Lookup.class, String.class, MethodType.class, MethodType.class, MethodHandle.class, MethodType.class); |
|
|
|
private static final MethodType LMF_ALT_MT = MethodType.methodType(CallSite.class, |
|
Lookup.class, String.class, MethodType.class, Object[].class); |
|
|
|
private static final MethodType LMF_CONDY_MT = MethodType.methodType(Object.class, |
|
Lookup.class, String.class, Class.class, MethodType.class, MethodHandle.class, MethodType.class); |
|
|
|
private static final MethodType SCF_MT = MethodType.methodType(CallSite.class, |
|
Lookup.class, String.class, MethodType.class, String.class, Object[].class); |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static boolean isStringConcatFactoryBSM(MethodType bsmType) { |
|
return bsmType == SCF_MT; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static boolean isLambdaMetafactoryCondyBSM(MethodType bsmType) { |
|
return bsmType == LMF_CONDY_MT; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static boolean isLambdaMetafactoryIndyBSM(MethodType bsmType) { |
|
return bsmType == LMF_INDY_MT; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static boolean isLambdaMetafactoryAltMetafactoryBSM(MethodType bsmType) { |
|
return bsmType == LMF_ALT_MT; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static Object maybeReBox(Object x) { |
|
if (x instanceof Integer) { |
|
int xi = (int) x; |
|
if (xi == (byte) xi) |
|
x = xi; |
|
} |
|
return x; |
|
} |
|
|
|
private static void maybeReBoxElements(Object[] xa) { |
|
for (int i = 0; i < xa.length; i++) { |
|
xa[i] = maybeReBox(xa[i]); |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
private static final class VM_BSCI<T> extends BSCIWithCache<T> { |
|
private final int[] indexInfo; |
|
private final Class<?> caller; |
|
|
|
VM_BSCI(MethodHandle bsm, String name, T type, |
|
Lookup lookup, int[] indexInfo) { |
|
super(bsm, name, type, indexInfo[0]); |
|
if (!lookup.hasFullPrivilegeAccess()) |
|
throw new AssertionError("bad Lookup object"); |
|
this.caller = lookup.lookupClass(); |
|
this.indexInfo = indexInfo; |
|
|
|
prefetchIntoCache(0, size()); |
|
} |
|
|
|
@Override Object fillCache(int i) { |
|
Object[] buf = { null }; |
|
copyConstants(i, i+1, buf, 0); |
|
Object res = wrapNull(buf[0]); |
|
cache[i] = res; |
|
int next = i + 1; |
|
if (next < cache.length && cache[next] == null) |
|
maybePrefetchIntoCache(next, false); |
|
return res; |
|
} |
|
|
|
@Override public int copyConstants(int start, int end, |
|
Object[] buf, int pos) { |
|
int i = start, bufi = pos; |
|
while (i < end) { |
|
Object x = cache[i]; |
|
if (x == null) break; |
|
buf[bufi++] = unwrapNull(x); |
|
i++; |
|
} |
|
|
|
if (i >= end) return i; |
|
Object[] temp = new Object[end - i]; |
|
if (TRACE_METHOD_LINKAGE) { |
|
System.out.println("resolving more BSM arguments: " + |
|
Arrays.asList(caller.getSimpleName(), Arrays.toString(indexInfo), i, end)); |
|
} |
|
copyOutBootstrapArguments(caller, indexInfo, |
|
i, end, temp, 0, |
|
true, null); |
|
for (Object x : temp) { |
|
x = maybeReBox(x); |
|
buf[bufi++] = x; |
|
cache[i++] = wrapNull(x); |
|
} |
|
if (end < cache.length && cache[end] == null) |
|
maybePrefetchIntoCache(end, true); |
|
return i; |
|
} |
|
|
|
private static final int MIN_PF = 4; |
|
private void maybePrefetchIntoCache(int i, boolean bulk) { |
|
int len = cache.length; |
|
assert(0 <= i && i <= len); |
|
int pfLimit = i; |
|
if (bulk) pfLimit += i; |
|
|
|
if (pfLimit < i + MIN_PF) pfLimit = i + MIN_PF; |
|
if (pfLimit > len || pfLimit < 0) pfLimit = len; |
|
|
|
int empty = 0, nonEmpty = 0, lastEmpty = i; |
|
for (int j = i; j < pfLimit; j++) { |
|
if (cache[j] == null) { |
|
empty++; |
|
lastEmpty = j; |
|
} else { |
|
nonEmpty++; |
|
if (nonEmpty > empty) { |
|
pfLimit = lastEmpty + 1; |
|
break; |
|
} |
|
if (pfLimit < len) pfLimit++; |
|
} |
|
} |
|
if (bulk && empty < MIN_PF && pfLimit < len) |
|
return; |
|
prefetchIntoCache(i, pfLimit); |
|
} |
|
|
|
private void prefetchIntoCache(int i, int pfLimit) { |
|
if (pfLimit <= i) return; |
|
Object[] temp = new Object[pfLimit - i]; |
|
if (TRACE_METHOD_LINKAGE) { |
|
System.out.println("prefetching BSM arguments: " + |
|
Arrays.asList(caller.getSimpleName(), Arrays.toString(indexInfo), i, pfLimit)); |
|
} |
|
copyOutBootstrapArguments(caller, indexInfo, |
|
i, pfLimit, temp, 0, |
|
false, NOT_PRESENT); |
|
for (Object x : temp) { |
|
if (x != NOT_PRESENT && cache[i] == null) { |
|
cache[i] = wrapNull(maybeReBox(x)); |
|
} |
|
i++; |
|
} |
|
} |
|
} |
|
|
|
|
|
static final class PushAdapter { |
|
|
|
static Object pushToBootstrapMethod(MethodHandle pullModeBSM, |
|
MethodHandles.Lookup lookup, String name, Object type, |
|
Object... arguments) throws Throwable { |
|
ConstantGroup cons = makeConstantGroup(Arrays.asList(arguments)); |
|
BootstrapCallInfo<?> bsci = makeBootstrapCallInfo(pullModeBSM, name, type, cons); |
|
if (TRACE_METHOD_LINKAGE) |
|
System.out.println("pull-mode BSM gets pushed arguments from fake BSCI"); |
|
return pullModeBSM.invoke(lookup, bsci); |
|
} |
|
|
|
static final MethodHandle MH_pushToBootstrapMethod; |
|
static { |
|
final Class<?> THIS_CLASS = PushAdapter.class; |
|
try { |
|
MH_pushToBootstrapMethod = IMPL_LOOKUP |
|
.findStatic(THIS_CLASS, "pushToBootstrapMethod", |
|
MethodType.methodType(Object.class, MethodHandle.class, |
|
Lookup.class, String.class, Object.class, Object[].class)); |
|
} catch (Throwable ex) { |
|
throw new InternalError(ex); |
|
} |
|
} |
|
} |
|
|
|
|
|
static final class PullAdapter { |
|
|
|
static Object pullFromBootstrapMethod(MethodHandle pushModeBSM, |
|
MethodHandles.Lookup lookup, |
|
BootstrapCallInfo<?> bsci) |
|
throws Throwable { |
|
int argc = bsci.size(); |
|
switch (argc) { |
|
case 0: |
|
return pushModeBSM.invoke(lookup, bsci.invocationName(), bsci.invocationType()); |
|
case 1: |
|
return pushModeBSM.invoke(lookup, bsci.invocationName(), bsci.invocationType(), |
|
bsci.get(0)); |
|
case 2: |
|
return pushModeBSM.invoke(lookup, bsci.invocationName(), bsci.invocationType(), |
|
bsci.get(0), bsci.get(1)); |
|
case 3: |
|
return pushModeBSM.invoke(lookup, bsci.invocationName(), bsci.invocationType(), |
|
bsci.get(0), bsci.get(1), bsci.get(2)); |
|
case 4: |
|
return pushModeBSM.invoke(lookup, bsci.invocationName(), bsci.invocationType(), |
|
bsci.get(0), bsci.get(1), bsci.get(2), bsci.get(3)); |
|
case 5: |
|
return pushModeBSM.invoke(lookup, bsci.invocationName(), bsci.invocationType(), |
|
bsci.get(0), bsci.get(1), bsci.get(2), bsci.get(3), bsci.get(4)); |
|
case 6: |
|
return pushModeBSM.invoke(lookup, bsci.invocationName(), bsci.invocationType(), |
|
bsci.get(0), bsci.get(1), bsci.get(2), bsci.get(3), bsci.get(4), bsci.get(5)); |
|
default: |
|
final int NON_SPREAD_ARG_COUNT = 3; |
|
final int MAX_SAFE_SIZE = MethodType.MAX_MH_ARITY / 2 - NON_SPREAD_ARG_COUNT; |
|
if (argc >= MAX_SAFE_SIZE) { |
|
|
|
Object[] newargv = new Object[NON_SPREAD_ARG_COUNT + argc]; |
|
newargv[0] = lookup; |
|
newargv[1] = bsci.invocationName(); |
|
newargv[2] = bsci.invocationType(); |
|
bsci.copyConstants(0, argc, newargv, NON_SPREAD_ARG_COUNT); |
|
return pushModeBSM.invokeWithArguments(newargv); |
|
} |
|
MethodType invocationType = MethodType.genericMethodType(NON_SPREAD_ARG_COUNT + argc); |
|
MethodHandle typedBSM = pushModeBSM.asType(invocationType); |
|
MethodHandle spreader = invocationType.invokers().spreadInvoker(NON_SPREAD_ARG_COUNT); |
|
Object[] argv = new Object[argc]; |
|
bsci.copyConstants(0, argc, argv, 0); |
|
return spreader.invokeExact(typedBSM, (Object) lookup, (Object) bsci.invocationName(), bsci.invocationType(), argv); |
|
} |
|
} |
|
|
|
static final MethodHandle MH_pullFromBootstrapMethod; |
|
|
|
static { |
|
final Class<?> THIS_CLASS = PullAdapter.class; |
|
try { |
|
MH_pullFromBootstrapMethod = IMPL_LOOKUP |
|
.findStatic(THIS_CLASS, "pullFromBootstrapMethod", |
|
MethodType.methodType(Object.class, MethodHandle.class, |
|
Lookup.class, BootstrapCallInfo.class)); |
|
} catch (Throwable ex) { |
|
throw new InternalError(ex); |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
static MethodHandle pushMePullYou(MethodHandle bsm, boolean goToPushMode) { |
|
if (TRACE_METHOD_LINKAGE) { |
|
System.out.println("converting BSM of type " + bsm.type() + " to " |
|
+ (goToPushMode ? "push mode" : "pull mode")); |
|
} |
|
assert(isPullModeBSM(bsm) == goToPushMode); |
|
if (goToPushMode) { |
|
return PushAdapter.MH_pushToBootstrapMethod.bindTo(bsm).withVarargs(true); |
|
} else { |
|
return PullAdapter.MH_pullFromBootstrapMethod.bindTo(bsm).withVarargs(false); |
|
} |
|
} |
|
} |