< prev index next >

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

Print this page
*** 24,11 ***
   *
   */
  package jdk.internal.foreign;
  
  import jdk.internal.vm.annotation.ForceInline;
- 
  import java.lang.foreign.AddressLayout;
  import java.lang.foreign.GroupLayout;
  import java.lang.foreign.MemoryLayout;
  import java.lang.foreign.MemorySegment;
  import java.lang.foreign.SequenceLayout;
--- 24,10 ---

*** 37,15 ***
  import java.lang.invoke.MethodHandle;
  import java.lang.invoke.MethodHandles;
  import java.lang.invoke.MethodType;
  import java.lang.invoke.VarHandle;
  import java.util.Arrays;
  import java.util.Objects;
  import java.util.function.UnaryOperator;
  
  /**
!  * This class provide support for constructing layout paths; that is, starting from a root path (see {@link #rootPath(MemoryLayout)},
   * a path can be constructed by selecting layout elements using the selector methods provided by this class
   * (see {@link #sequenceElement()}, {@link #sequenceElement(long)}, {@link #sequenceElement(long, long)}, {@link #groupElement(String)}).
   * Once a path has been fully constructed, clients can ask for the offset associated with the layout element selected
   * by the path (see {@link #offset}), or obtain var handle to access the selected layout element
   * given an address pointing to a segment associated with the root layout (see {@link #dereferenceHandle()}).
--- 36,20 ---
  import java.lang.invoke.MethodHandle;
  import java.lang.invoke.MethodHandles;
  import java.lang.invoke.MethodType;
  import java.lang.invoke.VarHandle;
  import java.util.Arrays;
+ import java.util.List;
  import java.util.Objects;
  import java.util.function.UnaryOperator;
+ import java.util.stream.IntStream;
+ import java.util.stream.Stream;
+ 
+ import static java.util.stream.Collectors.joining;
  
  /**
!  * This class provide support for constructing layout paths; that is, starting from a root path (see {@link #rootPath(MemoryLayout)}),
   * a path can be constructed by selecting layout elements using the selector methods provided by this class
   * (see {@link #sequenceElement()}, {@link #sequenceElement(long)}, {@link #sequenceElement(long, long)}, {@link #groupElement(String)}).
   * Once a path has been fully constructed, clients can ask for the offset associated with the layout element selected
   * by the path (see {@link #offset}), or obtain var handle to access the selected layout element
   * given an address pointing to a segment associated with the root layout (see {@link #dereferenceHandle()}).

*** 59,10 ***
--- 63,11 ---
      private static final MethodHandle MH_ADD_SCALED_OFFSET;
      private static final MethodHandle MH_SLICE;
      private static final MethodHandle MH_SLICE_LAYOUT;
      private static final MethodHandle MH_CHECK_ALIGN;
      private static final MethodHandle MH_SEGMENT_RESIZE;
+     private static final MethodHandle MH_ADD;
  
      static {
          try {
              MethodHandles.Lookup lookup = MethodHandles.lookup();
              MH_ADD_SCALED_OFFSET = lookup.findStatic(LayoutPath.class, "addScaledOffset",

*** 70,23 ***
              MH_SLICE = lookup.findVirtual(MemorySegment.class, "asSlice",
                      MethodType.methodType(MemorySegment.class, long.class, long.class));
              MH_SLICE_LAYOUT = lookup.findVirtual(MemorySegment.class, "asSlice",
                      MethodType.methodType(MemorySegment.class, long.class, MemoryLayout.class));
              MH_CHECK_ALIGN = lookup.findStatic(LayoutPath.class, "checkAlign",
!                     MethodType.methodType(MemorySegment.class, MemorySegment.class, MemoryLayout.class));
              MH_SEGMENT_RESIZE = lookup.findStatic(LayoutPath.class, "resizeSegment",
                      MethodType.methodType(MemorySegment.class, MemorySegment.class, MemoryLayout.class));
          } catch (Throwable ex) {
              throw new ExceptionInInitializerError(ex);
          }
      }
  
      private final MemoryLayout layout;
      private final long offset;
      private final LayoutPath enclosing;
      private final long[] strides;
- 
      private final long[] bounds;
      private final MethodHandle[] derefAdapters;
  
      private LayoutPath(MemoryLayout layout, long offset, long[] strides, long[] bounds, MethodHandle[] derefAdapters, LayoutPath enclosing) {
          this.layout = layout;
--- 75,24 ---
              MH_SLICE = lookup.findVirtual(MemorySegment.class, "asSlice",
                      MethodType.methodType(MemorySegment.class, long.class, long.class));
              MH_SLICE_LAYOUT = lookup.findVirtual(MemorySegment.class, "asSlice",
                      MethodType.methodType(MemorySegment.class, long.class, MemoryLayout.class));
              MH_CHECK_ALIGN = lookup.findStatic(LayoutPath.class, "checkAlign",
!                     MethodType.methodType(void.class, MemorySegment.class, long.class, MemoryLayout.class));
              MH_SEGMENT_RESIZE = lookup.findStatic(LayoutPath.class, "resizeSegment",
                      MethodType.methodType(MemorySegment.class, MemorySegment.class, MemoryLayout.class));
+             MH_ADD = lookup.findStatic(Long.class, "sum",
+                     MethodType.methodType(long.class, long.class, long.class));
          } catch (Throwable ex) {
              throw new ExceptionInInitializerError(ex);
          }
      }
  
      private final MemoryLayout layout;
      private final long offset;
      private final LayoutPath enclosing;
      private final long[] strides;
      private final long[] bounds;
      private final MethodHandle[] derefAdapters;
  
      private LayoutPath(MemoryLayout layout, long offset, long[] strides, long[] bounds, MethodHandle[] derefAdapters, LayoutPath enclosing) {
          this.layout = layout;

*** 98,42 ***
      }
  
      // Layout path selector methods
  
      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.byteSize()), addBound(seq.elementCount()), derefAdapters, this);
      }
  
      public LayoutPath sequenceElement(long start, long step) {
!         check(SequenceLayout.class, "attempting to select a sequence element from a non-sequence layout");
-         SequenceLayout seq = (SequenceLayout)layout;
          checkSequenceBounds(seq, start);
          MemoryLayout elem = seq.elementLayout();
          long elemSize = elem.byteSize();
          long nelems = step > 0 ?
                  seq.elementCount() - start :
                  start + 1;
          long maxIndex = Math.ceilDiv(nelems, Math.abs(step));
          return LayoutPath.nestedPath(elem, offset + (start * elemSize),
!                                      addStride(elemSize * step), addBound(maxIndex), derefAdapters, this);
      }
  
      public LayoutPath sequenceElement(long index) {
!         check(SequenceLayout.class, "attempting to select a sequence element from a non-sequence layout");
-         SequenceLayout seq = (SequenceLayout)layout;
          checkSequenceBounds(seq, index);
          long elemSize = seq.elementLayout().byteSize();
          long elemOffset = elemSize * index;
!         return LayoutPath.nestedPath(seq.elementLayout(), offset + elemOffset, strides, bounds, derefAdapters,this);
      }
  
      public LayoutPath groupElement(String name) {
!         check(GroupLayout.class, "attempting to select a group element from a non-group layout");
-         GroupLayout g = (GroupLayout)layout;
          long offset = 0;
          MemoryLayout elem = null;
          for (int i = 0; i < g.memberLayouts().size(); i++) {
              MemoryLayout l = g.memberLayouts().get(i);
              if (l.name().isPresent() &&
--- 104,38 ---
      }
  
      // Layout path selector methods
  
      public LayoutPath sequenceElement() {
!         SequenceLayout seq = requireSequenceLayout();
          MemoryLayout elem = seq.elementLayout();
          return LayoutPath.nestedPath(elem, offset, addStride(elem.byteSize()), addBound(seq.elementCount()), derefAdapters, this);
      }
  
      public LayoutPath sequenceElement(long start, long step) {
!         SequenceLayout seq = requireSequenceLayout();
          checkSequenceBounds(seq, start);
          MemoryLayout elem = seq.elementLayout();
          long elemSize = elem.byteSize();
          long nelems = step > 0 ?
                  seq.elementCount() - start :
                  start + 1;
          long maxIndex = Math.ceilDiv(nelems, Math.abs(step));
          return LayoutPath.nestedPath(elem, offset + (start * elemSize),
!                 addStride(elemSize * step), addBound(maxIndex), derefAdapters, this);
      }
  
      public LayoutPath sequenceElement(long index) {
!         SequenceLayout seq = requireSequenceLayout();
          checkSequenceBounds(seq, index);
          long elemSize = seq.elementLayout().byteSize();
          long elemOffset = elemSize * index;
!         return LayoutPath.nestedPath(seq.elementLayout(), offset + elemOffset, strides, bounds, derefAdapters, this);
      }
  
      public LayoutPath groupElement(String name) {
!         GroupLayout g = requireGroupLayout();
          long offset = 0;
          MemoryLayout elem = null;
          for (int i = 0; i < g.memberLayouts().size(); i++) {
              MemoryLayout l = g.memberLayouts().get(i);
              if (l.name().isPresent() &&

*** 143,24 ***
              } else if (g instanceof StructLayout) {
                  offset += l.byteSize();
              }
          }
          if (elem == null) {
!             throw badLayoutPath("cannot resolve '" + name + "' in layout " + layout);
          }
          return LayoutPath.nestedPath(elem, this.offset + offset, strides, bounds, derefAdapters, this);
      }
  
      public LayoutPath groupElement(long index) {
!         check(GroupLayout.class, "attempting to select a group element from a non-group layout");
-         GroupLayout g = (GroupLayout)layout;
          long elemSize = g.memberLayouts().size();
          long offset = 0;
          MemoryLayout elem = null;
          for (int i = 0; i <= index; i++) {
              if (i == elemSize) {
!                 throw badLayoutPath("cannot resolve element " + index + " in layout " + layout);
              }
              elem = g.memberLayouts().get(i);
              if (g instanceof StructLayout && i < index) {
                  offset += elem.byteSize();
              }
--- 145,25 ---
              } else if (g instanceof StructLayout) {
                  offset += l.byteSize();
              }
          }
          if (elem == null) {
!             throw badLayoutPath(
+                     String.format("cannot resolve '%s' in layout %s", name, breadcrumbs()));
          }
          return LayoutPath.nestedPath(elem, this.offset + offset, strides, bounds, derefAdapters, this);
      }
  
      public LayoutPath groupElement(long index) {
!         GroupLayout g = requireGroupLayout();
          long elemSize = g.memberLayouts().size();
          long offset = 0;
          MemoryLayout elem = null;
          for (int i = 0; i <= index; i++) {
              if (i == elemSize) {
!                 throw badLayoutPath(
+                         String.format("cannot resolve element %d in layout: %s", index, breadcrumbs()));
              }
              elem = g.memberLayouts().get(i);
              if (g instanceof StructLayout && i < index) {
                  offset += elem.byteSize();
              }

*** 169,11 ***
      }
  
      public LayoutPath derefElement() {
          if (!(layout instanceof AddressLayout 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));
--- 172,12 ---
      }
  
      public LayoutPath derefElement() {
          if (!(layout instanceof AddressLayout addressLayout) ||
                  addressLayout.targetLayout().isEmpty()) {
!             throw badLayoutPath(
+                     String.format("Cannot dereference layout: %s", breadcrumbs()));
          }
          MemoryLayout derefLayout = addressLayout.targetLayout().get();
          MethodHandle handle = dereferenceHandle(false).toMethodHandle(VarHandle.AccessMode.GET);
          handle = MethodHandles.filterReturnValue(handle,
                  MethodHandles.insertArguments(MH_SEGMENT_RESIZE, 1, derefLayout));

*** 194,30 ***
          return dereferenceHandle(true);
      }
  
      public VarHandle dereferenceHandle(boolean adapt) {
          if (!(layout instanceof ValueLayout valueLayout)) {
!             throw new IllegalArgumentException("Path does not select a value layout");
          }
  
          // If we have an enclosing layout, drop the alignment check for the accessed element,
          // we check the root layout instead
          ValueLayout accessedLayout = enclosing != null ? valueLayout.withByteAlignment(1) : valueLayout;
!         VarHandle handle = Utils.makeSegmentViewVarHandle(accessedLayout);
          handle = MethodHandles.collectCoordinates(handle, 1, offsetHandle());
  
          // we only have to check the alignment of the root layout for the first dereference we do,
          // as each dereference checks the alignment of the target address when constructing its segment
          // (see Utils::longToAddress)
          if (derefAdapters.length == 0 && enclosing != null) {
!             MethodHandle checkHandle = MethodHandles.insertArguments(MH_CHECK_ALIGN, 1, rootLayout());
!             handle = MethodHandles.filterCoordinates(handle, 0, checkHandle);
          }
  
          if (adapt) {
              for (int i = derefAdapters.length; i > 0; i--) {
!                 handle = MethodHandles.collectCoordinates(handle, 0, derefAdapters[i - 1]);
              }
          }
          return handle;
      }
  
--- 198,45 ---
          return dereferenceHandle(true);
      }
  
      public VarHandle dereferenceHandle(boolean adapt) {
          if (!(layout instanceof ValueLayout valueLayout)) {
!             throw new IllegalArgumentException(
+                     String.format("Path does not select a value layout: %s", breadcrumbs()));
          }
  
          // If we have an enclosing layout, drop the alignment check for the accessed element,
          // we check the root layout instead
          ValueLayout accessedLayout = enclosing != null ? valueLayout.withByteAlignment(1) : valueLayout;
!         VarHandle handle = accessedLayout.varHandle();
          handle = MethodHandles.collectCoordinates(handle, 1, offsetHandle());
  
          // we only have to check the alignment of the root layout for the first dereference we do,
          // as each dereference checks the alignment of the target address when constructing its segment
          // (see Utils::longToAddress)
          if (derefAdapters.length == 0 && enclosing != null) {
!             // insert align check for the root layout on the initial MS + offset
!             List<Class<?>> coordinateTypes = handle.coordinateTypes();
+             MethodHandle alignCheck = MethodHandles.insertArguments(MH_CHECK_ALIGN, 2, rootLayout());
+             handle = MethodHandles.collectCoordinates(handle, 0, alignCheck);
+             int[] reorder = IntStream.concat(IntStream.of(0, 1), IntStream.range(0, coordinateTypes.size())).toArray();
+             handle = MethodHandles.permuteCoordinates(handle, coordinateTypes, reorder);
          }
  
          if (adapt) {
+             if (derefAdapters.length > 0) {
+                 // plug up the base offset if we have at least 1 enclosing dereference
+                 handle = MethodHandles.insertCoordinates(handle, 1, 0);
+             }
              for (int i = derefAdapters.length; i > 0; i--) {
!                 MethodHandle adapter = derefAdapters[i - 1];
+                 // the first/outermost adapter will have a base offset coordinate, the rest are constant 0
+                 if (i > 1) {
+                     // plug in a constant 0 base offset for all but the outermost access in a deref chain
+                     adapter = MethodHandles.insertArguments(adapter, 1, 0);
+                 }
+                 handle = MethodHandles.collectCoordinates(handle, 0, adapter);
              }
          }
          return handle;
      }
  

*** 226,18 ***
          Objects.checkIndex(index, bound);
          return base + (stride * index);
      }
  
      public MethodHandle offsetHandle() {
!         MethodHandle mh = MethodHandles.identity(long.class);
!         for (int i = strides.length - 1; i >=0; i--) {
              MethodHandle collector = MethodHandles.insertArguments(MH_ADD_SCALED_OFFSET, 2, strides[i], bounds[i]);
              // (J, ...) -> J to (J, J, ...) -> J
              // i.e. new coord is prefixed. Last coord will correspond to innermost layout
              mh = MethodHandles.collectArguments(mh, 0, collector);
          }
!         mh = MethodHandles.insertArguments(mh, 0, offset);
          return mh;
      }
  
      private MemoryLayout rootLayout() {
          return enclosing != null ? enclosing.rootLayout() : this.layout;
--- 245,18 ---
          Objects.checkIndex(index, bound);
          return base + (stride * index);
      }
  
      public MethodHandle offsetHandle() {
!         MethodHandle mh = MethodHandles.insertArguments(MH_ADD, 0, offset);
!         for (int i = strides.length - 1; i >= 0; i--) {
              MethodHandle collector = MethodHandles.insertArguments(MH_ADD_SCALED_OFFSET, 2, strides[i], bounds[i]);
              // (J, ...) -> J to (J, J, ...) -> J
              // i.e. new coord is prefixed. Last coord will correspond to innermost layout
              mh = MethodHandles.collectArguments(mh, 0, collector);
          }
! 
          return mh;
      }
  
      private MemoryLayout rootLayout() {
          return enclosing != null ? enclosing.rootLayout() : this.layout;

*** 251,25 ***
              sliceHandle = MethodHandles.insertArguments(sliceHandle, 2, layout.byteSize()); // (MS, long) -> MS
          } else {
              sliceHandle = MH_SLICE_LAYOUT; // (MS, long, MemoryLayout) -> MS
              sliceHandle = MethodHandles.insertArguments(sliceHandle, 2, layout); // (MS, long) -> MS
          }
!         sliceHandle = MethodHandles.collectArguments(sliceHandle, 1, offsetHandle()); // (MS, ...) -> MS
  
          if (enclosing != null) {
!             MethodHandle checkHandle = MethodHandles.insertArguments(MH_CHECK_ALIGN, 1, rootLayout());
!             sliceHandle = MethodHandles.filterArguments(sliceHandle, 0, checkHandle);
          }
  
          return sliceHandle;
      }
  
!     private static MemorySegment checkAlign(MemorySegment segment, MemoryLayout constraint) {
!         if (!((AbstractMemorySegmentImpl) segment).isAlignedForElement(0, constraint)) {
!             throw new IllegalArgumentException("Target offset incompatible with alignment constraints: " + constraint.byteAlignment());
          }
-         return segment;
      }
  
      public MemoryLayout layout() {
          return layout;
      }
--- 270,30 ---
              sliceHandle = MethodHandles.insertArguments(sliceHandle, 2, layout.byteSize()); // (MS, long) -> MS
          } else {
              sliceHandle = MH_SLICE_LAYOUT; // (MS, long, MemoryLayout) -> MS
              sliceHandle = MethodHandles.insertArguments(sliceHandle, 2, layout); // (MS, long) -> MS
          }
!         sliceHandle = MethodHandles.collectArguments(sliceHandle, 1, offsetHandle()); // (MS, long, ...) -> MS
  
          if (enclosing != null) {
!             // insert align check for the root layout on the initial MS + offset
!             MethodType oldType = sliceHandle.type();
+             MethodHandle alignCheck = MethodHandles.insertArguments(MH_CHECK_ALIGN, 2, rootLayout());
+             sliceHandle = MethodHandles.collectArguments(sliceHandle, 0, alignCheck); // (MS, long, MS, long) -> MS
+             int[] reorder = IntStream.concat(IntStream.of(0, 1), IntStream.range(0, oldType.parameterCount())).toArray();
+             sliceHandle = MethodHandles.permuteArguments(sliceHandle, oldType, reorder); // (MS, long, ...) -> MS
          }
  
          return sliceHandle;
      }
  
!     private static void checkAlign(MemorySegment segment, long offset, MemoryLayout constraint) {
!         if (!((AbstractMemorySegmentImpl) segment).isAlignedForElement(offset, constraint)) {
!             throw new IllegalArgumentException(String.format(
+                     "Target offset %d is incompatible with alignment constraint %d (of %s) for segment %s"
+                     , offset, constraint.byteAlignment(), constraint, segment));
          }
      }
  
      public MemoryLayout layout() {
          return layout;
      }

*** 290,19 ***
          return new LayoutPath(layout, 0L, EMPTY_STRIDES, EMPTY_BOUNDS, handles, null);
      }
  
      // Helper methods
  
!     private void check(Class<?> layoutClass, String msg) {
          if (!layoutClass.isAssignableFrom(layout.getClass())) {
!             throw badLayoutPath(msg);
          }
      }
  
      private void checkSequenceBounds(SequenceLayout seq, long index) {
          if (index >= seq.elementCount()) {
!             throw badLayoutPath(String.format("Sequence index out of bound; found: %d, size: %d", index, seq.elementCount()));
          }
      }
  
      private static IllegalArgumentException badLayoutPath(String cause) {
          return new IllegalArgumentException("Bad layout path: " + cause);
--- 314,31 ---
          return new LayoutPath(layout, 0L, EMPTY_STRIDES, EMPTY_BOUNDS, handles, null);
      }
  
      // Helper methods
  
!     private SequenceLayout requireSequenceLayout() {
+         return requireLayoutType(SequenceLayout.class, "sequence");
+     }
+ 
+     private GroupLayout requireGroupLayout() {
+         return requireLayoutType(GroupLayout.class, "group");
+     }
+ 
+     private <T extends MemoryLayout> T requireLayoutType(Class<T> layoutClass, String name) {
          if (!layoutClass.isAssignableFrom(layout.getClass())) {
!             throw badLayoutPath(
+                     String.format("attempting to select a %s element from a non-%s layout: %s",
+                             name, name, breadcrumbs()));
          }
+         return layoutClass.cast(layout);
      }
  
      private void checkSequenceBounds(SequenceLayout seq, long index) {
          if (index >= seq.elementCount()) {
!             throw badLayoutPath(String.format("sequence index out of bounds; index: %d, elementCount is %d for layout %s",
+                     index, seq.elementCount(), breadcrumbs()));
          }
      }
  
      private static IllegalArgumentException badLayoutPath(String cause) {
          return new IllegalArgumentException("Bad layout path: " + cause);

*** 318,10 ***
--- 354,17 ---
          long[] newBounds = Arrays.copyOf(bounds, bounds.length + 1);
          newBounds[bounds.length] = maxIndex;
          return newBounds;
      }
  
+     private String breadcrumbs() {
+         return Stream.iterate(this, Objects::nonNull, lp -> lp.enclosing)
+                 .map(LayoutPath::layout)
+                 .map(Object::toString)
+                 .collect(joining(", selected from: "));
+     }
+ 
      /**
       * This class provides an immutable implementation for the {@code PathElement} interface. A path element implementation
       * is simply a pointer to one of the selector methods provided by the {@code LayoutPath} class.
       */
      public static final class PathElementImpl implements MemoryLayout.PathElement, UnaryOperator<LayoutPath> {
< prev index next >