mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-28 07:14:30 +02:00
8263087: Add a MethodHandle combinator that switches over a set of MethodHandles
Reviewed-by: redestad
This commit is contained in:
parent
85f616522b
commit
3623abb7f6
10 changed files with 974 additions and 26 deletions
|
@ -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,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue