Skip to content

Commit 848ecc1

Browse files
committedOct 20, 2023
8318538: Add a way to obtain a strided var handle from a layout
Reviewed-by: jvernee, pminborg
1 parent b07da3a commit 848ecc1

File tree

6 files changed

+142
-44
lines changed

6 files changed

+142
-44
lines changed
 

‎src/java.base/share/classes/java/lang/foreign/MemoryLayout.java

+130-27
Original file line numberDiff line numberDiff line change
@@ -269,19 +269,87 @@
269269
* access modes. All other access modes will result in {@link UnsupportedOperationException} being thrown. Moreover,
270270
* while supported, access modes {@code get} and {@code set} might lead to word tearing.
271271
*
272-
* <h2 id="variable-length">Working with variable-length structs</h2>
272+
* <h2 id="variable-length">Working with variable-length arrays</h2>
273273
*
274-
* Memory layouts allow clients to describe the contents of a region of memory whose size is known <em>statically</em>.
275-
* There are, however, cases, where the size of a region of memory is only known <em>dynamically</em>, as it depends
276-
* on the value of one or more struct fields. Consider the following struct declaration in C:
274+
* We have seen how sequence layouts are used to describe the contents of an array whose size is known <em>statically</em>.
275+
* There are cases, however, where the array size is only known <em>dynamically</em>. We call such arrays <em>variable-length arrays</em>.
276+
* There are two common kinds of variable-length arrays:
277+
* <ul>
278+
* <li>a <em>toplevel</em> variable-length array whose size depends on the value of some unrelated variable, or parameter;</li>
279+
* <li>an variable-length array <em>nested</em> in a struct, whose size depends on the value of some other field in the enclosing struct.</li>
280+
* </ul>
281+
* While variable-length arrays cannot be modelled directly using sequence layouts, clients can still enjoy structured
282+
* access to elements of variable-length arrays using var handles as demonstrated in the following sections.
283+
*
284+
* <h3 id="variable-length-toplevel">Toplevel variable-length arrays</h3>
285+
*
286+
* Consider the following struct declaration in C:
287+
*
288+
* {@snippet lang=c :
289+
* typedef struct {
290+
* int x;
291+
* int y;
292+
* } Point;
293+
* }
294+
*
295+
* In the above code, a point is modelled as two coordinates ({@code x} and {@code y} respectively). Now consider
296+
* the following snippet of C code:
297+
*
298+
* {@snippet lang=c :
299+
* int size = ...
300+
* Point *points = (Point*)malloc(sizeof(Point) * size);
301+
* for (int i = 0 ; i < size ; i++) {
302+
* ... points[i].x ...
303+
* }
304+
* }
305+
*
306+
* Here, we allocate an array of point ({@code points}). Crucially, the size of the array is dynamically bound to the value
307+
* of the {@code size} variable. Inside the loop, the {@code x} coordinate of all the points in the array is accessed.
308+
* <p>
309+
* To model this code in Java, let's start by defining a layout for the {@code Point} struct, as follows:
310+
*
311+
* {@snippet lang=java :
312+
* StructLayout POINT = MemoryLayout.structLayout(
313+
* ValueLayout.JAVA_INT.withName("x"),
314+
* ValueLayout.JAVA_INT.withName("y")
315+
* );
316+
* }
317+
*
318+
* Since we know we need to create and access an array of points, it would be tempting to create a sequence layout modelling
319+
* the variable-length array, and then derive the necessary access var handles from the sequence layout. But this approach
320+
* is problematic, as the size of the variable-length array is not known. Instead, a var handle that provides structured
321+
* access to the elements of a variable-length array can be obtained directly from the layout describing the array elements
322+
* (e.g. the point layout), as demonstrated below:
323+
*
324+
* {@snippet lang=java :
325+
* VarHandle POINT_ARR_X = POINT.arrayElementVarHandle(PathElement.groupElement("x"));
326+
*
327+
* int size = ...
328+
* MemorySegment points = ...
329+
* for (int i = 0 ; i < size ; i++) {
330+
* ... POINT_ARR_X.get(segment, 0L, (long)i) ...
331+
* }
332+
* }
333+
*
334+
* Here, the coordinate {@code x} of subsequent point in the array is accessed using the {@code POINT_ARR_X} var
335+
* handle, which is obtained using the {@link #arrayElementVarHandle(PathElement...)} method. This var handle
336+
* features two {@code long} coordinates: the first is a base offset (set to {@code 0L}), while the
337+
* second is a logical index that can be used to stride over all the elements of the point array.
338+
* <p>
339+
* The base offset coordinate allows clients to express complex access operations, by injecting additional offset
340+
* computation into the var handle (we will see an example of that below). In cases where the base offset is constant
341+
* (as in the previous example) clients can, if desired, drop the base offset parameter and make the access expression
342+
* simpler. This is achieved using the {@link java.lang.invoke.MethodHandles#insertCoordinates(VarHandle, int, Object...)}
343+
* var handle adapter.
344+
*
345+
* <h3 id="variable-length-nested">Nested variable-length arrays</h3>
346+
*
347+
* Consider the following struct declaration in C:
277348
*
278349
* {@snippet lang=c :
279350
* typedef struct {
280351
* int size;
281-
* struct {
282-
* int x;
283-
* int y;
284-
* } points[];
352+
* Point points[];
285353
* } Polygon;
286354
* }
287355
*
@@ -290,45 +358,37 @@
290358
* the size of the {@code points} array is left <em>unspecified</em> in the C declaration, using a <em>Flexible Array Member</em>
291359
* (a feature standardized in C99).
292360
* <p>
293-
* Memory layouts do not support sequence layouts whose size is unknown. As such, it is not possible to model
294-
* the above struct directly. That said, clients can still enjoy structured access provided by memory layouts, as
295-
* demonstrated below:
361+
* Again, clients can perform structured access to elements in the nested variable-length array using the
362+
* {@link #arrayElementVarHandle(PathElement...)} method, as demonstrated below:
296363
*
297364
* {@snippet lang=java :
298-
* StructLayout POINT = MemoryLayout.structLayout(
299-
* ValueLayout.JAVA_INT.withName("x"),
300-
* ValueLayout.JAVA_INT.withName("y")
301-
* );
302-
*
303365
* StructLayout POLYGON = MemoryLayout.structLayout(
304366
* ValueLayout.JAVA_INT.withName("size"),
305367
* MemoryLayout.sequenceLayout(0, POINT).withName("points")
306368
* );
307369
*
308370
* VarHandle POLYGON_SIZE = POLYGON.varHandle(0, PathElement.groupElement("size"));
309-
* VarHandle POINT_X = POINT.varHandle(PathElement.groupElement("x"));
310371
* long POINTS_OFFSET = POLYGON.byteOffset(PathElement.groupElement("points"));
311372
* }
312373
*
313-
* Note how we have split the polygon struct in two. The {@code POLYGON} layout contains a sequence layout
314-
* of size <em>zero</em>. The element layout of the sequence layout is the {@code POINT} layout, which defines
315-
* the {@code x} and {@code y} coordinates, accordingly. The first layout is used to obtain a var handle
316-
* that provides access to the polygon size; the second layout is used to obtain a var handle that provides
317-
* access to the {@code x} coordinate of a point struct. Finally, an offset to the start of the variable-length
318-
* {@code points} array is also obtained.
374+
* The {@code POLYGON} layout contains a sequence layout of size <em>zero</em>. The element layout of the sequence layout
375+
* is the {@code POINT} layout, shown previously. The polygon layout is used to obtain a var handle
376+
* that provides access to the polygon size, as well as an offset ({@code POINTS_OFFSET}) to the start of the
377+
* variable-length {@code points} array.
319378
* <p>
320379
* The {@code x} coordinates of all the points in a polygon can then be accessed as follows:
321380
* {@snippet lang=java :
322381
* MemorySegment polygon = ...
323382
* int size = POLYGON_SIZE.get(polygon, 0L);
324383
* for (int i = 0 ; i < size ; i++) {
325-
* int x = POINT_X.get(polygon, POINT.scaleOffset(POINTS_OFFSET, i));
384+
* ... POINT_ARR_X.get(polygon, POINTS_OFFSET, (long)i) ...
326385
* }
327386
* }
328387
* Here, we first obtain the polygon size, using the {@code POLYGON_SIZE} var handle. Then, in a loop, we read
329-
* the {@code x} coordinates of all the points in the polygon. This is done by providing a custom base offset to
330-
* the {@code POINT_X} var handle. The custom offset is computed as {@code POINTS_OFFSET + (i * POINT.byteSize())}, where
331-
* {@code i} is the loop induction variable.
388+
* the {@code x} coordinates of all the points in the polygon. This is done by providing a custom offset
389+
* (namely, {@code POINTS_OFFSET}) to the offset coordinate of the {@code POINT_ARR_X} var handle. As before,
390+
* the loop induction variable {@code i} is passed as the index of the {@code POINT_ARR_X} var handle,
391+
* to stride over all the elements of the variable-length array.
332392
*
333393
* @implSpec
334394
* Implementations of this interface are immutable, thread-safe and <a href="{@docRoot}/java.base/java/lang/doc-files/ValueBased.html">value-based</a>.
@@ -544,6 +604,49 @@ public sealed interface MemoryLayout permits SequenceLayout, GroupLayout, Paddin
544604
*/
545605
VarHandle varHandle(PathElement... elements);
546606

607+
/**
608+
* Creates a var handle that accesses adjacent elements in a memory segment at offsets selected by the given layout path,
609+
* where the accessed elements have this layout, and where the initial layout in the path is this layout.
610+
* <p>
611+
* The returned var handle has the following characteristics:
612+
* <ul>
613+
* <li>its type is derived from the {@linkplain ValueLayout#carrier() carrier} of the
614+
* selected value layout;</li>
615+
* <li>it has a leading parameter of type {@code MemorySegment} representing the accessed segment</li>
616+
* <li>a following {@code long} parameter, corresponding to the base offset, denoted as {@code B};</li>
617+
* <li>a following {@code long} parameter, corresponding to the array index, denoted as {@code I0}. The array
618+
* index is used to scale the accessed offset by this layout size;</li>
619+
* <li>it has zero or more trailing access coordinates of type {@code long}, one for each
620+
* <a href=#open-path-elements>open path element</a> in the provided layout path, denoted as
621+
* {@code I1, I2, ... In}, respectively. The order of these access coordinates corresponds to the order
622+
* in which the open path elements occur in the provided layout path.
623+
* </ul>
624+
* <p>
625+
* If the provided layout path {@code P} contains no dereference elements, then the offset {@code O} of the access
626+
* operation is computed as follows:
627+
*
628+
* {@snippet lang = "java":
629+
* O = this.offsetHandle(P).invokeExact(this.scale(B, I0), I1, I2, ... In);
630+
* }
631+
* <p>
632+
* More formally, this method can be obtained from the {@link #varHandle(PathElement...)}, as follows:
633+
* {@snippet lang = "java":
634+
* MethodHandles.collectCoordinates(varHandle(elements), 1, scaleHandle())
635+
* }
636+
*
637+
* @apiNote
638+
* As the leading index coordinate {@code I0} is not bound by any sequence layout, it can assume <em>any</em> non-negative
639+
* value - provided that the resulting offset computation does not overflow, or that the computed offset does not fall
640+
* outside the spatial bound of the accessed memory segment. As such, the var handles returned from this method can
641+
* be especially useful when accessing <a href="#variable-length">variable-length arrays</a>.
642+
*
643+
* @param elements the layout path elements.
644+
* @return a var handle that accesses adjacent elements in a memory segment at offsets selected by the given layout path.
645+
* @throws IllegalArgumentException if the layout path is not <a href="#well-formedness">well-formed</a> for this layout.
646+
* @throws IllegalArgumentException if the layout selected by the provided path is not a {@linkplain ValueLayout value layout}.
647+
*/
648+
VarHandle arrayElementVarHandle(PathElement... elements);
649+
547650
/**
548651
* Creates a method handle which, given a memory segment, returns a {@linkplain MemorySegment#asSlice(long,long) slice}
549652
* corresponding to the layout selected by the given layout path, where the initial layout in the path is this layout.

‎src/java.base/share/classes/java/lang/foreign/MemorySegment.java

+4-11
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,6 @@
2727

2828
import java.io.UncheckedIOException;
2929
import java.lang.foreign.ValueLayout.OfInt;
30-
import java.lang.invoke.MethodHandle;
31-
import java.lang.invoke.VarHandle;
3230
import java.nio.Buffer;
3331
import java.nio.ByteBuffer;
3432
import java.nio.ByteOrder;
@@ -46,8 +44,6 @@
4644
import jdk.internal.foreign.AbstractMemorySegmentImpl;
4745
import jdk.internal.foreign.MemorySessionImpl;
4846
import jdk.internal.foreign.SegmentFactories;
49-
import jdk.internal.foreign.StringSupport;
50-
import jdk.internal.foreign.Utils;
5147
import jdk.internal.javac.Restricted;
5248
import jdk.internal.reflect.CallerSensitive;
5349
import jdk.internal.vm.annotation.ForceInline;
@@ -134,21 +130,18 @@
134130
* int value = (int) intAtOffsetHandle.get(segment, 10L); // segment.get(ValueLayout.JAVA_INT, 10L)
135131
* }
136132
*
137-
* The var handle returned by {@link ValueLayout#varHandle()} features a <em>base offset</em> parameter. This parameter
138-
* allows clients to express complex access operations, by injecting additional offset computation into the var handle.
139-
* For instance, a var handle that can be used to access an element of an {@code int} array at a given logical
133+
* Alternatively, a var handle that can be used to access an element of an {@code int} array at a given logical
140134
* index can be created as follows:
141135
*
142136
* {@snippet lang=java:
143-
* MethodHandle scale = ValueLayout.JAVA_INT.scaleHandle(); // (long, long)long
144137
* VarHandle intAtOffsetAndIndexHandle =
145-
* MethodHandles.collectCoordinates(intAtOffsetHandle, 1, scale); // (MemorySegment, long, long)
146-
* int value = (int) intAtOffsetAndIndexHandle.get(segment, 2L, 3L); // segment.get(ValueLayout.JAVA_INT, 2L + (3L * 4L))
138+
* ValueLayout.JAVA_INT.arrayElementVarHandle(); // (MemorySegment, long, long)
139+
* int value = (int) intAtOffsetAndIndexHandle.get(segment, 2L, 3L); // segment.get(ValueLayout.JAVA_INT, 2L + (3L * 4L))
147140
* }
148141
*
149142
* <p>
150143
* Clients can also drop the base offset parameter, in order to make the access expression simpler. This can be used to
151-
* implement access operation such as {@link #getAtIndex(OfInt, long)}:
144+
* implement access operations such as {@link #getAtIndex(OfInt, long)}:
152145
*
153146
* {@snippet lang=java:
154147
* VarHandle intAtIndexHandle =

‎src/java.base/share/classes/jdk/internal/foreign/layout/AbstractLayout.java

+4
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,10 @@ public VarHandle varHandle(PathElement... elements) {
196196
Set.of(), elements);
197197
}
198198

199+
public VarHandle arrayElementVarHandle(PathElement... elements) {
200+
return MethodHandles.collectCoordinates(varHandle(elements), 1, scaleHandle());
201+
}
202+
199203
public MethodHandle sliceHandle(PathElement... elements) {
200204
return computePathOp(LayoutPath.rootPath((MemoryLayout) this), LayoutPath::sliceHandle,
201205
Set.of(PathKind.DEREF_ELEMENT), elements);

‎test/jdk/java/foreign/TestAdaptVarHandles.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,8 @@ public class TestAdaptVarHandles {
8282
}
8383
}
8484

85-
static final VarHandle intHandleIndexed = MethodHandles.collectCoordinates(ValueLayout.JAVA_INT.varHandle(),
86-
1, MethodHandles.insertArguments(ValueLayout.JAVA_INT.scaleHandle(), 0, 0L));
85+
static final VarHandle intHandleIndexed = MethodHandles.insertCoordinates(
86+
ValueLayout.JAVA_INT.arrayElementVarHandle(), 1, 0L);
8787

8888
static final VarHandle intHandle = MethodHandles.insertCoordinates(ValueLayout.JAVA_INT.varHandle(), 1, 0L);
8989

‎test/jdk/java/foreign/TestArrayCopy.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -292,8 +292,7 @@ public static MemorySegment srcSegment(int bytesLength) {
292292
}
293293

294294
private static VarHandle arrayVarHandle(ValueLayout layout) {
295-
return MethodHandles.collectCoordinates(layout.varHandle(),
296-
1, MethodHandles.insertArguments(layout.scaleHandle(), 0, 0L));
295+
return MethodHandles.insertCoordinates(layout.arrayElementVarHandle(), 1, 0L);
297296
}
298297

299298
public static MemorySegment truthSegment(MemorySegment srcSeg, CopyHelper<?, ?> helper, int indexShifts, CopyMode mode) {

‎test/micro/org/openjdk/bench/java/lang/foreign/JavaLayouts.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ public class JavaLayouts {
4444
static final VarHandle VH_LONG = arrayVarHandle(JAVA_LONG);
4545

4646
private static VarHandle arrayVarHandle(ValueLayout layout) {
47-
return MethodHandles.collectCoordinates(layout.varHandle(),
48-
1, MethodHandles.insertArguments(layout.scaleHandle(), 0, 0L));
47+
return MethodHandles.insertCoordinates(layout.arrayElementVarHandle(), 1, 0L);
4948
}
5049
}

0 commit comments

Comments
 (0)
Please sign in to comment.