Skip to content
This repository has been archived by the owner. It is now read-only.
Permalink
Browse files
8246095: Tweaks to memory access API
Add more user friendly API points to the foreign memory acesss API

Reviewed-by: chegar, psandoz
  • Loading branch information
ChrisHegarty authored and mcimadamore committed Jun 3, 2020
1 parent eec7750 commit f1e1cb7055e4d062665fca22a157dc3d89cbbecd
Showing with 1,630 additions and 181 deletions.
  1. +51 −0 src/java.base/share/classes/java/lang/invoke/LambdaFormEditor.java
  2. +34 −0 src/java.base/share/classes/java/lang/invoke/MethodHandles.java
  3. +86 −26 src/java.base/share/classes/java/lang/invoke/VarHandles.java
  4. +26 −0 src/java.base/share/classes/jdk/internal/util/ArraysSupport.java
  5. +2 −0 src/java.base/share/classes/module-info.java
  6. +1 −1 src/jdk.incubator.foreign/share/classes/jdk/incubator/foreign/MappedMemorySegment.java
  7. +0 −27 src/jdk.incubator.foreign/share/classes/jdk/incubator/foreign/MemoryAddress.java
  8. +132 −8 src/jdk.incubator.foreign/share/classes/jdk/incubator/foreign/MemoryHandles.java
  9. +24 −4 src/jdk.incubator.foreign/share/classes/jdk/incubator/foreign/MemoryLayout.java
  10. +109 −16 src/jdk.incubator.foreign/share/classes/jdk/incubator/foreign/MemorySegment.java
  11. +63 −8 src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/AbstractMemorySegmentImpl.java
  12. +4 −3 src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/MappedMemorySegmentImpl.java
  13. +59 −0 test/jdk/java/foreign/TestAdaptVarHandles.java
  14. +84 −30 test/jdk/java/foreign/TestByteBuffer.java
  15. +106 −24 test/jdk/java/foreign/TestLayoutPaths.java
  16. +1 −1 test/jdk/java/foreign/TestMemoryCopy.java
  17. +251 −0 test/jdk/java/foreign/TestMemoryHandleAsUnsigned.java
  18. +242 −0 test/jdk/java/foreign/TestMismatch.java
  19. +2 −4 test/jdk/java/foreign/TestNative.java
  20. +93 −21 test/jdk/java/foreign/TestSegments.java
  21. +1 −3 test/jdk/java/foreign/TestSpliterator.java
  22. +3 −4 test/jdk/java/util/stream/test/org/openjdk/tests/java/util/stream/SegmentTestDataProvider.java
  23. +92 −0 test/micro/org/openjdk/bench/jdk/incubator/foreign/BulkOps.java
  24. +1 −1 test/micro/org/openjdk/bench/jdk/incubator/foreign/LoopOverNonConstantMapped.java
  25. +163 −0 test/micro/org/openjdk/bench/jdk/incubator/foreign/TestAdaptVarHandles.java
