8263087: Add a MethodHandle combinator that switches over a set of MethodHandles

Reviewed-by: redestad
This commit is contained in:
Jorn Vernee 2021-05-27 12:28:10 +00:00
parent 85f616522b
commit 3623abb7f6
10 changed files with 974 additions and 26 deletions

View file

@ -49,6 +49,8 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.stream.Stream;
@ -823,7 +825,9 @@ abstract class MethodHandleImpl {
names[PROFILE] = new Name(getFunction(NF_profileBoolean), names[CALL_TEST], names[GET_COUNTERS]);
}
// call selectAlternative
names[SELECT_ALT] = new Name(new NamedFunction(getConstantHandle(MH_selectAlternative), Intrinsic.SELECT_ALTERNATIVE), names[TEST], names[GET_TARGET], names[GET_FALLBACK]);
names[SELECT_ALT] = new Name(new NamedFunction(
makeIntrinsic(getConstantHandle(MH_selectAlternative), Intrinsic.SELECT_ALTERNATIVE)),
names[TEST], names[GET_TARGET], names[GET_FALLBACK]);
// call target or fallback
invokeArgs[0] = names[SELECT_ALT];
@ -894,7 +898,7 @@ abstract class MethodHandleImpl {
Object[] args = new Object[invokeBasic.type().parameterCount()];
args[0] = names[GET_COLLECT_ARGS];
System.arraycopy(names, ARG_BASE, args, 1, ARG_LIMIT-ARG_BASE);
names[BOXED_ARGS] = new Name(new NamedFunction(invokeBasic, Intrinsic.GUARD_WITH_CATCH), args);
names[BOXED_ARGS] = new Name(new NamedFunction(makeIntrinsic(invokeBasic, Intrinsic.GUARD_WITH_CATCH)), args);
// t_{i+1}:L=MethodHandleImpl.guardWithCatch(target:L,exType:L,catcher:L,t_{i}:L);
Object[] gwcArgs = new Object[] {names[GET_TARGET], names[GET_CLASS], names[GET_CATCHER], names[BOXED_ARGS]};
@ -1226,6 +1230,7 @@ abstract class MethodHandleImpl {
SELECT_ALTERNATIVE,
GUARD_WITH_CATCH,
TRY_FINALLY,
TABLE_SWITCH,
LOOP,
ARRAY_LOAD,
ARRAY_STORE,
@ -1240,11 +1245,17 @@ abstract class MethodHandleImpl {
static final class IntrinsicMethodHandle extends DelegatingMethodHandle {
private final MethodHandle target;
private final Intrinsic intrinsicName;
private final Object intrinsicData;
IntrinsicMethodHandle(MethodHandle target, Intrinsic intrinsicName) {
this(target, intrinsicName, null);
}
IntrinsicMethodHandle(MethodHandle target, Intrinsic intrinsicName, Object intrinsicData) {
super(target.type(), target);
this.target = target;
this.intrinsicName = intrinsicName;
this.intrinsicData = intrinsicData;
}
@Override
@ -1257,6 +1268,11 @@ abstract class MethodHandleImpl {
return intrinsicName;
}
@Override
Object intrinsicData() {
return intrinsicData;
}
@Override
public MethodHandle asTypeUncached(MethodType newType) {
// This MH is an alias for target, except for the intrinsic name
@ -1282,9 +1298,13 @@ abstract class MethodHandleImpl {
}
static MethodHandle makeIntrinsic(MethodHandle target, Intrinsic intrinsicName) {
return makeIntrinsic(target, intrinsicName, null);
}
static MethodHandle makeIntrinsic(MethodHandle target, Intrinsic intrinsicName, Object intrinsicData) {
if (intrinsicName == target.intrinsicName())
return target;
return new IntrinsicMethodHandle(target, intrinsicName);
return new IntrinsicMethodHandle(target, intrinsicName, intrinsicData);
}
static MethodHandle makeIntrinsic(MethodType type, LambdaForm form, Intrinsic intrinsicName) {
@ -1360,7 +1380,8 @@ abstract class MethodHandleImpl {
NF_tryFinally = 3,
NF_loop = 4,
NF_profileBoolean = 5,
NF_LIMIT = 6;
NF_tableSwitch = 6,
NF_LIMIT = 7;
private static final @Stable NamedFunction[] NFS = new NamedFunction[NF_LIMIT];
@ -1394,6 +1415,9 @@ abstract class MethodHandleImpl {
case NF_profileBoolean:
return new NamedFunction(MethodHandleImpl.class
.getDeclaredMethod("profileBoolean", boolean.class, int[].class));
case NF_tableSwitch:
return new NamedFunction(MethodHandleImpl.class
.getDeclaredMethod("tableSwitch", int.class, MethodHandle.class, CasesHolder.class, Object[].class));
default:
throw new InternalError("Undefined function: " + func);
}
@ -1602,7 +1626,7 @@ abstract class MethodHandleImpl {
Object[] args = new Object[invokeBasic.type().parameterCount()];
args[0] = names[GET_COLLECT_ARGS];
System.arraycopy(names, ARG_BASE, args, 1, ARG_LIMIT - ARG_BASE);
names[BOXED_ARGS] = new Name(new NamedFunction(invokeBasic, Intrinsic.LOOP), args);
names[BOXED_ARGS] = new Name(new NamedFunction(makeIntrinsic(invokeBasic, Intrinsic.LOOP)), args);
// t_{i+1}:L=MethodHandleImpl.loop(localTypes:L,clauses:L,t_{i}:L);
Object[] lArgs =
@ -1839,7 +1863,7 @@ abstract class MethodHandleImpl {
Object[] args = new Object[invokeBasic.type().parameterCount()];
args[0] = names[GET_COLLECT_ARGS];
System.arraycopy(names, ARG_BASE, args, 1, ARG_LIMIT-ARG_BASE);
names[BOXED_ARGS] = new Name(new NamedFunction(invokeBasic, Intrinsic.TRY_FINALLY), args);
names[BOXED_ARGS] = new Name(new NamedFunction(makeIntrinsic(invokeBasic, Intrinsic.TRY_FINALLY)), args);
// t_{i+1}:L=MethodHandleImpl.tryFinally(target:L,exType:L,catcher:L,t_{i}:L);
Object[] tfArgs = new Object[] {names[GET_TARGET], names[GET_CLEANUP], names[BOXED_ARGS]};
@ -1941,7 +1965,7 @@ abstract class MethodHandleImpl {
storeNameCursor < STORE_ELEMENT_LIMIT;
storeIndex++, storeNameCursor++, argCursor++){
names[storeNameCursor] = new Name(new NamedFunction(storeFunc, Intrinsic.ARRAY_STORE),
names[storeNameCursor] = new Name(new NamedFunction(makeIntrinsic(storeFunc, Intrinsic.ARRAY_STORE)),
names[CALL_NEW_ARRAY], storeIndex, names[argCursor]);
}
@ -1952,6 +1976,141 @@ abstract class MethodHandleImpl {
return lform;
}
// use a wrapper because we need this array to be @Stable
static class CasesHolder {
@Stable
final MethodHandle[] cases;
public CasesHolder(MethodHandle[] cases) {
this.cases = cases;
}
}
static MethodHandle makeTableSwitch(MethodType type, MethodHandle defaultCase, MethodHandle[] caseActions) {
MethodType varargsType = type.changeReturnType(Object[].class);
MethodHandle collectArgs = varargsArray(type.parameterCount()).asType(varargsType);
MethodHandle unboxResult = unboxResultHandle(type.returnType());
BoundMethodHandle.SpeciesData data = BoundMethodHandle.speciesData_LLLL();
LambdaForm form = makeTableSwitchForm(type.basicType(), data, caseActions.length);
BoundMethodHandle mh;
CasesHolder caseHolder = new CasesHolder(caseActions);
try {
mh = (BoundMethodHandle) data.factory().invokeBasic(type, form, (Object) defaultCase, (Object) collectArgs,
(Object) unboxResult, (Object) caseHolder);
} catch (Throwable ex) {
throw uncaughtException(ex);
}
assert(mh.type() == type);
return mh;
}
private static class TableSwitchCacheKey {
private static final Map<TableSwitchCacheKey, LambdaForm> CACHE = new ConcurrentHashMap<>();
private final MethodType basicType;
private final int numberOfCases;
public TableSwitchCacheKey(MethodType basicType, int numberOfCases) {
this.basicType = basicType;
this.numberOfCases = numberOfCases;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TableSwitchCacheKey that = (TableSwitchCacheKey) o;
return numberOfCases == that.numberOfCases && Objects.equals(basicType, that.basicType);
}
@Override
public int hashCode() {
return Objects.hash(basicType, numberOfCases);
}
}
private static LambdaForm makeTableSwitchForm(MethodType basicType, BoundMethodHandle.SpeciesData data,
int numCases) {
MethodType lambdaType = basicType.invokerType();
// We need to cache based on the basic type X number of cases,
// since the number of cases is used when generating bytecode.
// This also means that we can't use the cache in MethodTypeForm,
// which only uses the basic type as a key.
TableSwitchCacheKey key = new TableSwitchCacheKey(basicType, numCases);
LambdaForm lform = TableSwitchCacheKey.CACHE.get(key);
if (lform != null) {
return lform;
}
final int THIS_MH = 0;
final int ARG_BASE = 1; // start of incoming arguments
final int ARG_LIMIT = ARG_BASE + basicType.parameterCount();
final int ARG_SWITCH_ON = ARG_BASE;
assert ARG_SWITCH_ON < ARG_LIMIT;
int nameCursor = ARG_LIMIT;
final int GET_COLLECT_ARGS = nameCursor++;
final int GET_DEFAULT_CASE = nameCursor++;
final int GET_UNBOX_RESULT = nameCursor++;
final int GET_CASES = nameCursor++;
final int BOXED_ARGS = nameCursor++;
final int TABLE_SWITCH = nameCursor++;
final int UNBOXED_RESULT = nameCursor++;
int fieldCursor = 0;
final int FIELD_DEFAULT_CASE = fieldCursor++;
final int FIELD_COLLECT_ARGS = fieldCursor++;
final int FIELD_UNBOX_RESULT = fieldCursor++;
final int FIELD_CASES = fieldCursor++;
Name[] names = arguments(nameCursor - ARG_LIMIT, lambdaType);
names[THIS_MH] = names[THIS_MH].withConstraint(data);
names[GET_DEFAULT_CASE] = new Name(data.getterFunction(FIELD_DEFAULT_CASE), names[THIS_MH]);
names[GET_COLLECT_ARGS] = new Name(data.getterFunction(FIELD_COLLECT_ARGS), names[THIS_MH]);
names[GET_UNBOX_RESULT] = new Name(data.getterFunction(FIELD_UNBOX_RESULT), names[THIS_MH]);
names[GET_CASES] = new Name(data.getterFunction(FIELD_CASES), names[THIS_MH]);
{
MethodType collectArgsType = basicType.changeReturnType(Object.class);
MethodHandle invokeBasic = MethodHandles.basicInvoker(collectArgsType);
Object[] args = new Object[invokeBasic.type().parameterCount()];
args[0] = names[GET_COLLECT_ARGS];
System.arraycopy(names, ARG_BASE, args, 1, ARG_LIMIT - ARG_BASE);
names[BOXED_ARGS] = new Name(new NamedFunction(makeIntrinsic(invokeBasic, Intrinsic.TABLE_SWITCH, numCases)), args);
}
{
Object[] tfArgs = new Object[]{
names[ARG_SWITCH_ON], names[GET_DEFAULT_CASE], names[GET_CASES], names[BOXED_ARGS]};
names[TABLE_SWITCH] = new Name(getFunction(NF_tableSwitch), tfArgs);
}
{
MethodHandle invokeBasic = MethodHandles.basicInvoker(MethodType.methodType(basicType.rtype(), Object.class));
Object[] unboxArgs = new Object[]{names[GET_UNBOX_RESULT], names[TABLE_SWITCH]};
names[UNBOXED_RESULT] = new Name(invokeBasic, unboxArgs);
}
lform = new LambdaForm(lambdaType.parameterCount(), names, Kind.TABLE_SWITCH);
LambdaForm prev = TableSwitchCacheKey.CACHE.putIfAbsent(key, lform);
return prev != null ? prev : lform;
}
@Hidden
static Object tableSwitch(int input, MethodHandle defaultCase, CasesHolder holder, Object[] args) throws Throwable {
MethodHandle[] caseActions = holder.cases;
MethodHandle selectedCase;
if (input < 0 || input >= caseActions.length) {
selectedCase = defaultCase;
} else {
selectedCase = caseActions[input];
}
return selectedCase.invokeWithArguments(args);
}
// Indexes into constant method handles:
static final int
MH_cast = 0,