You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
859 lines
34 KiB
859 lines
34 KiB
/*
|
|
* Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
|
|
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*/
|
|
|
|
package java.lang.invoke;
|
|
|
|
import java.lang.ref.SoftReference;
|
|
import java.util.Arrays;
|
|
import static java.lang.invoke.LambdaForm.*;
|
|
import static java.lang.invoke.LambdaForm.BasicType.*;
|
|
import static java.lang.invoke.MethodHandleImpl.Intrinsic;
|
|
import java.util.Collections;
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
|
|
import sun.invoke.util.Wrapper;
|
|
|
|
/** Transforms on LFs.
|
|
* A lambda-form editor can derive new LFs from its base LF.
|
|
* The editor can cache derived LFs, which simplifies the reuse of their underlying bytecodes.
|
|
* To support this caching, a LF has an optional pointer to its editor.
|
|
*/
|
|
class LambdaFormEditor {
|
|
final LambdaForm lambdaForm;
|
|
|
|
private LambdaFormEditor(LambdaForm lambdaForm) {
|
|
this.lambdaForm = lambdaForm;
|
|
}
|
|
|
|
// Factory method.
|
|
static LambdaFormEditor lambdaFormEditor(LambdaForm lambdaForm) {
|
|
// TO DO: Consider placing intern logic here, to cut down on duplication.
|
|
// lambdaForm = findPreexistingEquivalent(lambdaForm)
|
|
|
|
// Always use uncustomized version for editing.
|
|
// It helps caching and customized LambdaForms reuse transformCache field to keep a link to uncustomized version.
|
|
return new LambdaFormEditor(lambdaForm.uncustomize());
|
|
}
|
|
|
|
/** A description of a cached transform, possibly associated with the result of the transform.
|
|
* The logical content is a sequence of byte values, starting with a Kind.ordinal value.
|
|
* The sequence is unterminated, ending with an indefinite number of zero bytes.
|
|
* Sequences that are simple (short enough and with small enough values) pack into a 64-bit long.
|
|
*/
|
|
private static final class Transform extends SoftReference<LambdaForm> {
|
|
final long packedBytes;
|
|
final byte[] fullBytes;
|
|
|
|
private enum Kind {
|
|
NO_KIND, // necessary because ordinal must be greater than zero
|
|
BIND_ARG, ADD_ARG, DUP_ARG,
|
|
SPREAD_ARGS,
|
|
FILTER_ARG, FILTER_RETURN, FILTER_RETURN_TO_ZERO,
|
|
COLLECT_ARGS, COLLECT_ARGS_TO_VOID, COLLECT_ARGS_TO_ARRAY,
|
|
FOLD_ARGS, FOLD_ARGS_TO_VOID,
|
|
PERMUTE_ARGS
|
|
//maybe add more for guard with test, catch exception, pointwise type conversions
|
|
}
|
|
|
|
private static final boolean STRESS_TEST = false; // turn on to disable most packing
|
|
private static final int
|
|
PACKED_BYTE_SIZE = (STRESS_TEST ? 2 : 4),
|
|
PACKED_BYTE_MASK = (1 << PACKED_BYTE_SIZE) - 1,
|
|
PACKED_BYTE_MAX_LENGTH = (STRESS_TEST ? 3 : 64 / PACKED_BYTE_SIZE);
|
|
|
|
private static long packedBytes(byte[] bytes) {
|
|
if (bytes.length > PACKED_BYTE_MAX_LENGTH) return 0;
|
|
long pb = 0;
|
|
int bitset = 0;
|
|
for (int i = 0; i < bytes.length; i++) {
|
|
int b = bytes[i] & 0xFF;
|
|
bitset |= b;
|
|
pb |= (long)b << (i * PACKED_BYTE_SIZE);
|
|
}
|
|
if (!inRange(bitset))
|
|
return 0;
|
|
return pb;
|
|
}
|
|
private static long packedBytes(int b0, int b1) {
|
|
assert(inRange(b0 | b1));
|
|
return ( (b0 << 0*PACKED_BYTE_SIZE)
|
|
| (b1 << 1*PACKED_BYTE_SIZE));
|
|
}
|
|
private static long packedBytes(int b0, int b1, int b2) {
|
|
assert(inRange(b0 | b1 | b2));
|
|
return ( (b0 << 0*PACKED_BYTE_SIZE)
|
|
| (b1 << 1*PACKED_BYTE_SIZE)
|
|
| (b2 << 2*PACKED_BYTE_SIZE));
|
|
}
|
|
private static long packedBytes(int b0, int b1, int b2, int b3) {
|
|
assert(inRange(b0 | b1 | b2 | b3));
|
|
return ( (b0 << 0*PACKED_BYTE_SIZE)
|
|
| (b1 << 1*PACKED_BYTE_SIZE)
|
|
| (b2 << 2*PACKED_BYTE_SIZE)
|
|
| (b3 << 3*PACKED_BYTE_SIZE));
|
|
}
|
|
private static boolean inRange(int bitset) {
|
|
assert((bitset & 0xFF) == bitset); // incoming values must fit in *unsigned* byte
|
|
return ((bitset & ~PACKED_BYTE_MASK) == 0);
|
|
}
|
|
private static byte[] fullBytes(int... byteValues) {
|
|
byte[] bytes = new byte[byteValues.length];
|
|
int i = 0;
|
|
for (int bv : byteValues) {
|
|
bytes[i++] = bval(bv);
|
|
}
|
|
assert(packedBytes(bytes) == 0);
|
|
return bytes;
|
|
}
|
|
|
|
private byte byteAt(int i) {
|
|
long pb = packedBytes;
|
|
if (pb == 0) {
|
|
if (i >= fullBytes.length) return 0;
|
|
return fullBytes[i];
|
|
}
|
|
assert(fullBytes == null);
|
|
if (i > PACKED_BYTE_MAX_LENGTH) return 0;
|
|
int pos = (i * PACKED_BYTE_SIZE);
|
|
return (byte)((pb >>> pos) & PACKED_BYTE_MASK);
|
|
}
|
|
|
|
Kind kind() { return Kind.values()[byteAt(0)]; }
|
|
|
|
private Transform(long packedBytes, byte[] fullBytes, LambdaForm result) {
|
|
super(result);
|
|
this.packedBytes = packedBytes;
|
|
this.fullBytes = fullBytes;
|
|
}
|
|
private Transform(long packedBytes) {
|
|
this(packedBytes, null, null);
|
|
assert(packedBytes != 0);
|
|
}
|
|
private Transform(byte[] fullBytes) {
|
|
this(0, fullBytes, null);
|
|
}
|
|
|
|
private static byte bval(int b) {
|
|
assert((b & 0xFF) == b); // incoming value must fit in *unsigned* byte
|
|
return (byte)b;
|
|
}
|
|
private static byte bval(Kind k) {
|
|
return bval(k.ordinal());
|
|
}
|
|
static Transform of(Kind k, int b1) {
|
|
byte b0 = bval(k);
|
|
if (inRange(b0 | b1))
|
|
return new Transform(packedBytes(b0, b1));
|
|
else
|
|
return new Transform(fullBytes(b0, b1));
|
|
}
|
|
static Transform of(Kind k, int b1, int b2) {
|
|
byte b0 = (byte) k.ordinal();
|
|
if (inRange(b0 | b1 | b2))
|
|
return new Transform(packedBytes(b0, b1, b2));
|
|
else
|
|
return new Transform(fullBytes(b0, b1, b2));
|
|
}
|
|
static Transform of(Kind k, int b1, int b2, int b3) {
|
|
byte b0 = (byte) k.ordinal();
|
|
if (inRange(b0 | b1 | b2 | b3))
|
|
return new Transform(packedBytes(b0, b1, b2, b3));
|
|
else
|
|
return new Transform(fullBytes(b0, b1, b2, b3));
|
|
}
|
|
private static final byte[] NO_BYTES = {};
|
|
static Transform of(Kind k, int... b123) {
|
|
return ofBothArrays(k, b123, NO_BYTES);
|
|
}
|
|
static Transform of(Kind k, int b1, byte[] b234) {
|
|
return ofBothArrays(k, new int[]{ b1 }, b234);
|
|
}
|
|
static Transform of(Kind k, int b1, int b2, byte[] b345) {
|
|
return ofBothArrays(k, new int[]{ b1, b2 }, b345);
|
|
}
|
|
private static Transform ofBothArrays(Kind k, int[] b123, byte[] b456) {
|
|
byte[] fullBytes = new byte[1 + b123.length + b456.length];
|
|
int i = 0;
|
|
fullBytes[i++] = bval(k);
|
|
for (int bv : b123) {
|
|
fullBytes[i++] = bval(bv);
|
|
}
|
|
for (byte bv : b456) {
|
|
fullBytes[i++] = bv;
|
|
}
|
|
long packedBytes = packedBytes(fullBytes);
|
|
if (packedBytes != 0)
|
|
return new Transform(packedBytes);
|
|
else
|
|
return new Transform(fullBytes);
|
|
}
|
|
|
|
Transform withResult(LambdaForm result) {
|
|
return new Transform(this.packedBytes, this.fullBytes, result);
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object obj) {
|
|
return obj instanceof Transform && equals((Transform)obj);
|
|
}
|
|
public boolean equals(Transform that) {
|
|
return this.packedBytes == that.packedBytes && Arrays.equals(this.fullBytes, that.fullBytes);
|
|
}
|
|
@Override
|
|
public int hashCode() {
|
|
if (packedBytes != 0) {
|
|
assert(fullBytes == null);
|
|
return Long.hashCode(packedBytes);
|
|
}
|
|
return Arrays.hashCode(fullBytes);
|
|
}
|
|
@Override
|
|
public String toString() {
|
|
StringBuilder buf = new StringBuilder();
|
|
long bits = packedBytes;
|
|
if (bits != 0) {
|
|
buf.append("(");
|
|
while (bits != 0) {
|
|
buf.append(bits & PACKED_BYTE_MASK);
|
|
bits >>>= PACKED_BYTE_SIZE;
|
|
if (bits != 0) buf.append(",");
|
|
}
|
|
buf.append(")");
|
|
}
|
|
if (fullBytes != null) {
|
|
buf.append("unpacked");
|
|
buf.append(Arrays.toString(fullBytes));
|
|
}
|
|
LambdaForm result = get();
|
|
if (result != null) {
|
|
buf.append(" result=");
|
|
buf.append(result);
|
|
}
|
|
return buf.toString();
|
|
}
|
|
}
|
|
|
|
/** Find a previously cached transform equivalent to the given one, and return its result. */
|
|
private LambdaForm getInCache(Transform key) {
|
|
assert(key.get() == null);
|
|
// The transformCache is one of null, Transform, Transform[], or ConcurrentHashMap.
|
|
Object c = lambdaForm.transformCache;
|
|
Transform k = null;
|
|
if (c instanceof ConcurrentHashMap) {
|
|
@SuppressWarnings("unchecked")
|
|
ConcurrentHashMap<Transform,Transform> m = (ConcurrentHashMap<Transform,Transform>) c;
|
|
k = m.get(key);
|
|
} else if (c == null) {
|
|
return null;
|
|
} else if (c instanceof Transform) {
|
|
// one-element cache avoids overhead of an array
|
|
Transform t = (Transform)c;
|
|
if (t.equals(key)) k = t;
|
|
} else {
|
|
Transform[] ta = (Transform[])c;
|
|
for (int i = 0; i < ta.length; i++) {
|
|
Transform t = ta[i];
|
|
if (t == null) break;
|
|
if (t.equals(key)) { k = t; break; }
|
|
}
|
|
}
|
|
assert(k == null || key.equals(k));
|
|
return (k != null) ? k.get() : null;
|
|
}
|
|
|
|
/** Arbitrary but reasonable limits on Transform[] size for cache. */
|
|
private static final int MIN_CACHE_ARRAY_SIZE = 4, MAX_CACHE_ARRAY_SIZE = 16;
|
|
|
|
/** Cache a transform with its result, and return that result.
|
|
* But if an equivalent transform has already been cached, return its result instead.
|
|
*/
|
|
private LambdaForm putInCache(Transform key, LambdaForm form) {
|
|
key = key.withResult(form);
|
|
for (int pass = 0; ; pass++) {
|
|
Object c = lambdaForm.transformCache;
|
|
if (c instanceof ConcurrentHashMap) {
|
|
@SuppressWarnings("unchecked")
|
|
ConcurrentHashMap<Transform,Transform> m = (ConcurrentHashMap<Transform,Transform>) c;
|
|
Transform k = m.putIfAbsent(key, key);
|
|
if (k == null) return form;
|
|
LambdaForm result = k.get();
|
|
if (result != null) {
|
|
return result;
|
|
} else {
|
|
if (m.replace(key, k, key)) {
|
|
return form;
|
|
} else {
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
assert(pass == 0);
|
|
synchronized (lambdaForm) {
|
|
c = lambdaForm.transformCache;
|
|
if (c instanceof ConcurrentHashMap)
|
|
continue;
|
|
if (c == null) {
|
|
lambdaForm.transformCache = key;
|
|
return form;
|
|
}
|
|
Transform[] ta;
|
|
if (c instanceof Transform) {
|
|
Transform k = (Transform)c;
|
|
if (k.equals(key)) {
|
|
LambdaForm result = k.get();
|
|
if (result == null) {
|
|
lambdaForm.transformCache = key;
|
|
return form;
|
|
} else {
|
|
return result;
|
|
}
|
|
} else if (k.get() == null) { // overwrite stale entry
|
|
lambdaForm.transformCache = key;
|
|
return form;
|
|
}
|
|
// expand one-element cache to small array
|
|
ta = new Transform[MIN_CACHE_ARRAY_SIZE];
|
|
ta[0] = k;
|
|
lambdaForm.transformCache = ta;
|
|
} else {
|
|
// it is already expanded
|
|
ta = (Transform[])c;
|
|
}
|
|
int len = ta.length;
|
|
int stale = -1;
|
|
int i;
|
|
for (i = 0; i < len; i++) {
|
|
Transform k = ta[i];
|
|
if (k == null) {
|
|
break;
|
|
}
|
|
if (k.equals(key)) {
|
|
LambdaForm result = k.get();
|
|
if (result == null) {
|
|
ta[i] = key;
|
|
return form;
|
|
} else {
|
|
return result;
|
|
}
|
|
} else if (stale < 0 && k.get() == null) {
|
|
stale = i; // remember 1st stale entry index
|
|
}
|
|
}
|
|
if (i < len || stale >= 0) {
|
|
// just fall through to cache update
|
|
} else if (len < MAX_CACHE_ARRAY_SIZE) {
|
|
len = Math.min(len * 2, MAX_CACHE_ARRAY_SIZE);
|
|
ta = Arrays.copyOf(ta, len);
|
|
lambdaForm.transformCache = ta;
|
|
} else {
|
|
ConcurrentHashMap<Transform, Transform> m = new ConcurrentHashMap<>(MAX_CACHE_ARRAY_SIZE * 2);
|
|
for (Transform k : ta) {
|
|
m.put(k, k);
|
|
}
|
|
lambdaForm.transformCache = m;
|
|
// The second iteration will update for this query, concurrently.
|
|
continue;
|
|
}
|
|
int idx = (stale >= 0) ? stale : i;
|
|
ta[idx] = key;
|
|
return form;
|
|
}
|
|
}
|
|
}
|
|
|
|
private LambdaFormBuffer buffer() {
|
|
return new LambdaFormBuffer(lambdaForm);
|
|
}
|
|
|
|
/// Editing methods for method handles. These need to have fast paths.
|
|
|
|
private BoundMethodHandle.SpeciesData oldSpeciesData() {
|
|
return BoundMethodHandle.speciesData(lambdaForm);
|
|
}
|
|
private BoundMethodHandle.SpeciesData newSpeciesData(BasicType type) {
|
|
return oldSpeciesData().extendWith(type);
|
|
}
|
|
|
|
BoundMethodHandle bindArgumentL(BoundMethodHandle mh, int pos, Object value) {
|
|
assert(mh.speciesData() == oldSpeciesData());
|
|
BasicType bt = L_TYPE;
|
|
MethodType type2 = bindArgumentType(mh, pos, bt);
|
|
LambdaForm form2 = bindArgumentForm(1+pos);
|
|
return mh.copyWithExtendL(type2, form2, value);
|
|
}
|
|
BoundMethodHandle bindArgumentI(BoundMethodHandle mh, int pos, int value) {
|
|
assert(mh.speciesData() == oldSpeciesData());
|
|
BasicType bt = I_TYPE;
|
|
MethodType type2 = bindArgumentType(mh, pos, bt);
|
|
LambdaForm form2 = bindArgumentForm(1+pos);
|
|
return mh.copyWithExtendI(type2, form2, value);
|
|
}
|
|
|
|
BoundMethodHandle bindArgumentJ(BoundMethodHandle mh, int pos, long value) {
|
|
assert(mh.speciesData() == oldSpeciesData());
|
|
BasicType bt = J_TYPE;
|
|
MethodType type2 = bindArgumentType(mh, pos, bt);
|
|
LambdaForm form2 = bindArgumentForm(1+pos);
|
|
return mh.copyWithExtendJ(type2, form2, value);
|
|
}
|
|
|
|
BoundMethodHandle bindArgumentF(BoundMethodHandle mh, int pos, float value) {
|
|
assert(mh.speciesData() == oldSpeciesData());
|
|
BasicType bt = F_TYPE;
|
|
MethodType type2 = bindArgumentType(mh, pos, bt);
|
|
LambdaForm form2 = bindArgumentForm(1+pos);
|
|
return mh.copyWithExtendF(type2, form2, value);
|
|
}
|
|
|
|
BoundMethodHandle bindArgumentD(BoundMethodHandle mh, int pos, double value) {
|
|
assert(mh.speciesData() == oldSpeciesData());
|
|
BasicType bt = D_TYPE;
|
|
MethodType type2 = bindArgumentType(mh, pos, bt);
|
|
LambdaForm form2 = bindArgumentForm(1+pos);
|
|
return mh.copyWithExtendD(type2, form2, value);
|
|
}
|
|
|
|
private MethodType bindArgumentType(BoundMethodHandle mh, int pos, BasicType bt) {
|
|
assert(mh.form.uncustomize() == lambdaForm);
|
|
assert(mh.form.names[1+pos].type == bt);
|
|
assert(BasicType.basicType(mh.type().parameterType(pos)) == bt);
|
|
return mh.type().dropParameterTypes(pos, pos+1);
|
|
}
|
|
|
|
/// Editing methods for lambda forms.
|
|
// Each editing method can (potentially) cache the edited LF so that it can be reused later.
|
|
|
|
LambdaForm bindArgumentForm(int pos) {
|
|
Transform key = Transform.of(Transform.Kind.BIND_ARG, pos);
|
|
LambdaForm form = getInCache(key);
|
|
if (form != null) {
|
|
assert(form.parameterConstraint(0) == newSpeciesData(lambdaForm.parameterType(pos)));
|
|
return form;
|
|
}
|
|
LambdaFormBuffer buf = buffer();
|
|
buf.startEdit();
|
|
|
|
BoundMethodHandle.SpeciesData oldData = oldSpeciesData();
|
|
BoundMethodHandle.SpeciesData newData = newSpeciesData(lambdaForm.parameterType(pos));
|
|
Name oldBaseAddress = lambdaForm.parameter(0); // BMH holding the values
|
|
Name newBaseAddress;
|
|
NamedFunction getter = newData.getterFunction(oldData.fieldCount());
|
|
|
|
if (pos != 0) {
|
|
// The newly created LF will run with a different BMH.
|
|
// Switch over any pre-existing BMH field references to the new BMH class.
|
|
buf.replaceFunctions(oldData.getterFunctions(), newData.getterFunctions(), oldBaseAddress);
|
|
newBaseAddress = oldBaseAddress.withConstraint(newData);
|
|
buf.renameParameter(0, newBaseAddress);
|
|
buf.replaceParameterByNewExpression(pos, new Name(getter, newBaseAddress));
|
|
} else {
|
|
// cannot bind the MH arg itself, unless oldData is empty
|
|
assert(oldData == BoundMethodHandle.SpeciesData.EMPTY);
|
|
newBaseAddress = new Name(L_TYPE).withConstraint(newData);
|
|
buf.replaceParameterByNewExpression(0, new Name(getter, newBaseAddress));
|
|
buf.insertParameter(0, newBaseAddress);
|
|
}
|
|
|
|
form = buf.endEdit();
|
|
return putInCache(key, form);
|
|
}
|
|
|
|
LambdaForm addArgumentForm(int pos, BasicType type) {
|
|
Transform key = Transform.of(Transform.Kind.ADD_ARG, pos, type.ordinal());
|
|
LambdaForm form = getInCache(key);
|
|
if (form != null) {
|
|
assert(form.arity == lambdaForm.arity+1);
|
|
assert(form.parameterType(pos) == type);
|
|
return form;
|
|
}
|
|
LambdaFormBuffer buf = buffer();
|
|
buf.startEdit();
|
|
|
|
buf.insertParameter(pos, new Name(type));
|
|
|
|
form = buf.endEdit();
|
|
return putInCache(key, form);
|
|
}
|
|
|
|
LambdaForm dupArgumentForm(int srcPos, int dstPos) {
|
|
Transform key = Transform.of(Transform.Kind.DUP_ARG, srcPos, dstPos);
|
|
LambdaForm form = getInCache(key);
|
|
if (form != null) {
|
|
assert(form.arity == lambdaForm.arity-1);
|
|
return form;
|
|
}
|
|
LambdaFormBuffer buf = buffer();
|
|
buf.startEdit();
|
|
|
|
assert(lambdaForm.parameter(srcPos).constraint == null);
|
|
assert(lambdaForm.parameter(dstPos).constraint == null);
|
|
buf.replaceParameterByCopy(dstPos, srcPos);
|
|
|
|
form = buf.endEdit();
|
|
return putInCache(key, form);
|
|
}
|
|
|
|
LambdaForm spreadArgumentsForm(int pos, Class<?> arrayType, int arrayLength) {
|
|
Class<?> elementType = arrayType.getComponentType();
|
|
Class<?> erasedArrayType = arrayType;
|
|
if (!elementType.isPrimitive())
|
|
erasedArrayType = Object[].class;
|
|
BasicType bt = basicType(elementType);
|
|
int elementTypeKey = bt.ordinal();
|
|
if (bt.basicTypeClass() != elementType) {
|
|
if (elementType.isPrimitive()) {
|
|
elementTypeKey = TYPE_LIMIT + Wrapper.forPrimitiveType(elementType).ordinal();
|
|
}
|
|
}
|
|
Transform key = Transform.of(Transform.Kind.SPREAD_ARGS, pos, elementTypeKey, arrayLength);
|
|
LambdaForm form = getInCache(key);
|
|
if (form != null) {
|
|
assert(form.arity == lambdaForm.arity - arrayLength + 1);
|
|
return form;
|
|
}
|
|
LambdaFormBuffer buf = buffer();
|
|
buf.startEdit();
|
|
|
|
assert(pos <= MethodType.MAX_JVM_ARITY);
|
|
assert(pos + arrayLength <= lambdaForm.arity);
|
|
assert(pos > 0); // cannot spread the MH arg itself
|
|
|
|
Name spreadParam = new Name(L_TYPE);
|
|
Name checkSpread = new Name(MethodHandleImpl.Lazy.NF_checkSpreadArgument, spreadParam, arrayLength);
|
|
|
|
// insert the new expressions
|
|
int exprPos = lambdaForm.arity();
|
|
buf.insertExpression(exprPos++, checkSpread);
|
|
// adjust the arguments
|
|
MethodHandle aload = MethodHandles.arrayElementGetter(erasedArrayType);
|
|
for (int i = 0; i < arrayLength; i++) {
|
|
Name loadArgument = new Name(aload, spreadParam, i);
|
|
buf.insertExpression(exprPos + i, loadArgument);
|
|
buf.replaceParameterByCopy(pos + i, exprPos + i);
|
|
}
|
|
buf.insertParameter(pos, spreadParam);
|
|
|
|
form = buf.endEdit();
|
|
return putInCache(key, form);
|
|
}
|
|
|
|
LambdaForm collectArgumentsForm(int pos, MethodType collectorType) {
|
|
int collectorArity = collectorType.parameterCount();
|
|
boolean dropResult = (collectorType.returnType() == void.class);
|
|
if (collectorArity == 1 && !dropResult) {
|
|
return filterArgumentForm(pos, basicType(collectorType.parameterType(0)));
|
|
}
|
|
BasicType[] newTypes = BasicType.basicTypes(collectorType.parameterList());
|
|
Transform.Kind kind = (dropResult
|
|
? Transform.Kind.COLLECT_ARGS_TO_VOID
|
|
: Transform.Kind.COLLECT_ARGS);
|
|
if (dropResult && collectorArity == 0) pos = 1; // pure side effect
|
|
Transform key = Transform.of(kind, pos, collectorArity, BasicType.basicTypesOrd(newTypes));
|
|
LambdaForm form = getInCache(key);
|
|
if (form != null) {
|
|
assert(form.arity == lambdaForm.arity - (dropResult ? 0 : 1) + collectorArity);
|
|
return form;
|
|
}
|
|
form = makeArgumentCombinationForm(pos, collectorType, false, dropResult);
|
|
return putInCache(key, form);
|
|
}
|
|
|
|
LambdaForm collectArgumentArrayForm(int pos, MethodHandle arrayCollector) {
|
|
MethodType collectorType = arrayCollector.type();
|
|
int collectorArity = collectorType.parameterCount();
|
|
assert(arrayCollector.intrinsicName() == Intrinsic.NEW_ARRAY);
|
|
Class<?> arrayType = collectorType.returnType();
|
|
Class<?> elementType = arrayType.getComponentType();
|
|
BasicType argType = basicType(elementType);
|
|
int argTypeKey = argType.ordinal();
|
|
if (argType.basicTypeClass() != elementType) {
|
|
// return null if it requires more metadata (like String[].class)
|
|
if (!elementType.isPrimitive())
|
|
return null;
|
|
argTypeKey = TYPE_LIMIT + Wrapper.forPrimitiveType(elementType).ordinal();
|
|
}
|
|
assert(collectorType.parameterList().equals(Collections.nCopies(collectorArity, elementType)));
|
|
Transform.Kind kind = Transform.Kind.COLLECT_ARGS_TO_ARRAY;
|
|
Transform key = Transform.of(kind, pos, collectorArity, argTypeKey);
|
|
LambdaForm form = getInCache(key);
|
|
if (form != null) {
|
|
assert(form.arity == lambdaForm.arity - 1 + collectorArity);
|
|
return form;
|
|
}
|
|
LambdaFormBuffer buf = buffer();
|
|
buf.startEdit();
|
|
|
|
assert(pos + 1 <= lambdaForm.arity);
|
|
assert(pos > 0); // cannot filter the MH arg itself
|
|
|
|
Name[] newParams = new Name[collectorArity];
|
|
for (int i = 0; i < collectorArity; i++) {
|
|
newParams[i] = new Name(pos + i, argType);
|
|
}
|
|
Name callCombiner = new Name(arrayCollector, (Object[]) /*...*/ newParams);
|
|
|
|
// insert the new expression
|
|
int exprPos = lambdaForm.arity();
|
|
buf.insertExpression(exprPos, callCombiner);
|
|
|
|
// insert new arguments
|
|
int argPos = pos + 1; // skip result parameter
|
|
for (Name newParam : newParams) {
|
|
buf.insertParameter(argPos++, newParam);
|
|
}
|
|
assert(buf.lastIndexOf(callCombiner) == exprPos+newParams.length);
|
|
buf.replaceParameterByCopy(pos, exprPos+newParams.length);
|
|
|
|
form = buf.endEdit();
|
|
return putInCache(key, form);
|
|
}
|
|
|
|
LambdaForm filterArgumentForm(int pos, BasicType newType) {
|
|
Transform key = Transform.of(Transform.Kind.FILTER_ARG, pos, newType.ordinal());
|
|
LambdaForm form = getInCache(key);
|
|
if (form != null) {
|
|
assert(form.arity == lambdaForm.arity);
|
|
assert(form.parameterType(pos) == newType);
|
|
return form;
|
|
}
|
|
|
|
BasicType oldType = lambdaForm.parameterType(pos);
|
|
MethodType filterType = MethodType.methodType(oldType.basicTypeClass(),
|
|
newType.basicTypeClass());
|
|
form = makeArgumentCombinationForm(pos, filterType, false, false);
|
|
return putInCache(key, form);
|
|
}
|
|
|
|
private LambdaForm makeArgumentCombinationForm(int pos,
|
|
MethodType combinerType,
|
|
boolean keepArguments, boolean dropResult) {
|
|
LambdaFormBuffer buf = buffer();
|
|
buf.startEdit();
|
|
int combinerArity = combinerType.parameterCount();
|
|
int resultArity = (dropResult ? 0 : 1);
|
|
|
|
assert(pos <= MethodType.MAX_JVM_ARITY);
|
|
assert(pos + resultArity + (keepArguments ? combinerArity : 0) <= lambdaForm.arity);
|
|
assert(pos > 0); // cannot filter the MH arg itself
|
|
assert(combinerType == combinerType.basicType());
|
|
assert(combinerType.returnType() != void.class || dropResult);
|
|
|
|
BoundMethodHandle.SpeciesData oldData = oldSpeciesData();
|
|
BoundMethodHandle.SpeciesData newData = newSpeciesData(L_TYPE);
|
|
|
|
// The newly created LF will run with a different BMH.
|
|
// Switch over any pre-existing BMH field references to the new BMH class.
|
|
Name oldBaseAddress = lambdaForm.parameter(0); // BMH holding the values
|
|
buf.replaceFunctions(oldData.getterFunctions(), newData.getterFunctions(), oldBaseAddress);
|
|
Name newBaseAddress = oldBaseAddress.withConstraint(newData);
|
|
buf.renameParameter(0, newBaseAddress);
|
|
|
|
Name getCombiner = new Name(newData.getterFunction(oldData.fieldCount()), newBaseAddress);
|
|
Object[] combinerArgs = new Object[1 + combinerArity];
|
|
combinerArgs[0] = getCombiner;
|
|
Name[] newParams;
|
|
if (keepArguments) {
|
|
newParams = new Name[0];
|
|
System.arraycopy(lambdaForm.names, pos + resultArity,
|
|
combinerArgs, 1, combinerArity);
|
|
} else {
|
|
newParams = new Name[combinerArity];
|
|
BasicType[] newTypes = basicTypes(combinerType.parameterList());
|
|
for (int i = 0; i < newTypes.length; i++) {
|
|
newParams[i] = new Name(pos + i, newTypes[i]);
|
|
}
|
|
System.arraycopy(newParams, 0,
|
|
combinerArgs, 1, combinerArity);
|
|
}
|
|
Name callCombiner = new Name(combinerType, combinerArgs);
|
|
|
|
// insert the two new expressions
|
|
int exprPos = lambdaForm.arity();
|
|
buf.insertExpression(exprPos+0, getCombiner);
|
|
buf.insertExpression(exprPos+1, callCombiner);
|
|
|
|
// insert new arguments, if needed
|
|
int argPos = pos + resultArity; // skip result parameter
|
|
for (Name newParam : newParams) {
|
|
buf.insertParameter(argPos++, newParam);
|
|
}
|
|
assert(buf.lastIndexOf(callCombiner) == exprPos+1+newParams.length);
|
|
if (!dropResult) {
|
|
buf.replaceParameterByCopy(pos, exprPos+1+newParams.length);
|
|
}
|
|
|
|
return buf.endEdit();
|
|
}
|
|
|
|
LambdaForm filterReturnForm(BasicType newType, boolean constantZero) {
|
|
Transform.Kind kind = (constantZero ? Transform.Kind.FILTER_RETURN_TO_ZERO : Transform.Kind.FILTER_RETURN);
|
|
Transform key = Transform.of(kind, newType.ordinal());
|
|
LambdaForm form = getInCache(key);
|
|
if (form != null) {
|
|
assert(form.arity == lambdaForm.arity);
|
|
assert(form.returnType() == newType);
|
|
return form;
|
|
}
|
|
LambdaFormBuffer buf = buffer();
|
|
buf.startEdit();
|
|
|
|
int insPos = lambdaForm.names.length;
|
|
Name callFilter;
|
|
if (constantZero) {
|
|
// Synthesize a constant zero value for the given type.
|
|
if (newType == V_TYPE)
|
|
callFilter = null;
|
|
else
|
|
callFilter = new Name(constantZero(newType));
|
|
} else {
|
|
BoundMethodHandle.SpeciesData oldData = oldSpeciesData();
|
|
BoundMethodHandle.SpeciesData newData = newSpeciesData(L_TYPE);
|
|
|
|
// The newly created LF will run with a different BMH.
|
|
// Switch over any pre-existing BMH field references to the new BMH class.
|
|
Name oldBaseAddress = lambdaForm.parameter(0); // BMH holding the values
|
|
buf.replaceFunctions(oldData.getterFunctions(), newData.getterFunctions(), oldBaseAddress);
|
|
Name newBaseAddress = oldBaseAddress.withConstraint(newData);
|
|
buf.renameParameter(0, newBaseAddress);
|
|
|
|
Name getFilter = new Name(newData.getterFunction(oldData.fieldCount()), newBaseAddress);
|
|
buf.insertExpression(insPos++, getFilter);
|
|
BasicType oldType = lambdaForm.returnType();
|
|
if (oldType == V_TYPE) {
|
|
MethodType filterType = MethodType.methodType(newType.basicTypeClass());
|
|
callFilter = new Name(filterType, getFilter);
|
|
} else {
|
|
MethodType filterType = MethodType.methodType(newType.basicTypeClass(), oldType.basicTypeClass());
|
|
callFilter = new Name(filterType, getFilter, lambdaForm.names[lambdaForm.result]);
|
|
}
|
|
}
|
|
|
|
if (callFilter != null)
|
|
buf.insertExpression(insPos++, callFilter);
|
|
buf.setResult(callFilter);
|
|
|
|
form = buf.endEdit();
|
|
return putInCache(key, form);
|
|
}
|
|
|
|
LambdaForm foldArgumentsForm(int foldPos, boolean dropResult, MethodType combinerType) {
|
|
int combinerArity = combinerType.parameterCount();
|
|
Transform.Kind kind = (dropResult ? Transform.Kind.FOLD_ARGS_TO_VOID : Transform.Kind.FOLD_ARGS);
|
|
Transform key = Transform.of(kind, foldPos, combinerArity);
|
|
LambdaForm form = getInCache(key);
|
|
if (form != null) {
|
|
assert(form.arity == lambdaForm.arity - (kind == Transform.Kind.FOLD_ARGS ? 1 : 0));
|
|
return form;
|
|
}
|
|
form = makeArgumentCombinationForm(foldPos, combinerType, true, dropResult);
|
|
return putInCache(key, form);
|
|
}
|
|
|
|
LambdaForm permuteArgumentsForm(int skip, int[] reorder) {
|
|
assert(skip == 1); // skip only the leading MH argument, names[0]
|
|
int length = lambdaForm.names.length;
|
|
int outArgs = reorder.length;
|
|
int inTypes = 0;
|
|
boolean nullPerm = true;
|
|
for (int i = 0; i < reorder.length; i++) {
|
|
int inArg = reorder[i];
|
|
if (inArg != i) nullPerm = false;
|
|
inTypes = Math.max(inTypes, inArg+1);
|
|
}
|
|
assert(skip + reorder.length == lambdaForm.arity);
|
|
if (nullPerm) return lambdaForm; // do not bother to cache
|
|
Transform key = Transform.of(Transform.Kind.PERMUTE_ARGS, reorder);
|
|
LambdaForm form = getInCache(key);
|
|
if (form != null) {
|
|
assert(form.arity == skip+inTypes) : form;
|
|
return form;
|
|
}
|
|
|
|
BasicType[] types = new BasicType[inTypes];
|
|
for (int i = 0; i < outArgs; i++) {
|
|
int inArg = reorder[i];
|
|
types[inArg] = lambdaForm.names[skip + i].type;
|
|
}
|
|
assert (skip + outArgs == lambdaForm.arity);
|
|
assert (permutedTypesMatch(reorder, types, lambdaForm.names, skip));
|
|
int pos = 0;
|
|
while (pos < outArgs && reorder[pos] == pos) {
|
|
pos += 1;
|
|
}
|
|
Name[] names2 = new Name[length - outArgs + inTypes];
|
|
System.arraycopy(lambdaForm.names, 0, names2, 0, skip + pos);
|
|
int bodyLength = length - lambdaForm.arity;
|
|
System.arraycopy(lambdaForm.names, skip + outArgs, names2, skip + inTypes, bodyLength);
|
|
int arity2 = names2.length - bodyLength;
|
|
int result2 = lambdaForm.result;
|
|
if (result2 >= skip) {
|
|
if (result2 < skip + outArgs) {
|
|
result2 = reorder[result2 - skip] + skip;
|
|
} else {
|
|
result2 = result2 - outArgs + inTypes;
|
|
}
|
|
}
|
|
for (int j = pos; j < outArgs; j++) {
|
|
Name n = lambdaForm.names[skip + j];
|
|
int i = reorder[j];
|
|
Name n2 = names2[skip + i];
|
|
if (n2 == null) {
|
|
names2[skip + i] = n2 = new Name(types[i]);
|
|
} else {
|
|
assert (n2.type == types[i]);
|
|
}
|
|
for (int k = arity2; k < names2.length; k++) {
|
|
names2[k] = names2[k].replaceName(n, n2);
|
|
}
|
|
}
|
|
for (int i = skip + pos; i < arity2; i++) {
|
|
if (names2[i] == null) {
|
|
names2[i] = argument(i, types[i - skip]);
|
|
}
|
|
}
|
|
for (int j = lambdaForm.arity; j < lambdaForm.names.length; j++) {
|
|
int i = j - lambdaForm.arity + arity2;
|
|
Name n = lambdaForm.names[j];
|
|
Name n2 = names2[i];
|
|
if (n != n2) {
|
|
for (int k = i + 1; k < names2.length; k++) {
|
|
names2[k] = names2[k].replaceName(n, n2);
|
|
}
|
|
}
|
|
}
|
|
|
|
form = new LambdaForm(lambdaForm.debugName, arity2, names2, result2);
|
|
return putInCache(key, form);
|
|
}
|
|
|
|
static boolean permutedTypesMatch(int[] reorder, BasicType[] types, Name[] names, int skip) {
|
|
for (int i = 0; i < reorder.length; i++) {
|
|
assert (names[skip + i].isParam());
|
|
assert (names[skip + i].type == types[reorder[i]]);
|
|
}
|
|
return true;
|
|
}
|
|
}
|