Skip to content

Commit

Permalink
Add support for complex filter value adaptations
Browse files Browse the repository at this point in the history
  • Loading branch information
mcimadamore committed May 20, 2020
1 parent 0dc2fed commit 768ce55
Show file tree
Hide file tree
Showing 6 changed files with 190 additions and 23 deletions.
42 changes: 42 additions & 0 deletions src/java.base/share/classes/java/lang/invoke/LambdaFormEditor.java
Expand Up @@ -922,6 +922,48 @@ LambdaForm filterReturnForm(BasicType newType, boolean constantZero) {
return putInCache(key, form);
}

LambdaForm collectReturnValueForm(MethodType combinerType) {
LambdaFormBuffer buf = buffer();
buf.startEdit();
int combinerArity = combinerType.parameterCount();
int argPos = lambdaForm.arity();
int exprPos = lambdaForm.names.length;

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[combinerArity + 1];
combinerArgs[0] = getCombiner;
Name[] newParams = new Name[combinerArity - 1]; // last combiner parameter is the return adapter
for (int i = 0; i < newParams.length; i++) {
newParams[i] = new Name(argPos + i, basicType(combinerType.parameterType(i)));
}
System.arraycopy(newParams, 0,
combinerArgs, 1, combinerArity - 1);
combinerArgs[combinerArity] = buf.name(lambdaForm.names.length - 1);
Name callCombiner = new Name(combinerType, combinerArgs);

buf.insertExpression(exprPos, getCombiner);
buf.insertExpression(exprPos + 1, callCombiner);

// insert the two new expressions
int insPos = argPos;
for (Name newParam : newParams) {
buf.insertParameter(insPos++, newParam);
}

buf.setResult(callCombiner);
return buf.endEdit();
}

