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.
  8  *
  9  *  This code is distributed in the hope that it will be useful, but WITHOUT
 10  *  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 11  *  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 12  *  version 2 for more details (a copy is included in the LICENSE file that
 13  *  accompanied this code).
 14  *
 15  *  You should have received a copy of the GNU General Public License version
 16  *  2 along with this work; if not, write to the Free Software Foundation,
 17  *  Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 18  *
 19  *   Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 20  *  or visit www.oracle.com if you need additional information or have any
 21  *  questions.
 22  *
 23  */
 24 
 25 /*
 26  * @test
 27  * @run testng TestLayoutPaths
 28  */
 29 
 30 import jdk.incubator.foreign.GroupLayout;
 31 import jdk.incubator.foreign.MemoryLayout;
 32 import jdk.incubator.foreign.MemoryLayout.PathElement;
 33 import jdk.incubator.foreign.MemorySegment;
 34 import jdk.incubator.foreign.ResourceScope;
 35 import jdk.incubator.foreign.SequenceLayout;
 36 
 37 import jdk.incubator.foreign.ValueLayout;
 38 import org.testng.SkipException;
 39 import org.testng.annotations.*;
 40 
 41 import java.lang.invoke.MethodHandle;
 42 import java.util.ArrayList;
 43 import java.util.List;
 44 
 45 import static jdk.incubator.foreign.MemoryLayout.PathElement.groupElement;
 46 import static jdk.incubator.foreign.MemoryLayout.PathElement.sequenceElement;
 47 import static jdk.incubator.foreign.ValueLayout.JAVA_INT;
 48 import static org.testng.Assert.*;
 49 
 50 public class TestLayoutPaths {
 51 
 52     @Test(expectedExceptions = IllegalArgumentException.class)
 53     public void testBadBitSelectFromSeq() {
 54         SequenceLayout seq = MemoryLayout.sequenceLayout(JAVA_INT);
 55         seq.bitOffset(groupElement("foo"));
 56     }
 57 
 58     @Test(expectedExceptions = IllegalArgumentException.class)
 59     public void testBadByteSelectFromSeq() {
 60         SequenceLayout seq = MemoryLayout.sequenceLayout(JAVA_INT);
 61         seq.byteOffset(groupElement("foo"));
 62     }
 63 
 64     @Test(expectedExceptions = IllegalArgumentException.class)
 65     public void testBadBitSelectFromStruct() {
 66         GroupLayout g = MemoryLayout.structLayout(JAVA_INT);
 67         g.bitOffset(sequenceElement());
 68     }
 69 
 70     @Test(expectedExceptions = IllegalArgumentException.class)
 71     public void testBadByteSelectFromStruct() {
 72         GroupLayout g = MemoryLayout.structLayout(JAVA_INT);
 73         g.byteOffset(sequenceElement());
 74     }
 75 
 76     @Test(expectedExceptions = IllegalArgumentException.class)
 77     public void testBadBitSelectFromValue() {
 78         SequenceLayout seq = MemoryLayout.sequenceLayout(JAVA_INT);
 79         seq.bitOffset(sequenceElement(), sequenceElement());
 80     }
 81 
 82     @Test(expectedExceptions = IllegalArgumentException.class)
 83     public void testBadByteSelectFromValue() {
 84         SequenceLayout seq = MemoryLayout.sequenceLayout(JAVA_INT);
 85         seq.byteOffset(sequenceElement(), sequenceElement());
 86     }
 87 
 88     @Test(expectedExceptions = IllegalArgumentException.class)
 89     public void testUnknownBitStructField() {
 90         GroupLayout g = MemoryLayout.structLayout(JAVA_INT);
 91         g.bitOffset(groupElement("foo"));
 92     }
 93 
 94     @Test(expectedExceptions = IllegalArgumentException.class)
 95     public void testUnknownByteStructField() {
 96         GroupLayout g = MemoryLayout.structLayout(JAVA_INT);
 97         g.byteOffset(groupElement("foo"));
 98     }
 99 
100     @Test(expectedExceptions = IllegalArgumentException.class)
101     public void testBitOutOfBoundsSeqIndex() {
102         SequenceLayout seq = MemoryLayout.sequenceLayout(5, JAVA_INT);
103         seq.bitOffset(sequenceElement(6));
104     }
105 
106     @Test(expectedExceptions = IllegalArgumentException.class)
107     public void testByteOutOfBoundsSeqIndex() {
108         SequenceLayout seq = MemoryLayout.sequenceLayout(5, JAVA_INT);
109         seq.byteOffset(sequenceElement(6));
110     }
111 
112     @Test(expectedExceptions = IllegalArgumentException.class)
113     public void testNegativeSeqIndex() {
114        sequenceElement(-2);
115     }
116 
117     @Test(expectedExceptions = IllegalArgumentException.class)
118     public void testBitNegativeSeqIndex() {
119         SequenceLayout seq = MemoryLayout.sequenceLayout(5, JAVA_INT);
120         seq.bitOffset(sequenceElement(-2));
121     }
122 
123     @Test(expectedExceptions = IllegalArgumentException.class)
124     public void testByteNegativeSeqIndex() {
125         SequenceLayout seq = MemoryLayout.sequenceLayout(5, JAVA_INT);
126         seq.byteOffset(sequenceElement(-2));
127     }
128 
129     @Test(expectedExceptions = IllegalArgumentException.class)
130     public void testOutOfBoundsSeqRange() {
131         SequenceLayout seq = MemoryLayout.sequenceLayout(5, JAVA_INT);
132         seq.bitOffset(sequenceElement(6, 2));
133     }
134 
135     @Test(expectedExceptions = IllegalArgumentException.class)
136     public void testNegativeSeqRange() {
137         sequenceElement(-2, 2);
138     }
139 
140     @Test(expectedExceptions = IllegalArgumentException.class)
141     public void testBitNegativeSeqRange() {
142         SequenceLayout seq = MemoryLayout.sequenceLayout(5, JAVA_INT);
143         seq.bitOffset(sequenceElement(-2, 2));
144     }
145 
146     @Test(expectedExceptions = IllegalArgumentException.class)
147     public void testByteNegativeSeqRange() {
148         SequenceLayout seq = MemoryLayout.sequenceLayout(5, JAVA_INT);
149         seq.byteOffset(sequenceElement(-2, 2));
150     }
151 
152     @Test(expectedExceptions = IllegalArgumentException.class)
153     public void testIncompleteAccess() {
154         SequenceLayout seq = MemoryLayout.sequenceLayout(5, MemoryLayout.structLayout(JAVA_INT));
155         seq.varHandle(sequenceElement());
156     }
157 
158     @Test(expectedExceptions = IllegalArgumentException.class)
159     public void testBitOffsetHandleBadRange() {
160         SequenceLayout seq = MemoryLayout.sequenceLayout(5, MemoryLayout.structLayout(JAVA_INT));
161         seq.bitOffsetHandle(sequenceElement(0, 1)); // ranges not accepted
162     }
163 
164     @Test(expectedExceptions = IllegalArgumentException.class)
165     public void testByteOffsetHandleBadRange() {
166         SequenceLayout seq = MemoryLayout.sequenceLayout(5, MemoryLayout.structLayout(JAVA_INT));
167         seq.byteOffsetHandle(sequenceElement(0, 1)); // ranges not accepted
168     }
169 
170     @Test(expectedExceptions = UnsupportedOperationException.class)
171     public void testBadMultiple() {
172         GroupLayout g = MemoryLayout.structLayout(MemoryLayout.paddingLayout(3), JAVA_INT.withName("foo"));
173         g.byteOffset(groupElement("foo"));
174     }
175 
176     @Test(expectedExceptions = UnsupportedOperationException.class)
177     public void testBitOffsetBadUnboundedSequenceTraverse() {
178         MemoryLayout layout = MemoryLayout.sequenceLayout(MemoryLayout.sequenceLayout(JAVA_INT));
179         layout.bitOffset(sequenceElement(1), sequenceElement(0));
180     }
181 
182     @Test(expectedExceptions = UnsupportedOperationException.class)
183     public void testByteOffsetBadUnboundedSequenceTraverse() {
184         MemoryLayout layout = MemoryLayout.sequenceLayout(MemoryLayout.sequenceLayout(JAVA_INT));
185         layout.byteOffset(sequenceElement(1), sequenceElement(0));
186     }
187 
188     @Test(expectedExceptions = UnsupportedOperationException.class)
189     public void testBitOffsetHandleBadUnboundedSequenceTraverse() {
190         MemoryLayout layout = MemoryLayout.sequenceLayout(MemoryLayout.sequenceLayout(JAVA_INT));
191         layout.bitOffsetHandle(sequenceElement(1), sequenceElement(0));
192     }
193 
194     @Test(expectedExceptions = UnsupportedOperationException.class)
195     public void testByteOffsetHandleBadUnboundedSequenceTraverse() {
196         MemoryLayout layout = MemoryLayout.sequenceLayout(MemoryLayout.sequenceLayout(JAVA_INT));
197         layout.byteOffsetHandle(sequenceElement(1), sequenceElement(0));
198     }
199 
200     @Test(expectedExceptions = UnsupportedOperationException.class)
201     public void testBadByteOffsetNoMultipleOf8() {
202         MemoryLayout layout = MemoryLayout.structLayout(MemoryLayout.paddingLayout(7), JAVA_INT.withName("x"));
203         layout.byteOffset(groupElement("x"));
204     }
205 
206     @Test(expectedExceptions = UnsupportedOperationException.class)
207     public void testBadByteOffsetHandleNoMultipleOf8() throws Throwable {
208         MemoryLayout layout = MemoryLayout.structLayout(MemoryLayout.paddingLayout(7), JAVA_INT.withName("x"));
209         MethodHandle handle = layout.byteOffsetHandle(groupElement("x"));
210         handle.invoke();
211     }
212 
213     @Test
214     public void testBadContainerAlign() {
215         GroupLayout g = MemoryLayout.structLayout(JAVA_INT.withBitAlignment(16).withName("foo")).withBitAlignment(8);
216         try {
217             g.bitOffset(groupElement("foo"));
218             g.byteOffset(groupElement("foo"));
219         } catch (Throwable ex) {
220             throw new AssertionError(ex); // should be ok!
221         }
222         try {
223             g.varHandle(groupElement("foo")); //ok
224             assertTrue(false); //should fail!
225         } catch (UnsupportedOperationException ex) {
226             //ok
227         } catch (Throwable ex) {
228             throw new AssertionError(ex); //should fail!
229         }
230     }
231 
232     @Test
233     public void testBadAlignOffset() {
234         GroupLayout g = MemoryLayout.structLayout(MemoryLayout.paddingLayout(8), JAVA_INT.withBitAlignment(16).withName("foo"));
235         try {
236             g.bitOffset(groupElement("foo"));
237             g.byteOffset(groupElement("foo"));
238         } catch (Throwable ex) {
239             throw new AssertionError(ex); // should be ok!
240         }
241         try {
242             g.varHandle(groupElement("foo")); //ok
243             assertTrue(false); //should fail!
244         } catch (UnsupportedOperationException ex) {
245             //ok
246         } catch (Throwable ex) {
247             throw new AssertionError(ex); //should fail!
248         }
249     }
250 
251     @Test
252     public void testBadSequencePathInOffset() {
253         SequenceLayout seq = MemoryLayout.sequenceLayout(10, JAVA_INT);
254         // bad path elements
255         for (PathElement e : List.of( sequenceElement(), sequenceElement(0, 2) )) {
256             try {
257                 seq.bitOffset(e);
258                 fail();
259             } catch (IllegalArgumentException ex) {
260                 assertTrue(true);
261             }
262             try {
263                 seq.byteOffset(e);
264                 fail();
265             } catch (IllegalArgumentException ex) {
266                 assertTrue(true);
267             }
268         }
269     }
270 
271     @Test
272     public void testBadSequencePathInSelect() {
273         SequenceLayout seq = MemoryLayout.sequenceLayout(10, JAVA_INT);
274         for (PathElement e : List.of( sequenceElement(0), sequenceElement(0, 2) )) {
275             try {
276                 seq.select(e);
277                 fail();
278             } catch (IllegalArgumentException ex) {
279                 assertTrue(true);
280             }
281         }
282     }
283 
284     @Test
285     public void testBadSequencePathInMap() {
286         SequenceLayout seq = MemoryLayout.sequenceLayout(10, JAVA_INT);
287         for (PathElement e : List.of( sequenceElement(0), sequenceElement(0, 2) )) {
288             try {
289                 seq.map(l -> l, e);
290                 fail();
291             } catch (IllegalArgumentException ex) {
292                 assertTrue(true);
293             }
294         }
295     }
296 
297     @Test
298     public void testStructPaths() {
299         long[] offsets = { 0, 8, 24, 56 };
300         GroupLayout g = MemoryLayout.structLayout(
301                 ValueLayout.JAVA_BYTE.withName("1"),
302                 ValueLayout.JAVA_CHAR.withName("2"),
303                 ValueLayout.JAVA_FLOAT.withName("3"),
304                 ValueLayout.JAVA_LONG.withName("4")
305         );
306 
307         // test select
308 
309         for (int i = 1 ; i <= 4 ; i++) {
310             MemoryLayout selected = g.select(groupElement(String.valueOf(i)));
311             assertTrue(selected == g.memberLayouts().get(i - 1));
312         }
313 
314         // test offset
315 
316         for (int i = 1 ; i <= 4 ; i++) {
317             long bitOffset = g.bitOffset(groupElement(String.valueOf(i)));
318             assertEquals(offsets[i - 1], bitOffset);
319             long byteOffset = g.byteOffset(groupElement(String.valueOf(i)));
320             assertEquals((offsets[i - 1]) >>> 3, byteOffset);
321         }
322 
323         // test map
324 
325         for (int i = 1 ; i <= 4 ; i++) {
326             GroupLayout g2 = (GroupLayout)g.map(l -> ValueLayout.JAVA_DOUBLE, groupElement(String.valueOf(i)));
327             assertTrue(g2.isStruct());
328             for (int j = 0 ; j < 4 ; j++) {
329                 if (j == i - 1) {
330                     assertEquals(g2.memberLayouts().get(j), ValueLayout.JAVA_DOUBLE);
331                 } else {
332                     assertEquals(g2.memberLayouts().get(j), g.memberLayouts().get(j));
333                 }
334             }
335         }
336     }
337 
338     @Test
339     public void testUnionPaths() {
340         long[] offsets = { 0, 0, 0, 0 };
341         GroupLayout g = MemoryLayout.unionLayout(
342                 ValueLayout.JAVA_BYTE.withName("1"),
343                 ValueLayout.JAVA_CHAR.withName("2"),
344                 ValueLayout.JAVA_FLOAT.withName("3"),
345                 ValueLayout.JAVA_LONG.withName("4")
346         );
347 
348         // test select
349 
350         for (int i = 1 ; i <= 4 ; i++) {
351             MemoryLayout selected = g.select(groupElement(String.valueOf(i)));
352             assertTrue(selected == g.memberLayouts().get(i - 1));
353         }
354 
355         // test offset
356 
357         for (int i = 1 ; i <= 4 ; i++) {
358             long bitOffset = g.bitOffset(groupElement(String.valueOf(i)));
359             assertEquals(offsets[i - 1], bitOffset);
360             long byteOffset = g.byteOffset(groupElement(String.valueOf(i)));
361             assertEquals((offsets[i - 1]) >>> 3, byteOffset);
362         }
363 
364         // test map
365 
366         for (int i = 1 ; i <= 4 ; i++) {
367             GroupLayout g2 = (GroupLayout)g.map(l -> ValueLayout.JAVA_DOUBLE, groupElement(String.valueOf(i)));
368             assertTrue(g2.isUnion());
369             for (int j = 0 ; j < 4 ; j++) {
370                 if (j == i - 1) {
371                     assertEquals(g2.memberLayouts().get(j), ValueLayout.JAVA_DOUBLE);
372                 } else {
373                     assertEquals(g2.memberLayouts().get(j), g.memberLayouts().get(j));
374                 }
375             }
376         }
377     }
378 
379     @Test
380     public void testSequencePaths() {
381         long[] offsets = { 0, 8, 16, 24 };
382         SequenceLayout g = MemoryLayout.sequenceLayout(4, ValueLayout.JAVA_BYTE);
383 
384         // test select
385 
386         MemoryLayout selected = g.select(sequenceElement());
387         assertTrue(selected == ValueLayout.JAVA_BYTE);
388 
389         // test offset
390 
391         for (int i = 0 ; i < 4 ; i++) {
392             long bitOffset = g.bitOffset(sequenceElement(i));
393             assertEquals(offsets[i], bitOffset);
394             long byteOffset = g.byteOffset(sequenceElement(i));
395             assertEquals((offsets[i]) >>> 3, byteOffset);
396         }
397 
398         // test map
399 
400         SequenceLayout seq2 = (SequenceLayout)g.map(l -> ValueLayout.JAVA_DOUBLE, sequenceElement());
401         assertTrue(seq2.elementLayout() == ValueLayout.JAVA_DOUBLE);
402     }
403 
404     @Test(dataProvider = "testLayouts")
405     public void testOffsetHandle(MemoryLayout layout, PathElement[] pathElements, long[] indexes,
406                                  long expectedBitOffset) throws Throwable {
407         MethodHandle bitOffsetHandle = layout.bitOffsetHandle(pathElements);
408         bitOffsetHandle = bitOffsetHandle.asSpreader(long[].class, indexes.length);
409         long actualBitOffset = (long) bitOffsetHandle.invokeExact(indexes);
410         assertEquals(actualBitOffset, expectedBitOffset);
411         if (expectedBitOffset % 8 == 0) {
412             MethodHandle byteOffsetHandle = layout.byteOffsetHandle(pathElements);
413             byteOffsetHandle = byteOffsetHandle.asSpreader(long[].class, indexes.length);
414             long actualByteOffset = (long) byteOffsetHandle.invokeExact(indexes);
415             assertEquals(actualByteOffset, expectedBitOffset / 8);
416         }
417     }
418 
419     @DataProvider
420     public static Object[][] testLayouts() {
421         List<Object[]> testCases = new ArrayList<>();
422 
423         testCases.add(new Object[] {
424             MemoryLayout.sequenceLayout(10, JAVA_INT),
425             new PathElement[] { sequenceElement() },
426             new long[] { 4 },
427             JAVA_INT.bitSize() * 4
428         });
429         testCases.add(new Object[] {
430             MemoryLayout.sequenceLayout(10, MemoryLayout.structLayout(JAVA_INT, JAVA_INT.withName("y"))),
431             new PathElement[] { sequenceElement(), groupElement("y") },
432             new long[] { 4 },
433             (JAVA_INT.bitSize() * 2) * 4 + JAVA_INT.bitSize()
434         });
435         testCases.add(new Object[] {
436             MemoryLayout.sequenceLayout(10, MemoryLayout.structLayout(MemoryLayout.paddingLayout(5), JAVA_INT.withName("y"))),
437             new PathElement[] { sequenceElement(), groupElement("y") },
438             new long[] { 4 },
439             (JAVA_INT.bitSize() + 5) * 4 + 5
440         });
441         testCases.add(new Object[] {
442             MemoryLayout.sequenceLayout(10, JAVA_INT),
443             new PathElement[] { sequenceElement() },
444             new long[] { 4 },
445             JAVA_INT.bitSize() * 4
446         });
447         testCases.add(new Object[] {
448             MemoryLayout.structLayout(
449                 MemoryLayout.sequenceLayout(10, JAVA_INT).withName("data")
450             ),
451             new PathElement[] { groupElement("data"), sequenceElement() },
452             new long[] { 4 },
453             JAVA_INT.bitSize() * 4
454         });
455 
456         MemoryLayout complexLayout = MemoryLayout.structLayout(
457             MemoryLayout.sequenceLayout(10,
458                 MemoryLayout.sequenceLayout(10,
459                     MemoryLayout.structLayout(
460                         JAVA_INT.withName("x"),
461                         JAVA_INT.withName("y")
462                     )
463                 )
464             ).withName("data")
465         );
466 
467         testCases.add(new Object[] {
468             complexLayout,
469             new PathElement[] { groupElement("data"), sequenceElement(), sequenceElement(), groupElement("x") },
470             new long[] { 0, 1 },
471             (JAVA_INT.bitSize() * 2)
472         });
473         testCases.add(new Object[] {
474             complexLayout,
475             new PathElement[] { groupElement("data"), sequenceElement(), sequenceElement(), groupElement("x") },
476             new long[] { 1, 0 },
477             (JAVA_INT.bitSize() * 2) * 10
478         });
479         testCases.add(new Object[] {
480             complexLayout,
481             new PathElement[] { groupElement("data"), sequenceElement(), sequenceElement(), groupElement("y") },
482             new long[] { 0, 1 },
483             (JAVA_INT.bitSize() * 2) + JAVA_INT.bitSize()
484         });
485         testCases.add(new Object[] {
486             complexLayout,
487             new PathElement[] { groupElement("data"), sequenceElement(), sequenceElement(), groupElement("y") },
488             new long[] { 1, 0 },
489             (JAVA_INT.bitSize() * 2) * 10 + JAVA_INT.bitSize()
490         });
491 
492         return testCases.toArray(Object[][]::new);
493     }
494 
495     @Test(dataProvider = "testLayouts")
496     public void testSliceHandle(MemoryLayout layout, PathElement[] pathElements, long[] indexes,
497                                 long expectedBitOffset) throws Throwable {
498         if (expectedBitOffset % 8 != 0)
499             throw new SkipException("Offset not a multiple of 8");
500 
501         MemoryLayout selected = layout.select(pathElements);
502         MethodHandle sliceHandle = layout.sliceHandle(pathElements);
503         sliceHandle = sliceHandle.asSpreader(long[].class, indexes.length);
504 
505         try (ResourceScope scope = ResourceScope.newConfinedScope()) {
506             MemorySegment segment = MemorySegment.allocateNative(layout, scope);
507             MemorySegment slice = (MemorySegment) sliceHandle.invokeExact(segment, indexes);
508             assertEquals(slice.address().toRawLongValue() - segment.address().toRawLongValue(), expectedBitOffset / 8);
509             assertEquals(slice.byteSize(), selected.byteSize());
510         }
511     }
512 
513     @Test(expectedExceptions = UnsupportedOperationException.class)
514     public void testSliceHandleUOEInvalidOffsetEager() throws Throwable {
515         MemoryLayout layout = MemoryLayout.structLayout(
516             MemoryLayout.paddingLayout(5),
517             JAVA_INT.withName("y") // offset not a multiple of 8
518         );
519 
520         layout.sliceHandle(groupElement("y")); // should throw
521     }
522 
523     @Test(expectedExceptions = UnsupportedOperationException.class)
524     public void testSliceHandleUOEInvalidOffsetLate() throws Throwable {
525         MemoryLayout layout = MemoryLayout.sequenceLayout(3,
526             MemoryLayout.structLayout(
527                 MemoryLayout.paddingLayout(4),
528                     JAVA_INT.withName("y") // offset not a multiple of 8
529             )
530         );
531 
532         MethodHandle sliceHandle;
533         try {
534             sliceHandle = layout.sliceHandle(sequenceElement(), groupElement("y")); // should work
535         } catch (UnsupportedOperationException uoe) {
536             fail("Unexpected exception", uoe);
537             return;
538         }
539 
540         try (ResourceScope scope = ResourceScope.newConfinedScope()) {
541             MemorySegment segment = MemorySegment.allocateNative(layout, scope);
542 
543             try {
544                 sliceHandle.invokeExact(segment, 1); // should work
545             } catch (UnsupportedOperationException uoe) {
546                 fail("Unexpected exception", uoe);
547                 return;
548             }
549 
550             sliceHandle.invokeExact(segment, 0); // should throw
551         }
552     }
553 }
554