From 3193402768e6b697a0c6ee9e05c6e1a49f8d5a90 Mon Sep 17 00:00:00 2001 From: Maurizio Cimadamore Date: Thu, 19 Oct 2023 17:42:06 +0100 Subject: [PATCH] Inital push --- .../java/lang/foreign/MemoryLayout.java | 157 +++++++++++++++--- .../java/lang/foreign/MemorySegment.java | 16 +- .../foreign/layout/AbstractLayout.java | 4 + .../jdk/java/foreign/TestAdaptVarHandles.java | 4 +- test/jdk/java/foreign/TestArrayCopy.java | 3 +- .../bench/java/lang/foreign/JavaLayouts.java | 3 +- 6 files changed, 143 insertions(+), 44 deletions(-) diff --git a/src/java.base/share/classes/java/lang/foreign/MemoryLayout.java b/src/java.base/share/classes/java/lang/foreign/MemoryLayout.java index db656ada136f5..f03466f32680c 100644 --- a/src/java.base/share/classes/java/lang/foreign/MemoryLayout.java +++ b/src/java.base/share/classes/java/lang/foreign/MemoryLayout.java @@ -269,19 +269,87 @@ * access modes. All other access modes will result in {@link UnsupportedOperationException} being thrown. Moreover, * while supported, access modes {@code get} and {@code set} might lead to word tearing. * - *

Working with variable-length structs

+ *

Working with variable-length arrays

* - * Memory layouts allow clients to describe the contents of a region of memory whose size is known statically. - * There are, however, cases, where the size of a region of memory is only known dynamically, as it depends - * on the value of one or more struct fields. Consider the following struct declaration in C: + * We have seen how sequence layouts are used to describe the contents of an array whose size is known statically. + * There are cases, however, where the array size is only known dynamically. We call such arrays variable-length arrays. + * There are two common kinds of variable-length arrays: + * + * While variable-length arrays cannot be modelled directly using sequence layouts, clients can still enjoy structured + * access to elements of variable-length arrays using var handles as demonstrated in the following sections. + * + *

Toplevel variable-length arrays

+ * + * Consider the following struct declaration in C: + * + * {@snippet lang=c : + * typedef struct { + * int x; + * int y; + * } Point; + * } + * + * In the above code, a point is modelled as two coordinates ({@code x} and {@code y} respectively). Now consider + * the following snippet of C code: + * + * {@snippet lang=c : + * int size = ... + * Point *points = (Point*)malloc(sizeof(Point) * size); + * for (int i = 0 ; i < size ; i++) { + * ... points[i].x ... + * } + * } + * + * Here, we allocate an array of point ({@code points}). Crucially, the size of the array is dynamically bound to the value + * of the {@code size} variable. Inside the loop, the {@code x} coordinate of all the points in the array is accessed. + *

+ * To model this code in Java, let's start by defining a layout for the {@code Point} struct, as follows: + * + * {@snippet lang=java : + * StructLayout POINT = MemoryLayout.structLayout( + * ValueLayout.JAVA_INT.withName("x"), + * ValueLayout.JAVA_INT.withName("y") + * ); + * } + * + * Since we know we need to create and access an array of points, it would be tempting to create a sequence layout modelling + * the variable-length array, and then derive the necessary access var handles from the sequence layout. But this approach + * is problematic, as the size of the variable-length array is not known. Instead, a var handle that provides structured + * access to the elements of a variable-length array can be obtained directly from the layout describing the array elements + * (e.g. the point layout), as demonstrated below: + * + * {@snippet lang=java : + * VarHandle POINT_ARR_X = POINT.arrayElementVarHandle(PathElement.groupElement("x")); + * + * int size = ... + * MemorySegment points = ... + * for (int i = 0 ; i < size ; i++) { + * ... POINT_ARR_X.get(segment, 0L, (long)i) ... + * } + * } + * + * Here, the coordinate {@code x} of subsequent point in the array are accessed using the {@code POINT_ARR_X} var + * handle, which is obtained using the {@link #arrayElementVarHandle(PathElement...)} method. This var handle + * features two {@code long} coordinates: the first is a base offset (set to {@code 0L}), while the + * second is a logical index that can be used to stride over all the elements of the point array. + *

