Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

8245459: Add support for complex filter value var handle adaptation #179

Closed
Closed
Changes from all commits
Commits
File filter...
Filter file types
Jump to…
Jump to file
Failed to load files.

Always

Just for now

@@ -922,6 +922,57 @@ 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);

// Now we set up the call to the filter
Name getCombiner = new Name(newData.getterFunction(oldData.fieldCount()), newBaseAddress);

Object[] combinerArgs = new Object[combinerArity + 1];
combinerArgs[0] = getCombiner; // first (synthetic) argument should be the MH that acts as a target of the invoke

// set up additional adapter parameters (in case the combiner is not a unary function)
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)));
}

// set up remaining filter parameters to point to the corresponding adapter parameters (see above)
System.arraycopy(newParams, 0,
combinerArgs, 1, combinerArity - 1);

// the last filter argument is set to point at the result of the target method handle
combinerArgs[combinerArity] = buf.name(lambdaForm.names.length - 1);
Name callCombiner = new Name(combinerType, combinerArgs);

// insert the two new expressions
buf.insertExpression(exprPos, getCombiner);
buf.insertExpression(exprPos + 1, callCombiner);

// insert additional arguments
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);
@@ -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

This comment has been minimized.

@PaulSandoz

PaulSandoz May 21, 2020
Member

Suggested improvement to JavaDoc:

Adapts a target method handle by post-processing
its return value and additional arguments (if any) with a filter 
(another method handle).
The result of the filter is returned from the adapter.

If the target returns a value, the filter must accept that value as
as the trailing argument, with the additional arguments proceeding that.
If the target returns void, the filter must accept only the additional arguments.

The return type of the filter
replaces the return type of the target
in the resulting adapted method handle.

The trailing argument type of the filter (if any) must be identical to the
return type of the target.

The additional argument types of the filter (if any) are appended (in order) 
to the argument types of the target method handle and become the argument
types of the resulting adapted method handle.

Here is pseudocode for the resulting adapter. In the code,
{@code T}/{@code t} represent the result type and value of the
{@code target}; {@code V}, the result type of the {@code filter};
{@code A}/{@code a}, the types and values of the parameters and arguments
of the {@code target} as well as the resulting adapter.
{@code B}/{@code b}, the types and values of the additional parameters and arguments
of the {@code filter} as well as appended to the resulting adapter.
...

This comment has been minimized.

@mcimadamore

mcimadamore May 21, 2020
Author Collaborator

Thanks - I'll see how much work it is to add support of the void case - hopefully it won't be too bad.

* 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) {

This comment has been minimized.

@PaulSandoz

PaulSandoz May 21, 2020
Member

Do you need to check that the type of last parameter of the filter function is equal to the return type of the target?
Filter parameters size constraints > 0
Can the filter return void?

This comment has been minimized.

@mcimadamore

mcimadamore May 21, 2020
Author Collaborator

yeah - ideally I'd need these checks - in reality the checks are performed on the VH side for now, and this is not exposed publicly, but I can add all required checks.

As for void, yes, for consistency with filterReturnValue we should eventually support that too - but it wasn't the goal of this patch to provide a full blown MH adapter; I have implemented what was necessary for the VH adaptation. I can add the other parts (but would prefer to deal with that separately).

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
@@ -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);
@@ -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.
@@ -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) {
@@ -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
@@ -49,6 +49,7 @@

static MethodHandle S2I;
static MethodHandle I2S;
static MethodHandle CTX_I2S;
static MethodHandle O2I;
static MethodHandle I2O;
static MethodHandle S2L;
@@ -63,6 +64,8 @@
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));
@@ -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;
@@ -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);
@@ -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);
}
}
ProTip! Use n and p to navigate between commits in a pull request.