Skip to content

Commit 04ce8e3

Browse files
committed
8257184: Upstream 8252504: Add a method to MemoryLayout which returns a offset-computing method handle
Reviewed-by: mcimadamore, chegar
1 parent 5a03e47 commit 04ce8e3

File tree

4 files changed

+314
-57
lines changed

4 files changed

+314
-57
lines changed

src/jdk.incubator.foreign/share/classes/jdk/incubator/foreign/MemoryLayout.java

Lines changed: 87 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,11 @@
3131

3232
import java.lang.constant.Constable;
3333
import java.lang.constant.DynamicConstantDesc;
34+
import java.lang.invoke.MethodHandle;
35+
import java.lang.invoke.MethodHandles;
3436
import java.lang.invoke.VarHandle;
3537
import java.nio.ByteOrder;
3638
import java.util.EnumSet;
37-
import java.util.List;
3839
import java.util.Objects;
3940
import java.util.Optional;
4041
import java.util.OptionalLong;
@@ -173,6 +174,19 @@
173174
* it follows that the memory access var handle {@code valueHandle} will feature an <em>additional</em> {@code long}
174175
* access coordinate.
175176
*
177+
* <p>A layout path with free dimensions can also be used to create an offset-computing method handle, using the
178+
* {@link #bitOffset(PathElement...)} or {@link #byteOffsetHandle(PathElement...)} method. Again, free dimensions are
179+
* translated into {@code long} parameters of the created method handle. The method handle can be used to compute the
180+
* offsets of elements of a sequence at different indices, by supplying these indices when invoking the method handle.
181+
* For instance:
182+
*
183+
* <blockquote><pre>{@code
184+
MethodHandle offsetHandle = taggedValues.byteOffsetHandle(PathElement.sequenceElement(),
185+
PathElement.groupElement("kind"));
186+
long offset1 = (long) offsetHandle.invokeExact(1L); // 8
187+
long offset2 = (long) offsetHandle.invokeExact(2L); // 16
188+
* }</pre></blockquote>
189+
*
176190
* <h2>Layout attributes</h2>
177191
*
178192
* Layouts can be optionally associated with one or more <em>attributes</em>. A layout attribute forms a <em>name/value</em>
@@ -337,9 +351,6 @@ default long byteAlignment() {
337351
* Computes the offset, in bits, of the layout selected by a given layout path, where the path is considered rooted in this
338352
* layout.
339353
*
340-
* @apiNote if the layout path has one (or more) free dimensions,
341-
* the offset is computed as if all the indices corresponding to such dimensions were set to {@code 0}.
342-
*
343354
* @param elements the layout path elements.
344355
* @return The offset, in bits, of the layout selected by the layout path in {@code elements}.
345356
* @throws IllegalArgumentException if the layout path does not select any layout nested in this layout, or if the
@@ -348,16 +359,46 @@ default long byteAlignment() {
348359
* @throws UnsupportedOperationException if one of the layouts traversed by the layout path has unspecified size.
349360
*/
350361
default long bitOffset(PathElement... elements) {
351-
return computePathOp(LayoutPath.rootPath(this, MemoryLayout::bitSize), LayoutPath::offset, EnumSet.of(PathKind.SEQUENCE_ELEMENT, PathKind.SEQUENCE_RANGE), elements);
362+
return computePathOp(LayoutPath.rootPath(this, MemoryLayout::bitSize), LayoutPath::offset,
363+
EnumSet.of(PathKind.SEQUENCE_ELEMENT, PathKind.SEQUENCE_RANGE), elements);
364+
}
365+
366+
/**
367+
* Creates a method handle that can be used to compute the offset, in bits, of the layout selected
368+
* by a given layout path, where the path is considered rooted in this layout.
369+
*
370+
* <p>The returned method handle has a return type of {@code long}, and features as many {@code long}
371+
* parameter types as there are free dimensions in the provided layout path (see {@link PathElement#sequenceElement()},
372+
* where the order of the parameters corresponds to the order of the path elements.
373+
* The returned method handle can be used to compute a layout offset similar to {@link #bitOffset(PathElement...)},
374+
* but where some sequence indices are specified only when invoking the method handle.
375+
*
376+
* <p>The final offset returned by the method handle is computed as follows:
377+
*
378+
* <blockquote><pre>{@code
379+
offset = c_1 + c_2 + ... + c_m + (x_1 * s_1) + (x_2 * s_2) + ... + (x_n * s_n)
380+
* }</pre></blockquote>
381+
*
382+
* where {@code x_1}, {@code x_2}, ... {@code x_n} are <em>dynamic</em> values provided as {@code long}
383+
* arguments, whereas {@code c_1}, {@code c_2}, ... {@code c_m} and {@code s_0}, {@code s_1}, ... {@code s_n} are
384+
* <em>static</em> stride constants which are derived from the layout path.
385+
*
386+
* @param elements the layout path elements.
387+
* @return a method handle that can be used to compute the bit offset of the layout element
388+
* specified by the given layout path elements, when supplied with the missing sequence element indices.
389+
* @throws IllegalArgumentException if the layout path contains one or more path elements that select
390+
* multiple sequence element indices (see {@link PathElement#sequenceElement(long, long)}).
391+
* @throws UnsupportedOperationException if one of the layouts traversed by the layout path has unspecified size.
392+
*/
393+
default MethodHandle bitOffsetHandle(PathElement... elements) {
394+
return computePathOp(LayoutPath.rootPath(this, MemoryLayout::bitSize), LayoutPath::offsetHandle,
395+
EnumSet.of(PathKind.SEQUENCE_RANGE), elements);
352396
}
353397

354398
/**
355399
* Computes the offset, in bytes, of the layout selected by a given layout path, where the path is considered rooted in this
356400
* layout.
357401
*
358-
* @apiNote if the layout path has one (or more) free dimensions,
359-
* the offset is computed as if all the indices corresponding to such dimensions were set to {@code 0}.
360-
*
361402
* @param elements the layout path elements.
362403
* @return The offset, in bytes, of the layout selected by the layout path in {@code elements}.
363404
* @throws IllegalArgumentException if the layout path does not select any layout nested in this layout, or if the
@@ -367,8 +408,44 @@ default long bitOffset(PathElement... elements) {
367408
* or if {@code bitOffset(elements)} is not a multiple of 8.
368409
*/
369410
default long byteOffset(PathElement... elements) {
370-
return Utils.bitsToBytesOrThrow(bitOffset(elements),
371-
() -> new UnsupportedOperationException("Cannot compute byte offset; bit offset is not a multiple of 8"));
411+
return Utils.bitsToBytesOrThrow(bitOffset(elements), Utils.bitsToBytesThrowOffset);
412+
}
413+
414+
/**
415+
* Creates a method handle that can be used to compute the offset, in bytes, of the layout selected
416+
* by a given layout path, where the path is considered rooted in this layout.
417+
*
418+
* <p>The returned method handle has a return type of {@code long}, and features as many {@code long}
419+
* parameter types as there are free dimensions in the provided layout path (see {@link PathElement#sequenceElement()},
420+
* where the order of the parameters corresponds to the order of the path elements.
421+
* The returned method handle can be used to compute a layout offset similar to {@link #byteOffset(PathElement...)},
422+
* but where some sequence indices are specified only when invoking the method handle.
423+
*
424+
* <p>The final offset returned by the method handle is computed as follows:
425+
*
426+
* <blockquote><pre>{@code
427+
bitOffset = c_1 + c_2 + ... + c_m + (x_1 * s_1) + (x_2 * s_2) + ... + (x_n * s_n)
428+
offset = bitOffset / 8
429+
* }</pre></blockquote>
430+
*
431+
* where {@code x_1}, {@code x_2}, ... {@code x_n} are <em>dynamic</em> values provided as {@code long}
432+
* arguments, whereas {@code c_1}, {@code c_2}, ... {@code c_m} and {@code s_0}, {@code s_1}, ... {@code s_n} are
433+
* <em>static</em> stride constants which are derived from the layout path.
434+
*
435+
* <p>The method handle will throw an {@link UnsupportedOperationException} if the computed
436+
* offset in bits is not a multiple of 8.
437+
*
438+
* @param elements the layout path elements.
439+
* @return a method handle that can be used to compute the byte offset of the layout element
440+
* specified by the given layout path elements, when supplied with the missing sequence element indices.
441+
* @throws IllegalArgumentException if the layout path contains one or more path elements that select
442+
* multiple sequence element indices (see {@link PathElement#sequenceElement(long, long)}).
443+
* @throws UnsupportedOperationException if one of the layouts traversed by the layout path has unspecified size.
444+
*/
445+
default MethodHandle byteOffsetHandle(PathElement... elements) {
446+
MethodHandle mh = bitOffsetHandle(elements);
447+
mh = MethodHandles.filterReturnValue(mh, Utils.MH_bitsToBytesOrThrowForOffset);
448+
return mh;
372449
}
373450

374451
/**

src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/LayoutPath.java

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,17 @@ public class LayoutPath {
6161
private static final JavaLangInvokeAccess JLI = SharedSecrets.getJavaLangInvokeAccess();
6262

6363
private static final MethodHandle ADD_STRIDE;
64+
private static final MethodHandle MH_ADD_SCALED_OFFSET;
65+
66+
private static final int UNSPECIFIED_ELEM_INDEX = -1;
6467

6568
static {
6669
try {
67-
ADD_STRIDE = MethodHandles.lookup().findStatic(LayoutPath.class, "addStride",
70+
MethodHandles.Lookup lookup = MethodHandles.lookup();
71+
ADD_STRIDE = lookup.findStatic(LayoutPath.class, "addStride",
6872
MethodType.methodType(long.class, MemorySegment.class, long.class, long.class, long.class));
73+
MH_ADD_SCALED_OFFSET = lookup.findStatic(LayoutPath.class, "addScaledOffset",
74+
MethodType.methodType(long.class, long.class, long.class, long.class));
6975
} catch (Throwable ex) {
7076
throw new ExceptionInInitializerError(ex);
7177
}
@@ -93,7 +99,7 @@ public LayoutPath sequenceElement() {
9399
check(SequenceLayout.class, "attempting to select a sequence element from a non-sequence layout");
94100
SequenceLayout seq = (SequenceLayout)layout;
95101
MemoryLayout elem = seq.elementLayout();
96-
return LayoutPath.nestedPath(elem, offset, addStride(sizeFunc.applyAsLong(elem)), -1, this);
102+
return LayoutPath.nestedPath(elem, offset, addStride(sizeFunc.applyAsLong(elem)), UNSPECIFIED_ELEM_INDEX, this);
97103
}
98104

99105
public LayoutPath sequenceElement(long start, long step) {
@@ -102,7 +108,8 @@ public LayoutPath sequenceElement(long start, long step) {
102108
checkSequenceBounds(seq, start);
103109
MemoryLayout elem = seq.elementLayout();
104110
long elemSize = sizeFunc.applyAsLong(elem);
105-
return LayoutPath.nestedPath(elem, offset + (start * elemSize), addStride(elemSize * step), -1, this);
111+
return LayoutPath.nestedPath(elem, offset + (start * elemSize), addStride(elemSize * step),
112+
UNSPECIFIED_ELEM_INDEX, this);
106113
}
107114

108115
public LayoutPath sequenceElement(long index) {
@@ -177,6 +184,22 @@ public VarHandle dereferenceHandle(Class<?> carrier) {
177184
return handle;
178185
}
179186

187+
private static long addScaledOffset(long base, long index, long stride) {
188+
return base + (stride * index);
189+
}
190+
191+
public MethodHandle offsetHandle() {
192+
MethodHandle mh = MethodHandles.identity(long.class);
193+
for (int i = strides.length - 1; i >=0; i--) {
194+
MethodHandle collector = MethodHandles.insertArguments(MH_ADD_SCALED_OFFSET, 2, strides[i]);
195+
// (J, ...) -> J to (J, J, ...) -> J
196+
// i.e. new coord is prefixed. Last coord will correspond to innermost layout
197+
mh = MethodHandles.collectArguments(mh, 0, collector);
198+
}
199+
mh = MethodHandles.insertArguments(mh, 0, offset);
200+
return mh;
201+
}
202+
180203
public MemoryLayout layout() {
181204
return layout;
182205
}

src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/Utils.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,21 @@ public final class Utils {
5353
.orElse("deny");
5454

5555
private static final MethodHandle SEGMENT_FILTER;
56+
public static final MethodHandle MH_bitsToBytesOrThrowForOffset;
57+
58+
public static final Supplier<RuntimeException> bitsToBytesThrowOffset
59+
= () -> new UnsupportedOperationException("Cannot compute byte offset; bit offset is not a multiple of 8");
5660

5761
static {
5862
try {
59-
SEGMENT_FILTER = MethodHandles.lookup().findStatic(Utils.class, "filterSegment",
63+
MethodHandles.Lookup lookup = MethodHandles.lookup();
64+
SEGMENT_FILTER = lookup.findStatic(Utils.class, "filterSegment",
6065
MethodType.methodType(MemorySegmentProxy.class, MemorySegment.class));
66+
MH_bitsToBytesOrThrowForOffset = MethodHandles.insertArguments(
67+
lookup.findStatic(Utils.class, "bitsToBytesOrThrow",
68+
MethodType.methodType(long.class, long.class, Supplier.class)),
69+
1,
70+
bitsToBytesThrowOffset);
6171
} catch (Throwable ex) {
6272
throw new ExceptionInInitializerError(ex);
6373
}

0 commit comments

Comments
 (0)