+ * The base offset coordinate allows clients to express complex access operations, by injecting additional offset + * computation into the var handle (we will see an example of that below). In cases where the base offset is constant + * (as in the previous example) clients can, if desired, drop the base offset parameter and make the access expression + * simpler. This is achieved using the {@link java.lang.invoke.MethodHandles#insertCoordinates(VarHandle, int, Object...)} + * var handle adapter. + * + *

Nested variable-length arrays

+ * + * Consider the following struct declaration in C: * * {@snippet lang=c : * typedef struct { * int size; - * struct { - * int x; - * int y; - * } points[]; + * Point points[]; * } Polygon; * } * @@ -290,45 +358,37 @@ * the size of the {@code points} array is left unspecified in the C declaration, using a Flexible Array Member * (a feature standardized in C99). *

- * Memory layouts do not support sequence layouts whose size is unknown. As such, it is not possible to model - * the above struct directly. That said, clients can still enjoy structured access provided by memory layouts, as - * demonstrated below: + * Again, clients can perform structured access to elements in the nested variable-length array using the + * {@link #arrayElementVarHandle(PathElement...)} method, as demonstrated below: * * {@snippet lang=java : - * StructLayout POINT = MemoryLayout.structLayout( - * ValueLayout.JAVA_INT.withName("x"), - * ValueLayout.JAVA_INT.withName("y") - * ); - * * StructLayout POLYGON = MemoryLayout.structLayout( * ValueLayout.JAVA_INT.withName("size"), * MemoryLayout.sequenceLayout(0, POINT).withName("points") * ); * * VarHandle POLYGON_SIZE = POLYGON.varHandle(0, PathElement.groupElement("size")); - * VarHandle POINT_X = POINT.varHandle(PathElement.groupElement("x")); * long POINTS_OFFSET = POLYGON.byteOffset(PathElement.groupElement("points")); * } * - * Note how we have split the polygon struct in two. The {@code POLYGON} layout contains a sequence layout - * of size zero. The element layout of the sequence layout is the {@code POINT} layout, which defines - * the {@code x} and {@code y} coordinates, accordingly. The first layout is used to obtain a var handle - * that provides access to the polygon size; the second layout is used to obtain a var handle that provides - * access to the {@code x} coordinate of a point struct. Finally, an offset to the start of the variable-length - * {@code points} array is also obtained. + * The {@code POLYGON} layout contains a sequence layout of size zero. The element layout of the sequence layout + * is the {@code POINT} layout, shown previously. The polygon layout is used to obtain a var handle + * that provides access to the polygon size, as well as an offset ({@code POINTS_OFFSET}) to the start of the + * variable-length {@code points} array. *

* The {@code x} coordinates of all the points in a polygon can then be accessed as follows: * {@snippet lang=java : * MemorySegment polygon = ... * int size = POLYGON_SIZE.get(polygon, 0L); * for (int i = 0 ; i < size ; i++) { - * int x = POINT_X.get(polygon, POINT.scaleOffset(POINTS_OFFSET, i)); + * ... POINT_ARR_X.get(polygon, POINTS_OFFSET, (long)i) ... * } * } * Here, we first obtain the polygon size, using the {@code POLYGON_SIZE} var handle. Then, in a loop, we read - * the {@code x} coordinates of all the points in the polygon. This is done by providing a custom base offset to - * the {@code POINT_X} var handle. The custom offset is computed as {@code POINTS_OFFSET + (i * POINT.byteSize())}, where - * {@code i} is the loop induction variable. + * the {@code x} coordinates of all the points in the polygon. This is done by providing a custom offset + * (namely, {@code POINTS_OFFSET}) to the offset coordinate of the {@code POINT_ARR_X} var handle. As before, + * the loop induction variable {@code i} is passed as the index of the {@code POINT_ARR_X} var handle, + * to stride over all the elements of the variable-length array. * * @implSpec * Implementations of this interface are immutable, thread-safe and value-based. @@ -544,6 +604,49 @@ public sealed interface MemoryLayout permits SequenceLayout, GroupLayout, Paddin */ VarHandle varHandle(PathElement... elements); + /** + * Creates a var handle that accesses adjacent elements in a memory segment at offsets selected by the given layout path, + * where the accessed elements have this layout, and where the initial layout in the path is this layout. + *

+ * The returned var handle has the following characteristics: + *

+ *

+ * If the provided layout path {@code P} contains no dereference elements, then the offset {@code O} of the access + * operation is computed as follows: + * + * {@snippet lang = "java": + * O = this.offsetHandle(P).invokeExact(this.scale(B, I0), I1, I2, ... In); + * } + *

+ * More formally, this method can be obtained from the {@link #varHandle(PathElement...)}, as follows: + * {@snippet lang = "java": + * MethodHandles.collectCoordinates(varHandle(elements), 1, scaleHandle()) + * } + * + * @apiNote + * As the leading index coordinate {@code I0} is not bound by any sequence layout, it can assume any non-negative + * value - provided that the resulting offset computation does not overflow, or that the computed offset does not fall + * outside the spatial bound of the accessed memory segment. As such, the var handles returned from this method can + * be especially useful when accessing variable-length arrays. + * + * @param elements the layout path elements. + * @return a var handle that accesses adjacent elements in a memory segment at offsets selected by the given layout path. + * @throws IllegalArgumentException if the layout path is not well-formed for this layout. + * @throws IllegalArgumentException if the layout selected by the provided path is not a {@linkplain ValueLayout value layout}. + */ + VarHandle arrayElementVarHandle(PathElement... elements); + /** * Creates a method handle which, given a memory segment, returns a {@linkplain MemorySegment#asSlice(long,long) slice} * corresponding to the layout selected by the given layout path, where the initial layout in the path is this layout. diff --git a/src/java.base/share/classes/java/lang/foreign/MemorySegment.java b/src/java.base/share/classes/java/lang/foreign/MemorySegment.java index 5565eaf24613c..dcf8a0ace392e 100644 --- a/src/java.base/share/classes/java/lang/foreign/MemorySegment.java +++ b/src/java.base/share/classes/java/lang/foreign/MemorySegment.java @@ -27,8 +27,6 @@ import java.io.UncheckedIOException; import java.lang.foreign.ValueLayout.OfInt; -import java.lang.invoke.MethodHandle; -import java.lang.invoke.VarHandle; import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -46,8 +44,6 @@ import jdk.internal.foreign.AbstractMemorySegmentImpl; import jdk.internal.foreign.MemorySessionImpl; import jdk.internal.foreign.SegmentFactories; -import jdk.internal.foreign.StringSupport; -import jdk.internal.foreign.Utils; import jdk.internal.javac.Restricted; import jdk.internal.reflect.CallerSensitive; import jdk.internal.vm.annotation.ForceInline; @@ -134,21 +130,19 @@ * int value = (int) intAtOffsetHandle.get(segment, 10L); // segment.get(ValueLayout.JAVA_INT, 10L) * } * - * The var handle returned by {@link ValueLayout#varHandle()} features a base offset parameter. This parameter - * allows clients to express complex access operations, by injecting additional offset computation into the var handle. - * For instance, a var handle that can be used to access an element of an {@code int} array at a given logical + * Alternatively, a var handle that can be used to access an element of an {@code int} array at a given logical * index can be created as follows: * * {@snippet lang=java: - * MethodHandle scale = ValueLayout.JAVA_INT.scaleHandle(); // (long, long)long + * MethodHandle scale = ValueLayout.JAVA_INT.scaleHandle(); * VarHandle intAtOffsetAndIndexHandle = - * MethodHandles.collectCoordinates(intAtOffsetHandle, 1, scale); // (MemorySegment, long, long) - * int value = (int) intAtOffsetAndIndexHandle.get(segment, 2L, 3L); // segment.get(ValueLayout.JAVA_INT, 2L + (3L * 4L)) + * ValueLayout.JAVA_INT.arrayElementVarHandle(); // (MemorySegment, long, long) + * int value = (int) intAtOffsetAndIndexHandle.get(segment, 2L, 3L); // segment.get(ValueLayout.JAVA_INT, 2L + (3L * 4L)) * } * *

* Clients can also drop the base offset parameter, in order to make the access expression simpler. This can be used to - * implement access operation such as {@link #getAtIndex(OfInt, long)}: + * implement access operations such as {@link #getAtIndex(OfInt, long)}: * * {@snippet lang=java: * VarHandle intAtIndexHandle = diff --git a/src/java.base/share/classes/jdk/internal/foreign/layout/AbstractLayout.java b/src/java.base/share/classes/jdk/internal/foreign/layout/AbstractLayout.java index 42baaa692700c..e4d931127af99 100644 --- a/src/java.base/share/classes/jdk/internal/foreign/layout/AbstractLayout.java +++ b/src/java.base/share/classes/jdk/internal/foreign/layout/AbstractLayout.java @@ -196,6 +196,10 @@ public VarHandle varHandle(PathElement... elements) { Set.of(), elements); } + public VarHandle arrayElementVarHandle(PathElement... elements) { + return MethodHandles.collectCoordinates(varHandle(elements), 1, scaleHandle()); + } + public MethodHandle sliceHandle(PathElement... elements) { return computePathOp(LayoutPath.rootPath((MemoryLayout) this), LayoutPath::sliceHandle, Set.of(PathKind.DEREF_ELEMENT), elements); diff --git a/test/jdk/java/foreign/TestAdaptVarHandles.java b/test/jdk/java/foreign/TestAdaptVarHandles.java index 2849b308160c3..ebbef0ffb6bcb 100644 --- a/test/jdk/java/foreign/TestAdaptVarHandles.java +++ b/test/jdk/java/foreign/TestAdaptVarHandles.java @@ -82,8 +82,8 @@ public class TestAdaptVarHandles { } } - static final VarHandle intHandleIndexed = MethodHandles.collectCoordinates(ValueLayout.JAVA_INT.varHandle(), - 1, MethodHandles.insertArguments(ValueLayout.JAVA_INT.scaleHandle(), 0, 0L)); + static final VarHandle intHandleIndexed = MethodHandles.insertCoordinates( + ValueLayout.JAVA_INT.arrayElementVarHandle(), 1, 0L); static final VarHandle intHandle = MethodHandles.insertCoordinates(ValueLayout.JAVA_INT.varHandle(), 1, 0L); diff --git a/test/jdk/java/foreign/TestArrayCopy.java b/test/jdk/java/foreign/TestArrayCopy.java index dfe5c343f7349..0a04f00e4f52f 100644 --- a/test/jdk/java/foreign/TestArrayCopy.java +++ b/test/jdk/java/foreign/TestArrayCopy.java @@ -292,8 +292,7 @@ public static MemorySegment srcSegment(int bytesLength) { } private static VarHandle arrayVarHandle(ValueLayout layout) { - return MethodHandles.collectCoordinates(layout.varHandle(), - 1, MethodHandles.insertArguments(layout.scaleHandle(), 0, 0L)); + return MethodHandles.insertCoordinates(layout.arrayElementVarHandle(), 1, 0L); } public static MemorySegment truthSegment(MemorySegment srcSeg, CopyHelper helper, int indexShifts, CopyMode mode) { diff --git a/test/micro/org/openjdk/bench/java/lang/foreign/JavaLayouts.java b/test/micro/org/openjdk/bench/java/lang/foreign/JavaLayouts.java index c6e740113b842..a7cb973aaaf94 100644 --- a/test/micro/org/openjdk/bench/java/lang/foreign/JavaLayouts.java +++ b/test/micro/org/openjdk/bench/java/lang/foreign/JavaLayouts.java @@ -44,7 +44,6 @@ public class JavaLayouts { static final VarHandle VH_LONG = arrayVarHandle(JAVA_LONG); private static VarHandle arrayVarHandle(ValueLayout layout) { - return MethodHandles.collectCoordinates(layout.varHandle(), - 1, MethodHandles.insertArguments(layout.scaleHandle(), 0, 0L)); + return MethodHandles.insertCoordinates(layout.arrayElementVarHandle(), 1, 0L); } }