1 /* 2 * Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 * 25 */ 26 package jdk.internal.foreign; 27 28 import jdk.internal.vm.annotation.ForceInline; 29 30 import java.lang.foreign.AddressLayout; 31 import java.lang.foreign.GroupLayout; 32 import java.lang.foreign.MemoryLayout; 33 import java.lang.foreign.MemorySegment; 34 import java.lang.foreign.SequenceLayout; 35 import java.lang.foreign.StructLayout; 36 import java.lang.foreign.ValueLayout; 37 import java.lang.invoke.MethodHandle; 38 import java.lang.invoke.MethodHandles; 39 import java.lang.invoke.MethodType; 40 import java.lang.invoke.VarHandle; 41 import java.util.Arrays; 42 import java.util.Objects; 43 import java.util.function.UnaryOperator; 44 45 /** 46 * This class provide support for constructing layout paths; that is, starting from a root path (see {@link #rootPath(MemoryLayout)}, 47 * a path can be constructed by selecting layout elements using the selector methods provided by this class 48 * (see {@link #sequenceElement()}, {@link #sequenceElement(long)}, {@link #sequenceElement(long, long)}, {@link #groupElement(String)}). 49 * Once a path has been fully constructed, clients can ask for the offset associated with the layout element selected 50 * by the path (see {@link #offset}), or obtain var handle to access the selected layout element 51 * given an address pointing to a segment associated with the root layout (see {@link #dereferenceHandle()}). 52 */ 53 public class LayoutPath { 54 55 private static final long[] EMPTY_STRIDES = new long[0]; 56 private static final long[] EMPTY_BOUNDS = new long[0]; 57 private static final MethodHandle[] EMPTY_DEREF_HANDLES = new MethodHandle[0]; 58 59 private static final MethodHandle MH_ADD_SCALED_OFFSET; 60 private static final MethodHandle MH_SLICE; 61 private static final MethodHandle MH_SLICE_LAYOUT; 62 private static final MethodHandle MH_CHECK_ALIGN; 63 private static final MethodHandle MH_SEGMENT_RESIZE; 64 65 static { 66 try { 67 MethodHandles.Lookup lookup = MethodHandles.lookup(); 68 MH_ADD_SCALED_OFFSET = lookup.findStatic(LayoutPath.class, "addScaledOffset", 69 MethodType.methodType(long.class, long.class, long.class, long.class, long.class)); 70 MH_SLICE = lookup.findVirtual(MemorySegment.class, "asSlice", 71 MethodType.methodType(MemorySegment.class, long.class, long.class)); 72 MH_SLICE_LAYOUT = lookup.findVirtual(MemorySegment.class, "asSlice", 73 MethodType.methodType(MemorySegment.class, long.class, MemoryLayout.class)); 74 MH_CHECK_ALIGN = lookup.findStatic(LayoutPath.class, "checkAlign", 75 MethodType.methodType(MemorySegment.class, MemorySegment.class, MemoryLayout.class)); 76 MH_SEGMENT_RESIZE = lookup.findStatic(LayoutPath.class, "resizeSegment", 77 MethodType.methodType(MemorySegment.class, MemorySegment.class, MemoryLayout.class)); 78 } catch (Throwable ex) { 79 throw new ExceptionInInitializerError(ex); 80 } 81 } 82 83 private final MemoryLayout layout; 84 private final long offset; 85 private final LayoutPath enclosing; 86 private final long[] strides; 87 88 private final long[] bounds; 89 private final MethodHandle[] derefAdapters; 90 91 private LayoutPath(MemoryLayout layout, long offset, long[] strides, long[] bounds, MethodHandle[] derefAdapters, LayoutPath enclosing) { 92 this.layout = layout; 93 this.offset = offset; 94 this.strides = strides; 95 this.bounds = bounds; 96 this.derefAdapters = derefAdapters; 97 this.enclosing = enclosing; 98 } 99 100 // Layout path selector methods 101 102 public LayoutPath sequenceElement() { 103 check(SequenceLayout.class, "attempting to select a sequence element from a non-sequence layout"); 104 SequenceLayout seq = (SequenceLayout)layout; 105 MemoryLayout elem = seq.elementLayout(); 106 return LayoutPath.nestedPath(elem, offset, addStride(elem.byteSize()), addBound(seq.elementCount()), derefAdapters, this); 107 } 108 109 public LayoutPath sequenceElement(long start, long step) { 110 check(SequenceLayout.class, "attempting to select a sequence element from a non-sequence layout"); 111 SequenceLayout seq = (SequenceLayout)layout; 112 checkSequenceBounds(seq, start); 113 MemoryLayout elem = seq.elementLayout(); 114 long elemSize = elem.byteSize(); 115 long nelems = step > 0 ? 116 seq.elementCount() - start : 117 start + 1; 118 long maxIndex = Math.ceilDiv(nelems, Math.abs(step)); 119 return LayoutPath.nestedPath(elem, offset + (start * elemSize), 120 addStride(elemSize * step), addBound(maxIndex), derefAdapters, this); 121 } 122 123 public LayoutPath sequenceElement(long index) { 124 check(SequenceLayout.class, "attempting to select a sequence element from a non-sequence layout"); 125 SequenceLayout seq = (SequenceLayout)layout; 126 checkSequenceBounds(seq, index); 127 long elemSize = seq.elementLayout().byteSize(); 128 long elemOffset = elemSize * index; 129 return LayoutPath.nestedPath(seq.elementLayout(), offset + elemOffset, strides, bounds, derefAdapters,this); 130 } 131 132 public LayoutPath groupElement(String name) { 133 check(GroupLayout.class, "attempting to select a group element from a non-group layout"); 134 GroupLayout g = (GroupLayout)layout; 135 long offset = 0; 136 MemoryLayout elem = null; 137 for (int i = 0; i < g.memberLayouts().size(); i++) { 138 MemoryLayout l = g.memberLayouts().get(i); 139 if (l.name().isPresent() && 140 l.name().get().equals(name)) { 141 elem = l; 142 break; 143 } else if (g instanceof StructLayout) { 144 offset += l.byteSize(); 145 } 146 } 147 if (elem == null) { 148 throw badLayoutPath("cannot resolve '" + name + "' in layout " + layout); 149 } 150 return LayoutPath.nestedPath(elem, this.offset + offset, strides, bounds, derefAdapters, this); 151 } 152 153 public LayoutPath groupElement(long index) { 154 check(GroupLayout.class, "attempting to select a group element from a non-group layout"); 155 GroupLayout g = (GroupLayout)layout; 156 long elemSize = g.memberLayouts().size(); 157 long offset = 0; 158 MemoryLayout elem = null; 159 for (int i = 0; i <= index; i++) { 160 if (i == elemSize) { 161 throw badLayoutPath("cannot resolve element " + index + " in layout " + layout); 162 } 163 elem = g.memberLayouts().get(i); 164 if (g instanceof StructLayout && i < index) { 165 offset += elem.byteSize(); 166 } 167 } 168 return LayoutPath.nestedPath(elem, this.offset + offset, strides, bounds, derefAdapters, this); 169 } 170 171 public LayoutPath derefElement() { 172 if (!(layout instanceof AddressLayout addressLayout) || 173 addressLayout.targetLayout().isEmpty()) { 174 throw badLayoutPath("Cannot dereference layout: " + layout); 175 } 176 MemoryLayout derefLayout = addressLayout.targetLayout().get(); 177 MethodHandle handle = dereferenceHandle(false).toMethodHandle(VarHandle.AccessMode.GET); 178 handle = MethodHandles.filterReturnValue(handle, 179 MethodHandles.insertArguments(MH_SEGMENT_RESIZE, 1, derefLayout)); 180 return derefPath(derefLayout, handle, this); 181 } 182 183 private static MemorySegment resizeSegment(MemorySegment segment, MemoryLayout layout) { 184 return Utils.longToAddress(segment.address(), layout.byteSize(), layout.byteAlignment()); 185 } 186 187 // Layout path projections 188 189 public long offset() { 190 return offset; 191 } 192 193 public VarHandle dereferenceHandle() { 194 return dereferenceHandle(true); 195 } 196 197 public VarHandle dereferenceHandle(boolean adapt) { 198 if (!(layout instanceof ValueLayout valueLayout)) { 199 throw new IllegalArgumentException("Path does not select a value layout"); 200 } 201 202 // If we have an enclosing layout, drop the alignment check for the accessed element, 203 // we check the root layout instead 204 ValueLayout accessedLayout = enclosing != null ? valueLayout.withByteAlignment(1) : valueLayout; 205 VarHandle handle = Utils.makeSegmentViewVarHandle(accessedLayout); 206 handle = MethodHandles.collectCoordinates(handle, 1, offsetHandle()); 207 208 // we only have to check the alignment of the root layout for the first dereference we do, 209 // as each dereference checks the alignment of the target address when constructing its segment 210 // (see Utils::longToAddress) 211 if (derefAdapters.length == 0 && enclosing != null) { 212 MethodHandle checkHandle = MethodHandles.insertArguments(MH_CHECK_ALIGN, 1, rootLayout()); 213 handle = MethodHandles.filterCoordinates(handle, 0, checkHandle); 214 } 215 216 if (adapt) { 217 for (int i = derefAdapters.length; i > 0; i--) { 218 handle = MethodHandles.collectCoordinates(handle, 0, derefAdapters[i - 1]); 219 } 220 } 221 return handle; 222 } 223 224 @ForceInline 225 private static long addScaledOffset(long base, long index, long stride, long bound) { 226 Objects.checkIndex(index, bound); 227 return base + (stride * index); 228 } 229 230 public MethodHandle offsetHandle() { 231 MethodHandle mh = MethodHandles.identity(long.class); 232 for (int i = strides.length - 1; i >=0; i--) { 233 MethodHandle collector = MethodHandles.insertArguments(MH_ADD_SCALED_OFFSET, 2, strides[i], bounds[i]); 234 // (J, ...) -> J to (J, J, ...) -> J 235 // i.e. new coord is prefixed. Last coord will correspond to innermost layout 236 mh = MethodHandles.collectArguments(mh, 0, collector); 237 } 238 mh = MethodHandles.insertArguments(mh, 0, offset); 239 return mh; 240 } 241 242 private MemoryLayout rootLayout() { 243 return enclosing != null ? enclosing.rootLayout() : this.layout; 244 } 245 246 public MethodHandle sliceHandle() { 247 MethodHandle sliceHandle; 248 if (enclosing != null) { 249 // drop the alignment check for the accessed element, we check the root layout instead 250 sliceHandle = MH_SLICE; // (MS, long, long) -> MS 251 sliceHandle = MethodHandles.insertArguments(sliceHandle, 2, layout.byteSize()); // (MS, long) -> MS 252 } else { 253 sliceHandle = MH_SLICE_LAYOUT; // (MS, long, MemoryLayout) -> MS 254 sliceHandle = MethodHandles.insertArguments(sliceHandle, 2, layout); // (MS, long) -> MS 255 } 256 sliceHandle = MethodHandles.collectArguments(sliceHandle, 1, offsetHandle()); // (MS, ...) -> MS 257 258 if (enclosing != null) { 259 MethodHandle checkHandle = MethodHandles.insertArguments(MH_CHECK_ALIGN, 1, rootLayout()); 260 sliceHandle = MethodHandles.filterArguments(sliceHandle, 0, checkHandle); 261 } 262 263 return sliceHandle; 264 } 265 266 private static MemorySegment checkAlign(MemorySegment segment, MemoryLayout constraint) { 267 if (!((AbstractMemorySegmentImpl) segment).isAlignedForElement(0, constraint)) { 268 throw new IllegalArgumentException("Target offset incompatible with alignment constraints: " + constraint.byteAlignment()); 269 } 270 return segment; 271 } 272 273 public MemoryLayout layout() { 274 return layout; 275 } 276 277 // Layout path construction 278 279 public static LayoutPath rootPath(MemoryLayout layout) { 280 return new LayoutPath(layout, 0L, EMPTY_STRIDES, EMPTY_BOUNDS, EMPTY_DEREF_HANDLES, null); 281 } 282 283 private static LayoutPath nestedPath(MemoryLayout layout, long offset, long[] strides, long[] bounds, MethodHandle[] derefAdapters, LayoutPath encl) { 284 return new LayoutPath(layout, offset, strides, bounds, derefAdapters, encl); 285 } 286 287 private static LayoutPath derefPath(MemoryLayout layout, MethodHandle handle, LayoutPath encl) { 288 MethodHandle[] handles = Arrays.copyOf(encl.derefAdapters, encl.derefAdapters.length + 1); 289 handles[encl.derefAdapters.length] = handle; 290 return new LayoutPath(layout, 0L, EMPTY_STRIDES, EMPTY_BOUNDS, handles, null); 291 } 292 293 // Helper methods 294 295 private void check(Class<?> layoutClass, String msg) { 296 if (!layoutClass.isAssignableFrom(layout.getClass())) { 297 throw badLayoutPath(msg); 298 } 299 } 300 301 private void checkSequenceBounds(SequenceLayout seq, long index) { 302 if (index >= seq.elementCount()) { 303 throw badLayoutPath(String.format("Sequence index out of bound; found: %d, size: %d", index, seq.elementCount())); 304 } 305 } 306 307 private static IllegalArgumentException badLayoutPath(String cause) { 308 return new IllegalArgumentException("Bad layout path: " + cause); 309 } 310 311 private long[] addStride(long stride) { 312 long[] newStrides = Arrays.copyOf(strides, strides.length + 1); 313 newStrides[strides.length] = stride; 314 return newStrides; 315 } 316 317 private long[] addBound(long maxIndex) { 318 long[] newBounds = Arrays.copyOf(bounds, bounds.length + 1); 319 newBounds[bounds.length] = maxIndex; 320 return newBounds; 321 } 322 323 /** 324 * This class provides an immutable implementation for the {@code PathElement} interface. A path element implementation 325 * is simply a pointer to one of the selector methods provided by the {@code LayoutPath} class. 326 */ 327 public static final class PathElementImpl implements MemoryLayout.PathElement, UnaryOperator<LayoutPath> { 328 329 public enum PathKind { 330 SEQUENCE_ELEMENT("unbound sequence element"), 331 SEQUENCE_ELEMENT_INDEX("bound sequence element"), 332 SEQUENCE_RANGE("sequence range"), 333 GROUP_ELEMENT("group element"), 334 DEREF_ELEMENT("dereference element"); 335 336 final String description; 337 338 PathKind(String description) { 339 this.description = description; 340 } 341 342 public String description() { 343 return description; 344 } 345 } 346 347 final PathKind kind; 348 final UnaryOperator<LayoutPath> pathOp; 349 350 public PathElementImpl(PathKind kind, UnaryOperator<LayoutPath> pathOp) { 351 this.kind = kind; 352 this.pathOp = pathOp; 353 } 354 355 @Override 356 public LayoutPath apply(LayoutPath layoutPath) { 357 return pathOp.apply(layoutPath); 358 } 359 360 public PathKind kind() { 361 return kind; 362 } 363 } 364 }