LambdaForm foldArgumentsForm(int foldPos, boolean dropResult, MethodType combinerType) {
int combinerArity = combinerType.parameterCount();
byte kind = (dropResult ? Transform.FOLD_ARGS_TO_VOID : Transform.FOLD_ARGS);
Expand Down
34 changes: 34 additions & 0 deletions src/java.base/share/classes/java/lang/invoke/MethodHandles.java
Expand Up @@ -5474,6 +5474,40 @@ private static void filterReturnValueChecks(MethodType targetType, MethodType fi
throw newIllegalArgumentException("target and filter types do not match", targetType, filterType);
}

/**
* Filter the return value of a target method handle with a filter function. The filter function is
* applied to the return value of the original handle; if the filter specifies more than one parameters,
* then any remaining parameter is appended to the adapter handle. In other words, the adaptation works
* as follows:
* <blockquote><pre>{@code
* T target(A...)
* V filter(B... , T)
* V adapter(A... a, B... b) {
* T t = target(a...);
* return filter(b..., t);
* }</pre></blockquote>
* <p>
* If the filter handle is a unary function, then this method behaves like {@link #filterReturnValue(MethodHandle, MethodHandle)}.
*
* @param target the target method handle
* @param filter the filter method handle
* @return the adapter method handle
*/
/* package */ static MethodHandle collectReturnValue(MethodHandle target, MethodHandle filter) {
MethodType targetType = target.type();
MethodType filterType = filter.type();
BoundMethodHandle result = target.rebind();
LambdaForm lform = result.editor().collectReturnValueForm(filterType.basicType());
MethodType newType = targetType.changeReturnType(filterType.returnType());
if (filterType.parameterList().size() > 1) {
for (int i = 0 ; i < filterType.parameterList().size() - 1 ; i++) {
newType = newType.appendParameterTypes(filterType.parameterType(i));
}
}
result = result.copyWithExtendL(newType, lform, filter);
return result;
}

/**
* Adapts a target method handle by pre-processing
* some of its arguments, and then calling the target with
Expand Down
84 changes: 69 additions & 15 deletions src/java.base/share/classes/java/lang/invoke/VarHandles.java
Expand Up @@ -356,43 +356,97 @@ public static VarHandle filterValue(VarHandle target, MethodHandle filterToTarge
noCheckedExceptions(filterToTarget);
noCheckedExceptions(filterFromTarget);

List<Class<?>> newCoordinates = new ArrayList<>();
List<Class<?>> additionalCoordinates = new ArrayList<>();
newCoordinates.addAll(target.coordinateTypes());

//check that from/to filters have right signatures
if (filterFromTarget.type().parameterCount() != 1) {
if (filterFromTarget.type().parameterCount() != filterToTarget.type().parameterCount()) {
throw newIllegalArgumentException("filterFromTarget and filterToTarget have different arity", filterFromTarget.type(), filterToTarget.type());
} else if (filterFromTarget.type().parameterCount() < 1) {
throw newIllegalArgumentException("filterFromTarget filter type has wrong arity", filterFromTarget.type());
} else if (filterToTarget.type().parameterCount() != 1) {
} else if (filterToTarget.type().parameterCount() < 1) {
throw newIllegalArgumentException("filterToTarget filter type has wrong arity", filterFromTarget.type());
} else if (filterFromTarget.type().parameterType(0) != filterToTarget.type().returnType() ||
filterToTarget.type().parameterType(0) != filterFromTarget.type().returnType()) {
} else if (filterFromTarget.type().lastParameterType() != filterToTarget.type().returnType() ||
filterToTarget.type().lastParameterType() != filterFromTarget.type().returnType()) {
throw newIllegalArgumentException("filterFromTarget and filterToTarget filter types do not match", filterFromTarget.type(), filterToTarget.type());
} else if (target.varType() != filterFromTarget.type().parameterType(0)) {
} else if (target.varType() != filterFromTarget.type().lastParameterType()) {
throw newIllegalArgumentException("filterFromTarget filter type does not match target var handle type", filterFromTarget.type(), target.varType());
} else if (target.varType() != filterToTarget.type().returnType()) {
throw newIllegalArgumentException("filterFromTarget filter type does not match target var handle type", filterToTarget.type(), target.varType());
} else if (filterFromTarget.type().parameterCount() > 1) {
for (int i = 0 ; i < filterFromTarget.type().parameterCount() - 1 ; i++) {
if (filterFromTarget.type().parameterType(i) != filterToTarget.type().parameterType(i)) {
throw newIllegalArgumentException("filterFromTarget and filterToTarget filter types do not match", filterFromTarget.type(), filterToTarget.type());
} else {
newCoordinates.add(filterFromTarget.type().parameterType(i));
additionalCoordinates.add((filterFromTarget.type().parameterType(i)));
}
}
}

return new IndirectVarHandle(target, filterFromTarget.type().returnType(), target.coordinateTypes().toArray(new Class<?>[0]),
return new IndirectVarHandle(target, filterFromTarget.type().returnType(), newCoordinates.toArray(new Class<?>[0]),
(mode, modeHandle) -> {
int lastParameterPos = modeHandle.type().parameterCount() - 1;
return switch (mode.at) {
case GET -> MethodHandles.filterReturnValue(modeHandle, filterFromTarget);
case SET -> MethodHandles.filterArgument(modeHandle, lastParameterPos, filterToTarget);
case GET -> MethodHandles.collectReturnValue(modeHandle, filterFromTarget);
case SET -> MethodHandles.collectArguments(modeHandle, lastParameterPos, filterToTarget);
case GET_AND_UPDATE -> {
MethodHandle adapter = MethodHandles.filterReturnValue(modeHandle, filterFromTarget);
yield MethodHandles.filterArgument(adapter, lastParameterPos, filterToTarget);
MethodHandle adapter = MethodHandles.collectReturnValue(modeHandle, filterFromTarget);
MethodHandle res = MethodHandles.collectArguments(adapter, lastParameterPos, filterToTarget);
if (additionalCoordinates.size() > 0) {
res = joinDuplicateArgs(res, lastParameterPos,
lastParameterPos + additionalCoordinates.size() + 1,
additionalCoordinates.size());
}
yield res;
}
case COMPARE_AND_EXCHANGE -> {
MethodHandle adapter = MethodHandles.filterReturnValue(modeHandle, filterFromTarget);
adapter = MethodHandles.filterArgument(adapter, lastParameterPos, filterToTarget);
yield MethodHandles.filterArgument(adapter, lastParameterPos - 1, filterToTarget);
MethodHandle adapter = MethodHandles.collectReturnValue(modeHandle, filterFromTarget);
adapter = MethodHandles.collectArguments(adapter, lastParameterPos, filterToTarget);
if (additionalCoordinates.size() > 0) {
adapter = joinDuplicateArgs(adapter, lastParameterPos,
lastParameterPos + additionalCoordinates.size() + 1,
additionalCoordinates.size());
}
MethodHandle res = MethodHandles.collectArguments(adapter, lastParameterPos - 1, filterToTarget);
if (additionalCoordinates.size() > 0) {
res = joinDuplicateArgs(res, lastParameterPos - 1,
lastParameterPos + additionalCoordinates.size(),
additionalCoordinates.size());
}
yield res;
}
case COMPARE_AND_SET -> {
MethodHandle adapter = MethodHandles.filterArgument(modeHandle, lastParameterPos, filterToTarget);
yield MethodHandles.filterArgument(adapter, lastParameterPos - 1, filterToTarget);
MethodHandle adapter = MethodHandles.collectArguments(modeHandle, lastParameterPos, filterToTarget);
MethodHandle res = MethodHandles.collectArguments(adapter, lastParameterPos - 1, filterToTarget);
if (additionalCoordinates.size() > 0) {
res = joinDuplicateArgs(res, lastParameterPos - 1,
lastParameterPos + additionalCoordinates.size(),
additionalCoordinates.size());
}
yield res;
}
};
});
}

private static MethodHandle joinDuplicateArgs(MethodHandle handle, int originalStart, int dropStart, int length) {
int[] perms = new int[handle.type().parameterCount()];
for (int i = 0 ; i < dropStart; i++) {
perms[i] = i;
}
for (int i = 0 ; i < length ; i++) {
perms[dropStart + i] = originalStart + i;
}
for (int i = dropStart + length ; i < perms.length ; i++) {
perms[i] = i - length;
}
return MethodHandles.permuteArguments(handle,
handle.type().dropParameterTypes(dropStart, dropStart + length),
perms);
}

public static VarHandle filterCoordinates(VarHandle target, int pos, MethodHandle... filters) {
Objects.nonNull(target);
Objects.nonNull(filters);
Expand Down
Expand Up @@ -416,19 +416,20 @@ public static VarHandle asUnsigned(VarHandle target, final Class<?> adaptedType)
}

/**
* Adapts a target var handle by pre-processing incoming and outgoing values using a pair of unary filter functions.
* Adapts a target var handle by pre-processing incoming and outgoing values using a pair of filter functions.
* <p>
* When calling e.g. {@link VarHandle#set(Object...)} on the resulting var handle, the incoming value (of type {@code T}, where
* {@code T} is the parameter type of the first filter function) is processed using the first filter and then passed
* {@code T} is the <em>last</em> parameter type of the first filter function) is processed using the first filter and then passed
* to the target var handle.
* Conversely, when calling e.g. {@link VarHandle#get(Object...)} on the resulting var handle, the return value obtained from
* the target var handle (of type {@code T}, where {@code T} is the parameter type of the second filter function)
* the target var handle (of type {@code T}, where {@code T} is the <em>last</em> parameter type of the second filter function)
* is processed using the second filter and returned to the caller. More advanced access mode types, such as
* {@link java.lang.invoke.VarHandle.AccessMode#COMPARE_AND_EXCHANGE} might apply both filters at the same time.
* <p>
* For the boxing and unboxing filters to be well formed, their types must be of the form {@code S -> T} and {@code T -> S},
* respectively, where {@code T} is the type of the target var handle. If this is the case, the resulting var handle will
* have type {@code S}.
* For the boxing and unboxing filters to be well formed, their types must be of the form {@code (A... , S) -> T} and
* {@code (A... , T) -> S}, respectively, where {@code T} is the type of the target var handle. If this is the case,
* the resulting var handle will have type {@code S} and will feature the additional coordinates {@code A...} (which
* will be appended to the coordinates of the target var handle).
* <p>
* The resulting var handle will feature the same access modes (see {@link java.lang.invoke.VarHandle.AccessMode} and
* atomic access guarantees as those featured by the target var handle.
Expand All @@ -439,7 +440,7 @@ public static VarHandle asUnsigned(VarHandle target, final Class<?> adaptedType)
* @return an adapter var handle which accepts a new type, performing the provided boxing/unboxing conversions.
* @throws NullPointerException if either {@code target}, {@code filterToTarget} or {@code filterFromTarget} are {@code == null}.
* @throws IllegalArgumentException if {@code filterFromTarget} and {@code filterToTarget} are not well-formed, that is, they have types
* other than {@code S -> T} and {@code T -> S}, respectively, where {@code T} is the type of the target var handle,
* other than {@code (A... , S) -> T} and {@code (A... , T) -> S}, respectively, where {@code T} is the type of the target var handle,
* or if either {@code filterFromTarget} or {@code filterToTarget} throws any checked exceptions.
*/
public static VarHandle filterValue(VarHandle target, MethodHandle filterToTarget, MethodHandle filterFromTarget) {
Expand Down
Expand Up @@ -447,7 +447,7 @@ public static AbstractMemorySegmentImpl ofBuffer(ByteBuffer bb) {
}
}

public static AbstractMemorySegmentImpl NOTHING = new AbstractMemorySegmentImpl(
public static final AbstractMemorySegmentImpl NOTHING = new AbstractMemorySegmentImpl(
0, 0, MemoryScope.createUnchecked(null, null, null)
) {
@Override
Expand Down
36 changes: 36 additions & 0 deletions test/jdk/java/foreign/TestAdaptVarHandles.java
Expand Up @@ -49,6 +49,7 @@ public class TestAdaptVarHandles {

static MethodHandle S2I;
static MethodHandle I2S;
static MethodHandle CTX_I2S;
static MethodHandle O2I;
static MethodHandle I2O;
static MethodHandle S2L;
Expand All @@ -63,6 +64,8 @@ public class TestAdaptVarHandles {
try {
S2I = MethodHandles.lookup().findStatic(TestAdaptVarHandles.class, "stringToInt", MethodType.methodType(int.class, String.class));
I2S = MethodHandles.lookup().findStatic(TestAdaptVarHandles.class, "intToString", MethodType.methodType(String.class, int.class));
CTX_I2S = MethodHandles.lookup().findStatic(TestAdaptVarHandles.class, "ctxIntToString",
MethodType.methodType(String.class, String.class, String.class, int.class));
O2I = MethodHandles.explicitCastArguments(S2I, MethodType.methodType(int.class, Object.class));
I2O = MethodHandles.explicitCastArguments(I2S, MethodType.methodType(Object.class, int.class));
S2L = MethodHandles.lookup().findStatic(TestAdaptVarHandles.class, "stringToLong", MethodType.methodType(long.class, String.class));
Expand Down Expand Up @@ -102,6 +105,27 @@ public void testFilterValue() throws Throwable {
assertEquals(value, "42");
}

@Test
public void testFilterValueComposite() throws Throwable {
ValueLayout layout = MemoryLayouts.JAVA_INT;
MemorySegment segment = MemorySegment.allocateNative(layout);
VarHandle intHandle = layout.varHandle(int.class);
MethodHandle CTX_S2I = MethodHandles.dropArguments(S2I, 0, String.class, String.class);
VarHandle i2SHandle = MemoryHandles.filterValue(intHandle, CTX_S2I, CTX_I2S);
i2SHandle = MemoryHandles.insertCoordinates(i2SHandle, 1, "a", "b");
i2SHandle.set(segment.baseAddress(), "1");
String oldValue = (String)i2SHandle.getAndAdd(segment.baseAddress(), "42");
assertEquals(oldValue, "ab1");
String value = (String)i2SHandle.get(segment.baseAddress());
assertEquals(value, "ab43");
boolean swapped = (boolean)i2SHandle.compareAndSet(segment.baseAddress(), "43", "12");
assertTrue(swapped);
oldValue = (String)i2SHandle.compareAndExchange(segment.baseAddress(), "12", "42");
assertEquals(oldValue, "ab12");
value = (String)i2SHandle.toMethodHandle(VarHandle.AccessMode.GET).invokeExact(segment.baseAddress());
assertEquals(value, "ab42");
}

@Test
public void testFilterValueLoose() throws Throwable {
ValueLayout layout = MemoryLayouts.JAVA_INT;
Expand Down Expand Up @@ -156,6 +180,14 @@ public void testBadFilterBoxArity() {
MemoryHandles.filterValue(intHandle, S2I, I2S.bindTo(42));
}

@Test(expectedExceptions = IllegalArgumentException.class)
public void testBadFilterBoxPrefixCoordinates() {
VarHandle intHandle = MemoryLayouts.JAVA_INT.varHandle(int.class);
MemoryHandles.filterValue(intHandle,
MethodHandles.dropArguments(S2I, 1, int.class),
MethodHandles.dropArguments(I2S, 1, long.class));
}

@Test(expectedExceptions = IllegalArgumentException.class)
public void testBadFilterBoxException() {
VarHandle intHandle = MemoryLayouts.JAVA_INT.varHandle(int.class);
Expand Down Expand Up @@ -484,4 +516,8 @@ static long sumOffsets(long l1, long l2) {
}

static void void_filter(String s) { }

static String ctxIntToString(String a, String b, int i) {
return a + b + String.valueOf(i);
}
}

0 comments on commit 768ce55

Please sign in to comment.