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

8301360: Add dereference layout paths to memory layout API #776

Closed
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 55 additions & 3 deletions src/java.base/share/classes/java/lang/foreign/MemoryLayout.java
Original file line number Diff line number Diff line change
@@ -25,6 +25,7 @@
*/
package java.lang.foreign;

import java.lang.foreign.ValueLayout.OfAddress;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
@@ -257,12 +258,14 @@ public sealed interface MemoryLayout permits SequenceLayout, GroupLayout, Paddin
* @throws IllegalArgumentException if the layout path does not select any layout nested in this layout, or if the
* layout path contains one or more path elements that select multiple sequence element indices
* (see {@link PathElement#sequenceElement()} and {@link PathElement#sequenceElement(long, long)}).
* @throws IllegalArgumentException if the layout path contains one or more dereference path elements
* (see {@link PathElement#dereferenceElement()}).
* @throws NullPointerException if either {@code elements == null}, or if any of the elements
* in {@code elements} is {@code null}.
*/
default long bitOffset(PathElement... elements) {
return computePathOp(LayoutPath.rootPath(this), LayoutPath::offset,
EnumSet.of(PathKind.SEQUENCE_ELEMENT, PathKind.SEQUENCE_RANGE), elements);
EnumSet.of(PathKind.SEQUENCE_ELEMENT, PathKind.SEQUENCE_RANGE, PathKind.DEREF_ELEMENT), elements);
}

/**
@@ -291,10 +294,12 @@ default long bitOffset(PathElement... elements) {
* specified by the given layout path elements, when supplied with the missing sequence element indices.
* @throws IllegalArgumentException if the layout path contains one or more path elements that select
* multiple sequence element indices (see {@link PathElement#sequenceElement(long, long)}).
* @throws IllegalArgumentException if the layout path contains one or more dereference path elements
* (see {@link PathElement#dereferenceElement()}).
*/
default MethodHandle bitOffsetHandle(PathElement... elements) {
return computePathOp(LayoutPath.rootPath(this), LayoutPath::offsetHandle,
EnumSet.of(PathKind.SEQUENCE_RANGE), elements);
EnumSet.of(PathKind.SEQUENCE_RANGE, PathKind.DEREF_ELEMENT), elements);
}

/**
@@ -306,6 +311,8 @@ default MethodHandle bitOffsetHandle(PathElement... elements) {
* @throws IllegalArgumentException if the layout path does not select any layout nested in this layout, or if the
* layout path contains one or more path elements that select multiple sequence element indices
* (see {@link PathElement#sequenceElement()} and {@link PathElement#sequenceElement(long, long)}).
* @throws IllegalArgumentException if the layout path contains one or more dereference path elements
* (see {@link PathElement#dereferenceElement()}).
* @throws UnsupportedOperationException if {@code bitOffset(elements)} is not a multiple of 8.
* @throws NullPointerException if either {@code elements == null}, or if any of the elements
* in {@code elements} is {@code null}.
@@ -344,6 +351,8 @@ default long byteOffset(PathElement... elements) {
* specified by the given layout path elements, when supplied with the missing sequence element indices.
* @throws IllegalArgumentException if the layout path contains one or more path elements that select
* multiple sequence element indices (see {@link PathElement#sequenceElement(long, long)}).
* @throws IllegalArgumentException if the layout path contains one or more dereference path elements
* (see {@link PathElement#dereferenceElement()}).
*/
default MethodHandle byteOffsetHandle(PathElement... elements) {
MethodHandle mh = bitOffsetHandle(elements);
@@ -377,6 +386,28 @@ default MethodHandle byteOffsetHandle(PathElement... elements) {
* <p>
* Additionally, the provided dynamic values must conform to some bound which is derived from the layout path, that is,
* {@code 0 <= x_i < b_i}, where {@code 1 <= i <= n}, or {@link IndexOutOfBoundsException} is thrown.
* <p>
* Multiple paths can be chained, by using {@linkplain PathElement#dereferenceElement() dereference path elements}.
* A dereference path element allows to obtain a native memory segment whose base address is the address obtained
* by following the layout path elements immediately preceding the dereference path element. In other words,
* if a layout path contains one or more dereference path elements, the final address accessed by the returned
* var handle can be computed as follows:
*
* <blockquote><pre>{@code
* address_1 = base(segment) + offset_1
* address_2 = base(segment_1) + offset_2
* ...
* address_k = base(segment_k-1) + offset_k
* }</pre></blockquote>
*
* where {@code k} is the number of dereference path elements in a layout path, {@code segment} is the input segment,
* {@code segment_1}, ... {@code segment_k-1} are the segments obtained by dereferencing the address associated with
* a given dereference path element (e.g. {@code segment_1} is a native segment whose base address is {@code address_1}),
* and {@code offset_1}, {@code offset_2}, ... {@code offset_k} are the offsets computed by evaluating
* the path elements after a given dereference operation (these offsets are obtained using the computation described
* above). In these more complex access operations, all memory accesses immediately preceding a dereference operation
* (e.g. those at addresses {@code address_1}, {@code address_2}, ..., {@code address_k-1} are performed using the
* {@link VarHandle.AccessMode#GET} access mode.
*
* @apiNote the resulting var handle will feature an additional {@code long} access coordinate for every
* unspecified sequence access component contained in this layout path. Moreover, the resulting var handle
@@ -386,6 +417,8 @@ default MethodHandle byteOffsetHandle(PathElement... elements) {
* @return a var handle which can be used to access a memory segment at the (possibly nested) layout selected by the layout path in {@code elements}.
* @throws UnsupportedOperationException if the layout path has one or more elements with incompatible alignment constraint.
* @throws IllegalArgumentException if the layout path in {@code elements} does not select a value layout (see {@link ValueLayout}).
* @throws IllegalArgumentException if the layout path in {@code elements} contains a {@linkplain PathElement#dereferenceElement()
* dereference path element} for an address layout that has no {@linkplain OfAddress#targetLayout() target layout}.
* @see MethodHandles#memorySegmentViewVarHandle(ValueLayout)
*/
default VarHandle varHandle(PathElement... elements) {
@@ -430,6 +463,8 @@ default VarHandle varHandle(PathElement... elements) {
* @param elements the layout path elements.
* @return a method handle which can be used to create a slice of the selected layout element, given a segment.
* @throws UnsupportedOperationException if the size of the selected layout in bits is not a multiple of 8.
* @throws IllegalArgumentException if the layout path contains one or more dereference path elements
* (see {@link PathElement#dereferenceElement()}).
*/
default MethodHandle sliceHandle(PathElement... elements) {
return computePathOp(LayoutPath.rootPath(this), LayoutPath::sliceHandle,
@@ -444,10 +479,12 @@ default MethodHandle sliceHandle(PathElement... elements) {
* @throws IllegalArgumentException if the layout path does not select any layout nested in this layout,
* or if the layout path contains one or more path elements that select one or more sequence element indices
* (see {@link PathElement#sequenceElement(long)} and {@link PathElement#sequenceElement(long, long)}).
* @throws IllegalArgumentException if the layout path contains one or more dereference path elements
* (see {@link PathElement#dereferenceElement()}).
*/
default MemoryLayout select(PathElement... elements) {
return computePathOp(LayoutPath.rootPath(this), LayoutPath::layout,
EnumSet.of(PathKind.SEQUENCE_ELEMENT_INDEX, PathKind.SEQUENCE_RANGE), elements);
EnumSet.of(PathKind.SEQUENCE_ELEMENT_INDEX, PathKind.SEQUENCE_RANGE, PathKind.DEREF_ELEMENT), elements);
}

private static <Z> Z computePathOp(LayoutPath path, Function<LayoutPath, Z> finalizer,
@@ -594,6 +631,21 @@ static PathElement sequenceElement() {
return new LayoutPath.PathElementImpl(PathKind.SEQUENCE_ELEMENT,
LayoutPath::sequenceElement);
}

/**
* Returns a path element which dereferences an address layout as its
* {@linkplain OfAddress#targetLayout() target layout} (where set).
* The path element returned by this method does not alter the number of free dimensions of any path
* that is combined with such element. Using this path layout to dereference an address layout
* that has no target layout results in an {@link IllegalArgumentException} (e.g. when
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This IAE should be specified on varHandle as well.

* a var handle is {@linkplain #varHandle(PathElement...) obtained}).
*
* @return a path element which dereferences an address layout.
*/
static PathElement dereferenceElement() {
return new LayoutPath.PathElementImpl(PathKind.DEREF_ELEMENT,
LayoutPath::derefElement);
}
}

/**
60 changes: 50 additions & 10 deletions src/java.base/share/classes/jdk/internal/foreign/LayoutPath.java
Original file line number Diff line number Diff line change
@@ -30,6 +30,7 @@
import java.lang.foreign.GroupLayout;
import java.lang.foreign.MemoryLayout;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.SegmentScope;
import java.lang.foreign.SequenceLayout;
import java.lang.foreign.StructLayout;
import java.lang.foreign.ValueLayout;
@@ -53,9 +54,11 @@ public class LayoutPath {

private static final long[] EMPTY_STRIDES = new long[0];
private static final long[] EMPTY_BOUNDS = new long[0];
private static final MethodHandle[] EMPTY_DEREF_HANDLES = new MethodHandle[0];

private static final MethodHandle MH_ADD_SCALED_OFFSET;
private static final MethodHandle MH_SLICE;
private static final MethodHandle MH_SEGMENT_RESIZE;

static {
try {
@@ -64,6 +67,8 @@ public class LayoutPath {
MethodType.methodType(long.class, long.class, long.class, long.class, long.class));
MH_SLICE = lookup.findVirtual(MemorySegment.class, "asSlice",
MethodType.methodType(MemorySegment.class, long.class, long.class));
MH_SEGMENT_RESIZE = lookup.findStatic(LayoutPath.class, "resizeSegment",
MethodType.methodType(MemorySegment.class, MemorySegment.class, MemoryLayout.class));
} catch (Throwable ex) {
throw new ExceptionInInitializerError(ex);
}
@@ -75,12 +80,14 @@ public class LayoutPath {
private final long[] strides;

private final long[] bounds;
private final MethodHandle[] derefAdapters;

private LayoutPath(MemoryLayout layout, long offset, long[] strides, long[] bounds, LayoutPath enclosing) {
private LayoutPath(MemoryLayout layout, long offset, long[] strides, long[] bounds, MethodHandle[] derefAdapters, LayoutPath enclosing) {
this.layout = layout;
this.offset = offset;
this.strides = strides;
this.bounds = bounds;
this.derefAdapters = derefAdapters;
this.enclosing = enclosing;
}

@@ -90,7 +97,7 @@ public LayoutPath sequenceElement() {
check(SequenceLayout.class, "attempting to select a sequence element from a non-sequence layout");
SequenceLayout seq = (SequenceLayout)layout;
MemoryLayout elem = seq.elementLayout();
return LayoutPath.nestedPath(elem, offset, addStride(elem.bitSize()), addBound(seq.elementCount()), this);
return LayoutPath.nestedPath(elem, offset, addStride(elem.bitSize()), addBound(seq.elementCount()), derefAdapters, this);
}

public LayoutPath sequenceElement(long start, long step) {
@@ -104,7 +111,7 @@ public LayoutPath sequenceElement(long start, long step) {
start + 1;
long maxIndex = Math.ceilDiv(nelems, Math.abs(step));
return LayoutPath.nestedPath(elem, offset + (start * elemSize),
addStride(elemSize * step), addBound(maxIndex), this);
addStride(elemSize * step), addBound(maxIndex), derefAdapters, this);
}

public LayoutPath sequenceElement(long index) {
@@ -113,7 +120,7 @@ public LayoutPath sequenceElement(long index) {
checkSequenceBounds(seq, index);
long elemSize = seq.elementLayout().bitSize();
long elemOffset = elemSize * index;
return LayoutPath.nestedPath(seq.elementLayout(), offset + elemOffset, strides, bounds, this);
return LayoutPath.nestedPath(seq.elementLayout(), offset + elemOffset, strides, bounds, derefAdapters,this);
}

public LayoutPath groupElement(String name) {
@@ -134,7 +141,7 @@ public LayoutPath groupElement(String name) {
if (elem == null) {
throw badLayoutPath("cannot resolve '" + name + "' in layout " + layout);
}
return LayoutPath.nestedPath(elem, this.offset + offset, strides, bounds, this);
return LayoutPath.nestedPath(elem, this.offset + offset, strides, bounds, derefAdapters, this);
}

public LayoutPath groupElement(long index) {
@@ -152,7 +159,23 @@ public LayoutPath groupElement(long index) {
offset += elem.bitSize();
}
}
return LayoutPath.nestedPath(elem, this.offset + offset, strides, bounds, this);
return LayoutPath.nestedPath(elem, this.offset + offset, strides, bounds, derefAdapters, this);
}

public LayoutPath derefElement() {
if (!(layout instanceof ValueLayout.OfAddress addressLayout) ||
addressLayout.targetLayout().isEmpty()) {
throw badLayoutPath("Cannot dereference layout: " + layout);
}
MemoryLayout derefLayout = addressLayout.targetLayout().get();
MethodHandle handle = dereferenceHandle(false).toMethodHandle(VarHandle.AccessMode.GET);
handle = MethodHandles.filterReturnValue(handle,
MethodHandles.insertArguments(MH_SEGMENT_RESIZE, 1, derefLayout));
return derefPath(derefLayout, handle, this);
}

private static MemorySegment resizeSegment(MemorySegment segment, MemoryLayout layout) {
return Utils.longToAddress(segment.address(), layout.byteSize(), layout.byteAlignment());
}

// Layout path projections
@@ -162,6 +185,10 @@ public long offset() {
}

public VarHandle dereferenceHandle() {
return dereferenceHandle(true);
}

public VarHandle dereferenceHandle(boolean adapt) {
if (!(layout instanceof ValueLayout valueLayout)) {
throw new IllegalArgumentException("Path does not select a value layout");
}
@@ -178,6 +205,12 @@ public VarHandle dereferenceHandle() {
}
handle = MethodHandles.insertCoordinates(handle, 1,
Utils.bitsToBytesOrThrow(offset, IllegalArgumentException::new));

if (adapt) {
for (int i = derefAdapters.length; i > 0; i--) {
handle = MethodHandles.collectCoordinates(handle, 0, derefAdapters[i - 1]);
}
}
return handle;
}

@@ -222,11 +255,17 @@ public MemoryLayout layout() {
// Layout path construction

public static LayoutPath rootPath(MemoryLayout layout) {
return new LayoutPath(layout, 0L, EMPTY_STRIDES, EMPTY_BOUNDS, null);
return new LayoutPath(layout, 0L, EMPTY_STRIDES, EMPTY_BOUNDS, EMPTY_DEREF_HANDLES, null);
}

private static LayoutPath nestedPath(MemoryLayout layout, long offset, long[] strides, long[] bounds, MethodHandle[] derefAdapters, LayoutPath encl) {
return new LayoutPath(layout, offset, strides, bounds, derefAdapters, encl);
}

private static LayoutPath nestedPath(MemoryLayout layout, long offset, long[] strides, long[] bounds, LayoutPath encl) {
return new LayoutPath(layout, offset, strides, bounds, encl);
private static LayoutPath derefPath(MemoryLayout layout, MethodHandle handle, LayoutPath encl) {
MethodHandle[] handles = Arrays.copyOf(encl.derefAdapters, encl.derefAdapters.length + 1);
handles[encl.derefAdapters.length] = handle;
return new LayoutPath(layout, 0L, EMPTY_STRIDES, EMPTY_BOUNDS, handles, null);
}

// Helper methods
@@ -289,7 +328,8 @@ public enum PathKind {
SEQUENCE_ELEMENT("unbound sequence element"),
SEQUENCE_ELEMENT_INDEX("bound sequence element"),
SEQUENCE_RANGE("sequence range"),
GROUP_ELEMENT("group element");
GROUP_ELEMENT("group element"),
DEREF_ELEMENT("dereference element");

final String description;

141 changes: 141 additions & 0 deletions test/jdk/java/foreign/TestDereferencePath.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*
*/

/*
* @test
* @enablePreview
* @run testng TestDereferencePath
*/

import java.lang.foreign.Arena;
import java.lang.foreign.MemoryLayout;
import java.lang.foreign.MemoryLayout.PathElement;
import java.lang.foreign.MemorySegment;

import java.lang.foreign.ValueLayout;

import org.testng.annotations.*;

import java.lang.invoke.VarHandle;
import static org.testng.Assert.*;

public class TestDereferencePath {

static final MemoryLayout C = MemoryLayout.structLayout(
ValueLayout.JAVA_INT.withName("x")
);

static final MemoryLayout B = MemoryLayout.structLayout(
ValueLayout.ADDRESS.withName("c")
.withTargetLayout(C)
);

static final MemoryLayout A = MemoryLayout.structLayout(
ValueLayout.ADDRESS.withName("b")
.withTargetLayout(B)
);

static final VarHandle abcx = A.varHandle(
PathElement.groupElement("b"), PathElement.dereferenceElement(),
PathElement.groupElement("c"), PathElement.dereferenceElement(),
PathElement.groupElement("x"));

@Test
public void testSingle() {
try (Arena arena = Arena.openConfined()) {
// init structs
MemorySegment a = arena.allocate(A);
MemorySegment b = arena.allocate(B);
MemorySegment c = arena.allocate(C);
// init struct fields
a.set(ValueLayout.ADDRESS, 0, b);
b.set(ValueLayout.ADDRESS, 0, c);
c.set(ValueLayout.JAVA_INT, 0, 42);
// dereference
int val = (int) abcx.get(a);
assertEquals(val, 42);
}
}

static final MemoryLayout B_MULTI = MemoryLayout.structLayout(
ValueLayout.ADDRESS.withName("cs")
.withTargetLayout(MemoryLayout.sequenceLayout(2, C))
);

static final MemoryLayout A_MULTI = MemoryLayout.structLayout(
ValueLayout.ADDRESS.withName("bs")
.withTargetLayout(MemoryLayout.sequenceLayout(2, B_MULTI))
);

static final VarHandle abcx_multi = A_MULTI.varHandle(
PathElement.groupElement("bs"), PathElement.dereferenceElement(), PathElement.sequenceElement(),
PathElement.groupElement("cs"), PathElement.dereferenceElement(), PathElement.sequenceElement(),
PathElement.groupElement("x"));

@Test
public void testMulti() {
try (Arena arena = Arena.openConfined()) {
// init structs
MemorySegment a = arena.allocate(A);
MemorySegment b = arena.allocateArray(B, 2);
MemorySegment c = arena.allocateArray(C, 4);
// init struct fields
a.set(ValueLayout.ADDRESS, 0, b);
b.set(ValueLayout.ADDRESS, 0, c);
b.setAtIndex(ValueLayout.ADDRESS, 1, c.asSlice(C.byteSize() * 2));
c.setAtIndex(ValueLayout.JAVA_INT, 0, 1);
c.setAtIndex(ValueLayout.JAVA_INT, 1, 2);
c.setAtIndex(ValueLayout.JAVA_INT, 2, 3);
c.setAtIndex(ValueLayout.JAVA_INT, 3, 4);
// dereference
int val00 = (int) abcx_multi.get(a, 0, 0); // a->b[0]->c[0] = 1
assertEquals(val00, 1);
int val10 = (int) abcx_multi.get(a, 1, 0); // a->b[1]->c[0] = 3
assertEquals(val10, 3);
int val01 = (int) abcx_multi.get(a, 0, 1); // a->b[0]->c[1] = 2
assertEquals(val01, 2);
int val11 = (int) abcx_multi.get(a, 1, 1); // a->b[1]->c[1] = 4
assertEquals(val11, 4);
}
}

@Test(expectedExceptions = IllegalArgumentException.class)
void testBadDerefInSelect() {
A.select(PathElement.groupElement("b"), PathElement.dereferenceElement());
}

@Test(expectedExceptions = IllegalArgumentException.class)
void testBadDerefInOffset() {
A.byteOffset(PathElement.groupElement("b"), PathElement.dereferenceElement());
}

static final MemoryLayout A_MULTI_NO_TARGET = MemoryLayout.structLayout(
ValueLayout.ADDRESS.withName("bs")
);

@Test(expectedExceptions = IllegalArgumentException.class)
void badDerefAddressNoTarget() {
A_MULTI_NO_TARGET.varHandle(PathElement.groupElement("bs"), PathElement.dereferenceElement());
}
}