@@ -989,6 +989,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 ? FOLD_ARGS_TO_VOID : FOLD_ARGS);
@@ -5539,6 +5539,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
@@ -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);
@@ -542,17 +596,23 @@ public static VarHandle dropCoordinates(VarHandle target, int pos, Class<?>... v
private static void noCheckedExceptions(MethodHandle handle) {
if (handle instanceof DirectMethodHandle) {
DirectMethodHandle directHandle = (DirectMethodHandle)handle;
MethodHandleInfo info = MethodHandles.Lookup.IMPL_LOOKUP.revealDirect(directHandle);
Class<?>[] exceptionTypes = switch (info.getReferenceKind()) {
case MethodHandleInfo.REF_invokeInterface, MethodHandleInfo.REF_invokeSpecial,
MethodHandleInfo.REF_invokeStatic, MethodHandleInfo.REF_invokeVirtual ->
info.reflectAs(Method.class, MethodHandles.Lookup.IMPL_LOOKUP).getExceptionTypes();
case MethodHandleInfo.REF_newInvokeSpecial ->
info.reflectAs(Constructor.class, MethodHandles.Lookup.IMPL_LOOKUP).getExceptionTypes();
case MethodHandleInfo.REF_getField, MethodHandleInfo.REF_getStatic,
MethodHandleInfo.REF_putField, MethodHandleInfo.REF_putStatic -> null;
default -> throw new AssertionError("Cannot get here");
};
byte refKind = directHandle.member.getReferenceKind();
MethodHandleInfo info = new InfoFromMemberName(
MethodHandles.Lookup.IMPL_LOOKUP,
directHandle.member,
refKind);
final Class<?>[] exceptionTypes;
if (MethodHandleNatives.refKindIsMethod(refKind)) {
exceptionTypes = info.reflectAs(Method.class, MethodHandles.Lookup.IMPL_LOOKUP)
.getExceptionTypes();
} else if (MethodHandleNatives.refKindIsField(refKind)) {
exceptionTypes = null;
} else if (MethodHandleNatives.refKindIsConstructor(refKind)) {
exceptionTypes = info.reflectAs(Constructor.class, MethodHandles.Lookup.IMPL_LOOKUP)
.getExceptionTypes();
} else {
throw new AssertionError("Cannot get here");
}
if (exceptionTypes != null) {
if (Stream.of(exceptionTypes).anyMatch(VarHandles::isCheckedException)) {
throw newIllegalArgumentException("Cannot adapt a var handle with a method handle which throws checked exceptions");
@@ -160,6 +160,32 @@ public static int vectorizedMismatch(Object a, long aOffset,
}
}

/**
* Mismatch over long lengths.
*/
public static long vectorizedMismatchLarge(Object a, long aOffset,
Object b, long bOffset,
long length,
int log2ArrayIndexScale) {
long off = 0;
long remaining = length;
int i ;
while (remaining > 7) {
int size = (int) Math.min(Integer.MAX_VALUE, remaining);
i = vectorizedMismatch(
a, aOffset + off,
b, bOffset + off,
size, log2ArrayIndexScale);
if (i >= 0)
return off + i;

i = size - ~i;
off += i;
remaining -= i;
}
return ~off;
}

// Booleans
// Each boolean element takes up one byte

@@ -231,6 +231,8 @@
jdk.internal.vm.ci,
jdk.incubator.foreign,
jdk.unsupported;
exports jdk.internal.util to
jdk.incubator.foreign;
exports jdk.internal.util.jar to
jdk.jartool;
exports jdk.internal.util.xml to
@@ -31,7 +31,7 @@
/**
* A mapped memory segment, that is, a memory segment backed by memory-mapped file.
*
* <p> Mapped memory segments are created via the {@link MemorySegment#mapFromPath(Path, long, FileChannel.MapMode)}.
* <p> Mapped memory segments are created via the {@link MemorySegment#mapFromPath(Path, long, long, FileChannel.MapMode)}.
* Mapped memory segments behave like ordinary segments, but provide additional capabilities to manipulate memory-mapped
* memory regions, such as {@link #force()} and {@link #load()}.
* <p>
@@ -115,33 +115,6 @@ public interface MemoryAddress {
@Override
int hashCode();

/**
* Perform bulk copy from source address to target address. More specifically, the bytes at addresses {@code src}
* through {@code src.addOffset(bytes - 1)} are copied into addresses {@code dst} through {@code dst.addOffset(bytes - 1)}.
* If the source and address ranges overlap, then the copying is performed as if the bytes at addresses {@code src}
* through {@code src.addOffset(bytes - 1)} were first copied into a temporary segment with size {@code bytes},
* and then the contents of the temporary segment were copied into the bytes at addresses {@code dst} through
* {@code dst.addOffset(bytes - 1)}.
* <p>
* The result of a bulk copy is unspecified if, in the uncommon case, the source and target address ranges do not
* overlap, but refer to overlapping regions of the same backing storage using different addresses. For example,
* this may occur if the same file is {@link MemorySegment#mapFromPath mapped} to two segments.
*
* @param src the source address.
* @param dst the target address.
* @param bytes the number of bytes to be copied.
* @throws IndexOutOfBoundsException if {@code bytes < 0}, or if it is greater than the size of the segments
* associated with either {@code src} or {@code dst}.
* @throws IllegalStateException if either the source address or the target address belong to memory segments
* which have been already closed, or if access occurs from a thread other than the thread owning either segment.
* @throws UnsupportedOperationException if either {@code src} or {@code dst} do not feature required access modes;
* more specifically, {@code src} should be associated with a segment with {@link MemorySegment#READ} access mode,
* while {@code dst} should be associated with a segment with {@link MemorySegment#WRITE} access mode.
*/
static void copy(MemoryAddress src, MemoryAddress dst, long bytes) {
MemoryAddressImpl.copy((MemoryAddressImpl)src, (MemoryAddressImpl)dst, bytes);
}

/**
* The <em>unchecked</em> memory address instance modelling the {@code NULL} address. This address is <em>not</em> backed by
* a memory segment and hence it cannot be dereferenced.

0 comments on commit f1e1cb7

Please sign in to comment.