1 /*
2  *  Copyright (c) 2019, 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.incubator.foreign.MemoryHandles;
29 import jdk.incubator.foreign.MemoryLayout;
30 import jdk.incubator.foreign.MemorySegment;
31 import jdk.internal.access.JavaLangInvokeAccess;
32 import jdk.internal.access.SharedSecrets;
33 import jdk.internal.access.foreign.MemorySegmentProxy;
34 
35 import jdk.incubator.foreign.GroupLayout;
36 import jdk.incubator.foreign.SequenceLayout;
37 import jdk.incubator.foreign.ValueLayout;
38 import sun.invoke.util.Wrapper;
39 
40 import java.lang.invoke.MethodHandle;
41 import java.lang.invoke.MethodHandles;
42 import java.lang.invoke.MethodType;
43 import java.lang.invoke.VarHandle;
44 import java.util.ArrayDeque;
45 import java.util.ArrayList;
46 import java.util.Deque;
47 import java.util.List;
48 import java.util.function.ToLongFunction;
49 import java.util.function.UnaryOperator;
50 
51 /**
52  * This class provide support for constructing layout paths; that is, starting from a root path (see {@link #rootPath(MemoryLayout, ToLongFunction)},
53  * a path can be constructed by selecting layout elements using the selector methods provided by this class
54  * (see {@link #sequenceElement()}, {@link #sequenceElement(long)}, {@link #sequenceElement(long, long)}, {@link #groupElement(String)}).
55  * Once a path has been fully constructed, clients can ask for the offset associated with the layout element selected
56  * by the path (see {@link #offset}), or obtain a memory access var handle to access the selected layout element
57  * given an address pointing to a segment associated with the root layout (see {@link #dereferenceHandle(Class)}).
58  */
59 public class LayoutPath {
60 
61     private static final JavaLangInvokeAccess JLI = SharedSecrets.getJavaLangInvokeAccess();
62 
63     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;
67 
68     static {
69         try {
70             MethodHandles.Lookup lookup = MethodHandles.lookup();
71             ADD_STRIDE = lookup.findStatic(LayoutPath.class, "addStride",
72                     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));
75         } catch (Throwable ex) {
76             throw new ExceptionInInitializerError(ex);
77         }
78     }
79 
80     private final MemoryLayout layout;
81     private final long offset;
82     private final LayoutPath enclosing;
83     private final long[] strides;
84     private final long elementIndex;
85     private final ToLongFunction<MemoryLayout> sizeFunc;
86 
LayoutPath(MemoryLayout layout, long offset, long[] strides, long elementIndex, LayoutPath enclosing, ToLongFunction<MemoryLayout> sizeFunc)87     private LayoutPath(MemoryLayout layout, long offset, long[] strides, long elementIndex, LayoutPath enclosing, ToLongFunction<MemoryLayout> sizeFunc) {
88         this.layout = layout;
89         this.offset = offset;
90         this.strides = strides;
91         this.enclosing = enclosing;
92         this.elementIndex = elementIndex;
93         this.sizeFunc = sizeFunc;
94     }
95 
96     // Layout path selector methods
97 
sequenceElement()98     public LayoutPath sequenceElement() {
99         check(SequenceLayout.class, "attempting to select a sequence element from a non-sequence layout");
100         SequenceLayout seq = (SequenceLayout)layout;
101         MemoryLayout elem = seq.elementLayout();
102         return LayoutPath.nestedPath(elem, offset, addStride(sizeFunc.applyAsLong(elem)), UNSPECIFIED_ELEM_INDEX, this);
103     }
104 
sequenceElement(long start, long step)105     public LayoutPath sequenceElement(long start, long step) {
106         check(SequenceLayout.class, "attempting to select a sequence element from a non-sequence layout");
107         SequenceLayout seq = (SequenceLayout)layout;
108         checkSequenceBounds(seq, start);
109         MemoryLayout elem = seq.elementLayout();
110         long elemSize = sizeFunc.applyAsLong(elem);
111         return LayoutPath.nestedPath(elem, offset + (start * elemSize), addStride(elemSize * step),
112                 UNSPECIFIED_ELEM_INDEX, this);
113     }
114 
sequenceElement(long index)115     public LayoutPath sequenceElement(long index) {
116         check(SequenceLayout.class, "attempting to select a sequence element from a non-sequence layout");
117         SequenceLayout seq = (SequenceLayout)layout;
118         checkSequenceBounds(seq, index);
119         long elemOffset = 0;
120         if (index > 0) {
121             //if index == 0, we do not depend on sequence element size, so skip
122             long elemSize = sizeFunc.applyAsLong(seq.elementLayout());
123             elemOffset = elemSize * index;
124         }
125         return LayoutPath.nestedPath(seq.elementLayout(), offset + elemOffset, strides, index, this);
126     }
127 
groupElement(String name)128     public LayoutPath groupElement(String name) {
129         check(GroupLayout.class, "attempting to select a group element from a non-group layout");
130         GroupLayout g = (GroupLayout)layout;
131         long offset = 0;
132         MemoryLayout elem = null;
133         int index = -1;
134         for (int i = 0; i < g.memberLayouts().size(); i++) {
135             MemoryLayout l = g.memberLayouts().get(i);
136             if (l.name().isPresent() &&
137                 l.name().get().equals(name)) {
138                 elem = l;
139                 index = i;
140                 break;
141             } else if (g.isStruct()) {
142                 offset += sizeFunc.applyAsLong(l);
143             }
144         }
145         if (elem == null) {
146             throw badLayoutPath("cannot resolve '" + name + "' in layout " + layout);
147         }
148         return LayoutPath.nestedPath(elem, this.offset + offset, strides, index, this);
149     }
150 
151     // Layout path projections
152 
offset()153     public long offset() {
154         return offset;
155     }
156 
dereferenceHandle(Class<?> carrier)157     public VarHandle dereferenceHandle(Class<?> carrier) {
158         Utils.checkPrimitiveCarrierCompat(carrier, layout);
159         checkAlignment(this);
160 
161         List<Class<?>> expectedCoordinates = new ArrayList<>();
162         Deque<Integer> perms = new ArrayDeque<>();
163         perms.addFirst(0);
164         expectedCoordinates.add(MemorySegment.class);
165 
166         VarHandle handle = Utils.fixUpVarHandle(JLI.memoryAccessVarHandle(carrier, true, layout.byteAlignment() - 1,
167                 ((ValueLayout)layout).order()));
168 
169         for (int i = 0 ; i < strides.length ; i++) {
170             expectedCoordinates.add(long.class);
171             perms.addFirst(0);
172             perms.addLast(i + 1);
173             //add stride
174             handle = MemoryHandles.collectCoordinates(handle, 1 + i,
175                     MethodHandles.insertArguments(ADD_STRIDE, 1, Utils.bitsToBytesOrThrow(strides[strides.length - 1 - i], IllegalStateException::new))); // MS, long, MS_n, long_n, long
176         }
177         //add offset
178         handle = MemoryHandles.insertCoordinates(handle, 1 + strides.length, Utils.bitsToBytesOrThrow(offset, IllegalStateException::new));
179 
180         if (strides.length > 0) {
181             // remove duplicate MS args
182             handle = MemoryHandles.permuteCoordinates(handle, expectedCoordinates, perms.stream().mapToInt(i -> i).toArray());
183         }
184         return handle;
185     }
186 
addScaledOffset(long base, long index, long stride)187     private static long addScaledOffset(long base, long index, long stride) {
188         return base + (stride * index);
189     }
190 
offsetHandle()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 
layout()203     public MemoryLayout layout() {
204         return layout;
205     }
206 
map(UnaryOperator<MemoryLayout> op)207     public MemoryLayout map(UnaryOperator<MemoryLayout> op) {
208         MemoryLayout newLayout = op.apply(layout);
209         if (enclosing == null) {
210             return newLayout;
211         } else if (enclosing.layout instanceof SequenceLayout) {
212             SequenceLayout seq = (SequenceLayout)enclosing.layout;
213             if (seq.elementCount().isPresent()) {
214                 return enclosing.map(l -> dup(l, MemoryLayout.ofSequence(seq.elementCount().getAsLong(), newLayout)));
215             } else {
216                 return enclosing.map(l -> dup(l, MemoryLayout.ofSequence(newLayout)));
217             }
218         } else if (enclosing.layout instanceof GroupLayout) {
219             GroupLayout g = (GroupLayout)enclosing.layout;
220             List<MemoryLayout> newElements = new ArrayList<>(g.memberLayouts());
221             //if we selected a layout in a group we must have a valid index
222             newElements.set((int)elementIndex, newLayout);
223             if (g.isUnion()) {
224                 return enclosing.map(l -> dup(l, MemoryLayout.ofUnion(newElements.toArray(new MemoryLayout[0]))));
225             } else {
226                 return enclosing.map(l -> dup(l, MemoryLayout.ofStruct(newElements.toArray(new MemoryLayout[0]))));
227             }
228         } else {
229             return newLayout;
230         }
231     }
232 
dup(MemoryLayout oldLayout, MemoryLayout newLayout)233     private MemoryLayout dup(MemoryLayout oldLayout, MemoryLayout newLayout) {
234         newLayout = newLayout.withBitAlignment(oldLayout.bitAlignment());
235         if (oldLayout.name().isPresent()) {
236             newLayout.withName(oldLayout.name().get());
237         }
238         return newLayout;
239     }
240 
241     // Layout path construction
242 
rootPath(MemoryLayout layout, ToLongFunction<MemoryLayout> sizeFunc)243     public static LayoutPath rootPath(MemoryLayout layout, ToLongFunction<MemoryLayout> sizeFunc) {
244         return new LayoutPath(layout, 0L, EMPTY_STRIDES, -1, null, sizeFunc);
245     }
246 
nestedPath(MemoryLayout layout, long offset, long[] strides, long elementIndex, LayoutPath encl)247     private static LayoutPath nestedPath(MemoryLayout layout, long offset, long[] strides, long elementIndex, LayoutPath encl) {
248         return new LayoutPath(layout, offset, strides, elementIndex, encl, encl.sizeFunc);
249     }
250 
251     // Helper methods
252 
check(Class<?> layoutClass, String msg)253     private void check(Class<?> layoutClass, String msg) {
254         if (!layoutClass.isAssignableFrom(layout.getClass())) {
255             throw badLayoutPath(msg);
256         }
257     }
258 
checkSequenceBounds(SequenceLayout seq, long index)259     private void checkSequenceBounds(SequenceLayout seq, long index) {
260         if (seq.elementCount().isPresent() && index >= seq.elementCount().getAsLong()) {
261             throw badLayoutPath(String.format("Sequence index out of bound; found: %d, size: %d", index, seq.elementCount().getAsLong()));
262         }
263     }
264 
badLayoutPath(String cause)265     private static IllegalArgumentException badLayoutPath(String cause) {
266         return new IllegalArgumentException("Bad layout path: " + cause);
267     }
268 
checkAlignment(LayoutPath path)269     private static void checkAlignment(LayoutPath path) {
270         MemoryLayout layout = path.layout;
271         long alignment = layout.bitAlignment();
272         if (path.offset % alignment != 0) {
273             throw new UnsupportedOperationException("Invalid alignment requirements for layout " + layout);
274         }
275         for (long stride : path.strides) {
276             if (stride % alignment != 0) {
277                 throw new UnsupportedOperationException("Alignment requirements for layout " + layout + " do not match stride " + stride);
278             }
279         }
280         LayoutPath encl = path.enclosing;
281         if (encl != null) {
282             if (encl.layout.bitAlignment() < alignment) {
283                 throw new UnsupportedOperationException("Alignment requirements for layout " + layout + " do not match those for enclosing layout " + encl.layout);
284             }
285             checkAlignment(encl);
286         }
287     }
288 
addStride(long stride)289     private long[] addStride(long stride) {
290         long[] newStrides = new long[strides.length + 1];
291         System.arraycopy(strides, 0, newStrides, 0, strides.length);
292         newStrides[strides.length] = stride;
293         return newStrides;
294     }
295 
296     private static final long[] EMPTY_STRIDES = new long[0];
297 
298     /**
299      * This class provides an immutable implementation for the {@code PathElement} interface. A path element implementation
300      * is simply a pointer to one of the selector methods provided by the {@code LayoutPath} class.
301      */
302     public static class PathElementImpl implements MemoryLayout.PathElement, UnaryOperator<LayoutPath> {
303 
304         public enum PathKind {
305             SEQUENCE_ELEMENT("unbound sequence element"),
306             SEQUENCE_ELEMENT_INDEX("bound sequence element"),
307             SEQUENCE_RANGE("sequence range"),
308             GROUP_ELEMENT("group element");
309 
310             final String description;
311 
PathKind(String description)312             PathKind(String description) {
313                 this.description = description;
314             }
315 
description()316             public String description() {
317                 return description;
318             }
319         }
320 
321         final PathKind kind;
322         final UnaryOperator<LayoutPath> pathOp;
323 
PathElementImpl(PathKind kind, UnaryOperator<LayoutPath> pathOp)324         public PathElementImpl(PathKind kind, UnaryOperator<LayoutPath> pathOp) {
325             this.kind = kind;
326             this.pathOp = pathOp;
327         }
328 
329         @Override
apply(LayoutPath layoutPath)330         public LayoutPath apply(LayoutPath layoutPath) {
331             return pathOp.apply(layoutPath);
332         }
333 
kind()334         public PathKind kind() {
335             return kind;
336         }
337     }
338 
addStride(MemorySegment segment, long stride, long base, long index)339     private static long addStride(MemorySegment segment, long stride, long base, long index) {
340         return MemorySegmentProxy.addOffsets(base,
341                     MemorySegmentProxy.multiplyOffsets(stride, index, ((MemorySegmentProxy)segment)), (MemorySegmentProxy)segment);
342     }
343 }
344