mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-27 14:54:52 +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
|
@ -864,6 +864,12 @@ class InvokerBytecodeGenerator {
|
|||
onStack = emitTryFinally(i);
|
||||
i += 2; // jump to the end of the TF idiom
|
||||
continue;
|
||||
case TABLE_SWITCH:
|
||||
assert lambdaForm.isTableSwitch(i);
|
||||
int numCases = (Integer) name.function.intrinsicData();
|
||||
onStack = emitTableSwitch(i, numCases);
|
||||
i += 2; // jump to the end of the TS idiom
|
||||
continue;
|
||||
case LOOP:
|
||||
assert lambdaForm.isLoop(i);
|
||||
onStack = emitLoop(i);
|
||||
|
@ -1389,6 +1395,58 @@ class InvokerBytecodeGenerator {
|
|||
}
|
||||
}
|
||||
|
||||
private Name emitTableSwitch(int pos, int numCases) {
|
||||
Name args = lambdaForm.names[pos];
|
||||
Name invoker = lambdaForm.names[pos + 1];
|
||||
Name result = lambdaForm.names[pos + 2];
|
||||
|
||||
Class<?> returnType = result.function.resolvedHandle().type().returnType();
|
||||
MethodType caseType = args.function.resolvedHandle().type()
|
||||
.dropParameterTypes(0, 1) // drop collector
|
||||
.changeReturnType(returnType);
|
||||
String caseDescriptor = caseType.basicType().toMethodDescriptorString();
|
||||
|
||||
emitPushArgument(invoker, 2); // push cases
|
||||
mv.visitFieldInsn(Opcodes.GETFIELD, "java/lang/invoke/MethodHandleImpl$CasesHolder", "cases",
|
||||
"[Ljava/lang/invoke/MethodHandle;");
|
||||
int casesLocal = extendLocalsMap(new Class<?>[] { MethodHandle[].class });
|
||||
emitStoreInsn(L_TYPE, casesLocal);
|
||||
|
||||
Label endLabel = new Label();
|
||||
Label defaultLabel = new Label();
|
||||
Label[] caseLabels = new Label[numCases];
|
||||
for (int i = 0; i < caseLabels.length; i++) {
|
||||
caseLabels[i] = new Label();
|
||||
}
|
||||
|
||||
emitPushArgument(invoker, 0); // push switch input
|
||||
mv.visitTableSwitchInsn(0, numCases - 1, defaultLabel, caseLabels);
|
||||
|
||||
mv.visitLabel(defaultLabel);
|
||||
emitPushArgument(invoker, 1); // push default handle
|
||||
emitPushArguments(args, 1); // again, skip collector
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, MH, "invokeBasic", caseDescriptor, false);
|
||||
mv.visitJumpInsn(Opcodes.GOTO, endLabel);
|
||||
|
||||
for (int i = 0; i < numCases; i++) {
|
||||
mv.visitLabel(caseLabels[i]);
|
||||
// Load the particular case:
|
||||
emitLoadInsn(L_TYPE, casesLocal);
|
||||
emitIconstInsn(i);
|
||||
mv.visitInsn(Opcodes.AALOAD);
|
||||
|
||||
// invoke it:
|
||||
emitPushArguments(args, 1); // again, skip collector
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, MH, "invokeBasic", caseDescriptor, false);
|
||||
|
||||
mv.visitJumpInsn(Opcodes.GOTO, endLabel);
|
||||
}
|
||||
|
||||
mv.visitLabel(endLabel);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit bytecode for the loop idiom.
|
||||
* <p>
|
||||
|
|
|
@ -314,6 +314,7 @@ class LambdaForm {
|
|||
GET_DOUBLE_VOLATILE("getDoubleVolatile"),
|
||||
PUT_DOUBLE_VOLATILE("putDoubleVolatile"),
|
||||
TRY_FINALLY("tryFinally"),
|
||||
TABLE_SWITCH("tableSwitch"),
|
||||
COLLECT("collect"),
|
||||
COLLECTOR("collector"),
|
||||
CONVERT("convert"),
|
||||
|
@ -707,6 +708,32 @@ class LambdaForm {
|
|||
return isMatchingIdiom(pos, "tryFinally", 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if i-th name is a start of the tableSwitch idiom.
|
||||
*/
|
||||
boolean isTableSwitch(int pos) {
|
||||
// tableSwitch idiom:
|
||||
// t_{n}:L=MethodHandle.invokeBasic(...) // args
|
||||
// t_{n+1}:L=MethodHandleImpl.tableSwitch(*, *, *, t_{n})
|
||||
// t_{n+2}:?=MethodHandle.invokeBasic(*, t_{n+1})
|
||||
if (pos + 2 >= names.length) return false;
|
||||
|
||||
final int POS_COLLECT_ARGS = pos;
|
||||
final int POS_TABLE_SWITCH = pos + 1;
|
||||
final int POS_UNBOX_RESULT = pos + 2;
|
||||
|
||||
Name collectArgs = names[POS_COLLECT_ARGS];
|
||||
Name tableSwitch = names[POS_TABLE_SWITCH];
|
||||
Name unboxResult = names[POS_UNBOX_RESULT];
|
||||
return tableSwitch.refersTo(MethodHandleImpl.class, "tableSwitch") &&
|
||||
collectArgs.isInvokeBasic() &&
|
||||
unboxResult.isInvokeBasic() &&
|
||||
tableSwitch.lastUseIndex(collectArgs) == 3 && // t_{n+1}:L=MethodHandleImpl.<invoker>(*, *, *, t_{n});
|
||||
lastUseIndex(collectArgs) == POS_TABLE_SWITCH && // t_{n} is local: used only in t_{n+1}
|
||||
unboxResult.lastUseIndex(tableSwitch) == 1 && // t_{n+2}:?=MethodHandle.invokeBasic(*, t_{n+1})
|
||||
lastUseIndex(tableSwitch) == POS_UNBOX_RESULT; // t_{n+1} is local: used only in t_{n+2}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if i-th name is a start of the loop idiom.
|
||||
*/
|
||||
|
@ -1067,24 +1094,13 @@ class LambdaForm {
|
|||
final MemberName member;
|
||||
private @Stable MethodHandle resolvedHandle;
|
||||
@Stable MethodHandle invoker;
|
||||
private final MethodHandleImpl.Intrinsic intrinsicName;
|
||||
|
||||
NamedFunction(MethodHandle resolvedHandle) {
|
||||
this(resolvedHandle.internalMemberName(), resolvedHandle, MethodHandleImpl.Intrinsic.NONE);
|
||||
}
|
||||
NamedFunction(MethodHandle resolvedHandle, MethodHandleImpl.Intrinsic intrinsic) {
|
||||
this(resolvedHandle.internalMemberName(), resolvedHandle, intrinsic);
|
||||
this(resolvedHandle.internalMemberName(), resolvedHandle);
|
||||
}
|
||||
NamedFunction(MemberName member, MethodHandle resolvedHandle) {
|
||||
this(member, resolvedHandle, MethodHandleImpl.Intrinsic.NONE);
|
||||
}
|
||||
NamedFunction(MemberName member, MethodHandle resolvedHandle, MethodHandleImpl.Intrinsic intrinsic) {
|
||||
this.member = member;
|
||||
this.resolvedHandle = resolvedHandle;
|
||||
this.intrinsicName = intrinsic;
|
||||
assert(resolvedHandle == null ||
|
||||
resolvedHandle.intrinsicName() == MethodHandleImpl.Intrinsic.NONE ||
|
||||
resolvedHandle.intrinsicName() == intrinsic) : resolvedHandle.intrinsicName() + " != " + intrinsic;
|
||||
// The following assert is almost always correct, but will fail for corner cases, such as PrivateInvokeTest.
|
||||
//assert(!isInvokeBasic(member));
|
||||
}
|
||||
|
@ -1097,7 +1113,6 @@ class LambdaForm {
|
|||
// necessary to pass BigArityTest
|
||||
this.member = Invokers.invokeBasicMethod(basicInvokerType);
|
||||
}
|
||||
this.intrinsicName = MethodHandleImpl.Intrinsic.NONE;
|
||||
assert(isInvokeBasic(member));
|
||||
}
|
||||
|
||||
|
@ -1250,7 +1265,15 @@ class LambdaForm {
|
|||
}
|
||||
|
||||
public MethodHandleImpl.Intrinsic intrinsicName() {
|
||||
return intrinsicName;
|
||||
return resolvedHandle != null
|
||||
? resolvedHandle.intrinsicName()
|
||||
: MethodHandleImpl.Intrinsic.NONE;
|
||||
}
|
||||
|
||||
public Object intrinsicData() {
|
||||
return resolvedHandle != null
|
||||
? resolvedHandle.intrinsicData()
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1732,15 +1755,15 @@ class LambdaForm {
|
|||
Name[] idNames = new Name[] { argument(0, L_TYPE), argument(1, type) };
|
||||
idForm = new LambdaForm(2, idNames, 1, Kind.IDENTITY);
|
||||
idForm.compileToBytecode();
|
||||
idFun = new NamedFunction(idMem, SimpleMethodHandle.make(idMem.getInvocationType(), idForm),
|
||||
MethodHandleImpl.Intrinsic.IDENTITY);
|
||||
idFun = new NamedFunction(idMem, MethodHandleImpl.makeIntrinsic(SimpleMethodHandle.make(idMem.getInvocationType(), idForm),
|
||||
MethodHandleImpl.Intrinsic.IDENTITY));
|
||||
|
||||
Object zeValue = Wrapper.forBasicType(btChar).zero();
|
||||
Name[] zeNames = new Name[] { argument(0, L_TYPE), new Name(idFun, zeValue) };
|
||||
zeForm = new LambdaForm(1, zeNames, 1, Kind.ZERO);
|
||||
zeForm.compileToBytecode();
|
||||
zeFun = new NamedFunction(zeMem, SimpleMethodHandle.make(zeMem.getInvocationType(), zeForm),
|
||||
MethodHandleImpl.Intrinsic.ZERO);
|
||||
zeFun = new NamedFunction(zeMem, MethodHandleImpl.makeIntrinsic(SimpleMethodHandle.make(zeMem.getInvocationType(), zeForm),
|
||||
MethodHandleImpl.Intrinsic.ZERO));
|
||||
}
|
||||
|
||||
LF_zero[ord] = zeForm;
|
||||
|
|
|
@ -38,6 +38,7 @@ import static java.lang.invoke.LambdaForm.*;
|
|||
import static java.lang.invoke.LambdaForm.BasicType.*;
|
||||
import static java.lang.invoke.MethodHandleImpl.Intrinsic;
|
||||
import static java.lang.invoke.MethodHandleImpl.NF_loop;
|
||||
import static java.lang.invoke.MethodHandleImpl.makeIntrinsic;
|
||||
|
||||
/** Transforms on LFs.
|
||||
* A lambda-form editor can derive new LFs from its base LF.
|
||||
|
@ -619,7 +620,7 @@ class LambdaFormEditor {
|
|||
// adjust the arguments
|
||||
MethodHandle aload = MethodHandles.arrayElementGetter(erasedArrayType);
|
||||
for (int i = 0; i < arrayLength; i++) {
|
||||
Name loadArgument = new Name(new NamedFunction(aload, Intrinsic.ARRAY_LOAD), spreadParam, i);
|
||||
Name loadArgument = new Name(new NamedFunction(makeIntrinsic(aload, Intrinsic.ARRAY_LOAD)), spreadParam, i);
|
||||
buf.insertExpression(exprPos + i, loadArgument);
|
||||
buf.replaceParameterByCopy(pos + i, exprPos + i);
|
||||
}
|
||||
|
|
|
@ -1679,6 +1679,11 @@ assertEquals("[three, thee, tee]", asListFix.invoke((Object)argv).toString());
|
|||
return MethodHandleImpl.Intrinsic.NONE;
|
||||
}
|
||||
|
||||
/*non-public*/
|
||||
Object intrinsicData() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/*non-public*/
|
||||
MethodHandle withInternalMemberName(MemberName member, boolean isInvokeSpecial) {
|
||||
if (member != null) {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -7751,4 +7751,90 @@ assertEquals("boojum", (String) catTrace.invokeExact("boo", "jum"));
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a table switch method handle, which can be used to switch over a set of target
|
||||
* method handles, based on a given target index, called selector.
|
||||
* <p>
|
||||
* For a selector value of {@code n}, where {@code n} falls in the range {@code [0, N)},
|
||||
* and where {@code N} is the number of target method handles, the table switch method
|
||||
* handle will invoke the n-th target method handle from the list of target method handles.
|
||||
* <p>
|
||||
* For a selector value that does not fall in the range {@code [0, N)}, the table switch
|
||||
* method handle will invoke the given fallback method handle.
|
||||
* <p>
|
||||
* All method handles passed to this method must have the same type, with the additional
|
||||
* requirement that the leading parameter be of type {@code int}. The leading parameter
|
||||
* represents the selector.
|
||||
* <p>
|
||||
* Any trailing parameters present in the type will appear on the returned table switch
|
||||
* method handle as well. Any arguments assigned to these parameters will be forwarded,
|
||||
* together with the selector value, to the selected method handle when invoking it.
|
||||
*
|
||||
* @apiNote Example:
|
||||
* The cases each drop the {@code selector} value they are given, and take an additional
|
||||
* {@code String} argument, which is concatenated (using {@link String#concat(String)})
|
||||
* to a specific constant label string for each case:
|
||||
* <blockquote><pre>{@code
|
||||
* MethodHandles.Lookup lookup = MethodHandles.lookup();
|
||||
* MethodHandle caseMh = lookup.findVirtual(String.class, "concat",
|
||||
* MethodType.methodType(String.class, String.class));
|
||||
* caseMh = MethodHandles.dropArguments(caseMh, 0, int.class);
|
||||
*
|
||||
* MethodHandle caseDefault = MethodHandles.insertArguments(caseMh, 1, "default: ");
|
||||
* MethodHandle case0 = MethodHandles.insertArguments(caseMh, 1, "case 0: ");
|
||||
* MethodHandle case1 = MethodHandles.insertArguments(caseMh, 1, "case 1: ");
|
||||
*
|
||||
* MethodHandle mhSwitch = MethodHandles.tableSwitch(
|
||||
* caseDefault,
|
||||
* case0,
|
||||
* case1
|
||||
* );
|
||||
*
|
||||
* assertEquals("default: data", (String) mhSwitch.invokeExact(-1, "data"));
|
||||
* assertEquals("case 0: data", (String) mhSwitch.invokeExact(0, "data"));
|
||||
* assertEquals("case 1: data", (String) mhSwitch.invokeExact(1, "data"));
|
||||
* assertEquals("default: data", (String) mhSwitch.invokeExact(2, "data"));
|
||||
* }</pre></blockquote>
|
||||
*
|
||||
* @param fallback the fallback method handle that is called when the selector is not
|
||||
* within the range {@code [0, N)}.
|
||||
* @param targets array of target method handles.
|
||||
* @return the table switch method handle.
|
||||
* @throws NullPointerException if {@code fallback}, the {@code targets} array, or any
|
||||
* any of the elements of the {@code targets} array are
|
||||
* {@code null}.
|
||||
* @throws IllegalArgumentException if the {@code targets} array is empty, if the leading
|
||||
* parameter of the fallback handle or any of the target
|
||||
* handles is not {@code int}, or if the types of
|
||||
* the fallback handle and all of target handles are
|
||||
* not the same.
|
||||
*/
|
||||
public static MethodHandle tableSwitch(MethodHandle fallback, MethodHandle... targets) {
|
||||
Objects.requireNonNull(fallback);
|
||||
Objects.requireNonNull(targets);
|
||||
targets = targets.clone();
|
||||
MethodType type = tableSwitchChecks(fallback, targets);
|
||||
return MethodHandleImpl.makeTableSwitch(type, fallback, targets);
|
||||
}
|
||||
|
||||
private static MethodType tableSwitchChecks(MethodHandle defaultCase, MethodHandle[] caseActions) {
|
||||
if (caseActions.length == 0)
|
||||
throw new IllegalArgumentException("Not enough cases: " + Arrays.toString(caseActions));
|
||||
|
||||
MethodType expectedType = defaultCase.type();
|
||||
|
||||
if (!(expectedType.parameterCount() >= 1) || expectedType.parameterType(0) != int.class)
|
||||
throw new IllegalArgumentException(
|
||||
"Case actions must have int as leading parameter: " + Arrays.toString(caseActions));
|
||||
|
||||
for (MethodHandle mh : caseActions) {
|
||||
Objects.requireNonNull(mh);
|
||||
if (mh.type() != expectedType)
|
||||
throw new IllegalArgumentException(
|
||||
"Case actions must have the same type: " + Arrays.toString(caseActions));
|
||||
}
|
||||
|
||||
return expectedType;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue