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.
  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  * @test
 26  * @run testng TestLayouts
 27  */
 28 
 29 import java.lang.foreign.*;
 30 
 31 import java.lang.invoke.VarHandle;
 32 import java.nio.ByteOrder;
 33 import java.util.ArrayList;
 34 import java.util.List;
 35 import java.util.function.LongFunction;
 36 import java.util.stream.Stream;
 37 
 38 import org.testng.annotations.*;
 39 
 40 import static java.lang.foreign.ValueLayout.*;
 41 import static org.testng.Assert.*;
 42 
 43 public class TestLayouts {
 44 
 45     @Test(dataProvider = "badAlignments", expectedExceptions = IllegalArgumentException.class)
 46     public void testBadLayoutAlignment(MemoryLayout layout, long alignment) {
 47         layout.withByteAlignment(alignment);
 48     }
 49 
 50     @Test(dataProvider = "basicLayoutsAndAddressAndGroups")
 51     public void testEqualities(MemoryLayout layout) {
 52 
 53         // Use another Type
 54         MemoryLayout differentType = MemoryLayout.paddingLayout(1);
 55         assertFalse(layout.equals(differentType));
 56 
 57         // Use another name
 58         MemoryLayout differentName = layout.withName("CustomName");
 59         assertFalse(layout.equals(differentName));
 60 
 61         // Use another alignment
 62         MemoryLayout differentAlignment = layout.withByteAlignment(layout.byteAlignment() * 2);
 63         assertFalse(layout.equals(differentAlignment));
 64 
 65         // Swap endian
 66         MemoryLayout differentOrder = JAVA_INT.withOrder(JAVA_INT.order() == ByteOrder.BIG_ENDIAN ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN);
 67         assertFalse(layout.equals(differentOrder));
 68 
 69         // Something totally different
 70         assertFalse(layout.equals("A"));
 71 
 72         // Null
 73         assertFalse(layout.equals(null));
 74 
 75         // Identity
 76         assertTrue(layout.equals(layout));
 77 
 78         assertFalse(layout.equals(MemoryLayout.sequenceLayout(13, JAVA_LONG)));
 79 
 80         MemoryLayout other = layout.withByteAlignment(16).withByteAlignment(layout.byteAlignment());
 81         assertTrue(layout.equals(other));
 82 
 83     }
 84 
 85     public void testTargetLayoutEquals() {
 86         MemoryLayout differentTargetLayout = ADDRESS.withTargetLayout(JAVA_CHAR);
 87         assertFalse(ADDRESS.equals(differentTargetLayout));
 88         var equalButNotSame = ADDRESS.withTargetLayout(JAVA_INT).withTargetLayout(JAVA_CHAR);
 89         assertTrue(differentTargetLayout.equals(equalButNotSame));
 90     }
 91 
 92     @Test
 93     public void testIndexedSequencePath() {
 94         MemoryLayout seq = MemoryLayout.sequenceLayout(10, ValueLayout.JAVA_INT);
 95         try (Arena arena = Arena.ofConfined()) {
 96             MemorySegment segment = arena.allocate(seq);;
 97             VarHandle indexHandle = seq.varHandle(MemoryLayout.PathElement.sequenceElement());
 98             // init segment
 99             for (int i = 0 ; i < 10 ; i++) {
100                 indexHandle.set(segment, 0L, (long)i, i);
101             }
102             //check statically indexed handles
103             for (int i = 0 ; i < 10 ; i++) {
104                 VarHandle preindexHandle = seq.varHandle(MemoryLayout.PathElement.sequenceElement(i));
105                 int expected = (int)indexHandle.get(segment, 0L, (long)i);
106                 int found = (int)preindexHandle.get(segment, 0L);
107                 assertEquals(expected, found);
108             }
109         }
110     }
111 
112     @Test(expectedExceptions = IllegalArgumentException.class)
113     public void testBadBoundSequenceLayoutResize() {
114         SequenceLayout seq = MemoryLayout.sequenceLayout(10, ValueLayout.JAVA_INT);
115         seq.withElementCount(-1);
116     }
117 
118     @Test(expectedExceptions = IllegalArgumentException.class)
119     public void testReshape() {
120         SequenceLayout layout = MemoryLayout.sequenceLayout(10, JAVA_INT);
121         layout.reshape();
122     }
123 
124     @Test(dataProvider = "basicLayoutsAndAddressAndGroups", expectedExceptions = IllegalArgumentException.class)
125     public void testGroupIllegalAlignmentNotPowerOfTwo(MemoryLayout layout) {
126         layout.withByteAlignment(9);
127     }
128 
129     @Test(dataProvider = "basicLayoutsAndAddressAndGroups", expectedExceptions = IllegalArgumentException.class)
130     public void testGroupIllegalAlignmentNotGreaterOrEqualTo1(MemoryLayout layout) {
131         layout.withByteAlignment(0);
132     }
133 
134     @Test
135     public void testEqualsPadding() {
136         PaddingLayout paddingLayout = MemoryLayout.paddingLayout(2);
137         testEqualities(paddingLayout);
138         PaddingLayout paddingLayout2 = MemoryLayout.paddingLayout(4);
139         assertNotEquals(paddingLayout, paddingLayout2);
140     }
141 
142     @Test
143     public void testEmptyGroup() {
144         MemoryLayout struct = MemoryLayout.structLayout();
145         assertEquals(struct.byteSize(), 0);
146         assertEquals(struct.byteAlignment(), 1);
147 
148         MemoryLayout union = MemoryLayout.unionLayout();
149         assertEquals(union.byteSize(), 0);
150         assertEquals(union.byteAlignment(), 1);
151     }
152 
153     @Test
154     public void testStructSizeAndAlign() {
155         MemoryLayout struct = MemoryLayout.structLayout(
156                 MemoryLayout.paddingLayout(1),
157                 ValueLayout.JAVA_BYTE,
158                 ValueLayout.JAVA_CHAR,
159                 ValueLayout.JAVA_INT,
160                 ValueLayout.JAVA_LONG
161         );
162         assertEquals(struct.byteSize(), 1 + 1 + 2 + 4 + 8);
163         assertEquals(struct.byteAlignment(), ADDRESS.byteSize());
164     }
165 
166     @Test(dataProvider="basicLayouts")
167     public void testPaddingNoAlign(MemoryLayout layout) {
168         assertEquals(MemoryLayout.paddingLayout(layout.byteSize()).byteAlignment(), 1);
169     }
170 
171     @Test(dataProvider="basicLayouts")
172     public void testStructPaddingAndAlign(MemoryLayout layout) {
173         MemoryLayout struct = MemoryLayout.structLayout(
174                 layout, MemoryLayout.paddingLayout(16 - layout.byteSize()));
175         assertEquals(struct.byteAlignment(), layout.byteAlignment());
176     }
177 
178     @Test(dataProvider="basicLayouts")
179     public void testUnionPaddingAndAlign(MemoryLayout layout) {
180         MemoryLayout struct = MemoryLayout.unionLayout(
181                 layout, MemoryLayout.paddingLayout(16 - layout.byteSize()));
182         assertEquals(struct.byteAlignment(), layout.byteAlignment());
183     }
184 
185     @Test
186     public void testUnionSizeAndAlign() {
187         MemoryLayout struct = MemoryLayout.unionLayout(
188                 ValueLayout.JAVA_BYTE,
189                 ValueLayout.JAVA_CHAR,
190                 ValueLayout.JAVA_INT,
191                 ValueLayout.JAVA_LONG
192         );
193         assertEquals(struct.byteSize(), 8);
194         assertEquals(struct.byteAlignment(), ADDRESS.byteSize());
195     }
196 
197     @Test
198     public void testSequenceBadCount() {
199         assertThrows(IllegalArgumentException.class, // negative
200                 () -> MemoryLayout.sequenceLayout(-2, JAVA_SHORT));
201     }
202 
203     @Test
204     public void testSequenceNegativeElementCount() {
205         assertThrows(IllegalArgumentException.class, // negative
206                 () -> MemoryLayout.sequenceLayout(-1, JAVA_SHORT));
207     }
208 
209     @Test
210     public void testSequenceOverflow() {
211         assertThrows(IllegalArgumentException.class, // negative
212                 () -> MemoryLayout.sequenceLayout(Long.MAX_VALUE, JAVA_SHORT));
213         assertThrows(IllegalArgumentException.class, // flip back to positive
214                 () -> MemoryLayout.sequenceLayout(Long.MAX_VALUE/3, JAVA_LONG));
215         assertThrows(IllegalArgumentException.class, // flip back to positive
216                 () -> MemoryLayout.sequenceLayout(0, JAVA_LONG).withElementCount(Long.MAX_VALUE));
217     }
218 
219     @Test
220     public void testStructOverflow() {
221         assertThrows(IllegalArgumentException.class, // negative
222                 () -> MemoryLayout.structLayout(MemoryLayout.sequenceLayout(Long.MAX_VALUE, JAVA_BYTE),
223                                                 MemoryLayout.sequenceLayout(Long.MAX_VALUE, JAVA_BYTE)));
224         assertThrows(IllegalArgumentException.class, // flip back to positive
225                 () -> MemoryLayout.structLayout(MemoryLayout.sequenceLayout(Long.MAX_VALUE, JAVA_BYTE),
226                                                 MemoryLayout.sequenceLayout(Long.MAX_VALUE, JAVA_BYTE),
227                                                 MemoryLayout.sequenceLayout(Long.MAX_VALUE, JAVA_BYTE)));
228     }
229 
230     @Test
231     public void testPadding() {
232         var padding = MemoryLayout.paddingLayout(1);
233         assertEquals(padding.byteAlignment(), 1);
234     }
235 
236     @Test
237     public void testPaddingInStruct() {
238         var padding = MemoryLayout.paddingLayout(1);
239         var struct = MemoryLayout.structLayout(padding);
240         assertEquals(struct.byteAlignment(), 1);
241     }
242 
243     @Test
244     public void testPaddingIllegalByteSize() {
245         for (long byteSize : List.of(-1L, 0L)) {
246             try {
247                 MemoryLayout.paddingLayout(byteSize);
248                 fail("byte size cannot be " + byteSize);
249             } catch (IllegalArgumentException ignore) {
250                 // Happy path
251             }
252         }
253     }
254 
255     @Test
256     public void testStructToString() {
257         for (ByteOrder order : List.of(ByteOrder.LITTLE_ENDIAN, ByteOrder.BIG_ENDIAN)) {
258             String intRepresentation = (order == ByteOrder.LITTLE_ENDIAN ? "i" : "I");
259             StructLayout padding = MemoryLayout.structLayout(JAVA_INT.withOrder(order)).withName("struct");
260             assertEquals(padding.toString(), "[" + intRepresentation + "4](struct)");
261             var toStringUnaligned = padding.withByteAlignment(8).toString();
262             assertEquals(toStringUnaligned, "8%[" + intRepresentation + "4](struct)");
263         }
264     }
265 
266     @Test(dataProvider = "layoutKinds")
267     public void testPadding(LayoutKind kind) {
268         assertEquals(kind == LayoutKind.PADDING, kind.layout instanceof PaddingLayout);
269     }
270 
271     @Test(dataProvider="layoutsAndAlignments")
272     public void testAlignmentString(MemoryLayout layout, long byteAlign) {
273         long[] alignments = { 1, 2, 4, 8, 16 };
274         for (long a : alignments) {
275             if (layout.byteAlignment() == byteAlign) {
276                 assertFalse(layout.toString().contains("%"));
277                 if (a >= layout.byteAlignment()) {
278                     assertEquals(layout.withByteAlignment(a).toString().contains("%"), a != byteAlign);
279                 }
280             }
281         }
282     }
283 
284     @Test(dataProvider="layoutsAndAlignments")
285     public void testBadByteAlignment(MemoryLayout layout, long byteAlign) {
286         long[] alignments = { 1, 2, 4, 8, 16 };
287         for (long a : alignments) {
288             if (a < byteAlign && !(layout instanceof ValueLayout)) {
289                 assertThrows(IllegalArgumentException.class, () -> layout.withByteAlignment(a));
290             }
291         }
292     }
293 
294     @Test(dataProvider="layoutsAndAlignments", expectedExceptions = IllegalArgumentException.class)
295     public void testBadSequenceElementAlignmentTooBig(MemoryLayout layout, long byteAlign) {
296         layout = layout.withByteAlignment(layout.byteSize() * 2); // hyper-align
297         MemoryLayout.sequenceLayout(1, layout);
298     }
299 
300     @Test(dataProvider="layoutsAndAlignments")
301     public void testBadSequenceElementSizeNotMultipleOfAlignment(MemoryLayout layout, long byteAlign) {
302         boolean shouldFail = layout.byteSize() % layout.byteAlignment() != 0;
303         try {
304             MemoryLayout.sequenceLayout(1, layout);
305             assertFalse(shouldFail);
306         } catch (IllegalArgumentException ex) {
307             assertTrue(shouldFail);
308         }
309     }
310 
311     @Test(dataProvider="layoutsAndAlignments")
312     public void testBadSpliteratorElementSizeNotMultipleOfAlignment(MemoryLayout layout, long byteAlign) {
313         boolean shouldFail = layout.byteSize() % layout.byteAlignment() != 0;
314         try (Arena arena = Arena.ofConfined()) {
315             MemorySegment segment = arena.allocate(layout);
316             segment.spliterator(layout);
317             assertFalse(shouldFail);
318         } catch (IllegalArgumentException ex) {
319             assertTrue(shouldFail);
320         }
321     }
322 
323     @Test(dataProvider="layoutsAndAlignments")
324     public void testBadElementsElementSizeNotMultipleOfAlignment(MemoryLayout layout, long byteAlign) {
325         boolean shouldFail = layout.byteSize() % layout.byteAlignment() != 0;
326         try (Arena arena = Arena.ofConfined()) {
327             MemorySegment segment = arena.allocate(layout);
328             segment.elements(layout);
329             assertFalse(shouldFail);
330         } catch (IllegalArgumentException ex) {
331             assertTrue(shouldFail);
332         }
333     }
334 
335     @Test(dataProvider="layoutsAndAlignments", expectedExceptions = IllegalArgumentException.class)
336     public void testBadStruct(MemoryLayout layout, long byteAlign) {
337         layout = layout.withByteAlignment(layout.byteSize() * 2); // hyper-align
338         MemoryLayout.structLayout(layout, layout);
339     }
340 
341     @Test(expectedExceptions = IllegalArgumentException.class)
342     public void testSequenceElement() {
343         SequenceLayout layout = MemoryLayout.sequenceLayout(10, JAVA_INT);
344         // Step must be != 0
345         PathElement.sequenceElement(3, 0);
346     }
347 
348     @Test
349     public void testVarHandleCaching() {
350         assertSame(JAVA_INT.varHandle(), JAVA_INT.varHandle());
351         assertSame(JAVA_INT.withName("foo").varHandle(), JAVA_INT.varHandle());
352 
353         assertNotSame(JAVA_INT_UNALIGNED.varHandle(), JAVA_INT.varHandle());
354         assertNotSame(ADDRESS.withTargetLayout(JAVA_INT).varHandle(), ADDRESS.varHandle());
355     }
356 
357     @Test(expectedExceptions=IllegalArgumentException.class,
358         expectedExceptionsMessageRegExp=".*Negative offset.*")
359     public void testScaleNegativeOffset() {
360         JAVA_INT.scale(-1, 0);
361     }
362 
363     @Test(expectedExceptions=IllegalArgumentException.class,
364         expectedExceptionsMessageRegExp=".*Negative index.*")
365     public void testScaleNegativeIndex() {
366         JAVA_INT.scale(0, -1);
367     }
368 
369     @Test(expectedExceptions=ArithmeticException.class)
370     public void testScaleAddOverflow() {
371         JAVA_INT.scale(Long.MAX_VALUE, 1);
372     }
373 
374     @Test(expectedExceptions=ArithmeticException.class)
375     public void testScaleMultiplyOverflow() {
376         JAVA_INT.scale(0, Long.MAX_VALUE);
377     }
378 
379     @DataProvider(name = "badAlignments")
380     public Object[][] layoutsAndBadAlignments() {
381         LayoutKind[] layoutKinds = LayoutKind.values();
382         Object[][] values = new Object[layoutKinds.length * 2][2];
383         for (int i = 0; i < layoutKinds.length ; i++) {
384             values[i * 2] = new Object[] { layoutKinds[i].layout, 0 }; // smaller than 1
385             values[(i * 2) + 1] = new Object[] { layoutKinds[i].layout, 5 }; // not a power of 2
386         }
387         return values;
388     }
389 
390     @DataProvider(name = "layoutKinds")
391     public Object[][] layoutsKinds() {
392         return Stream.of(LayoutKind.values())
393                 .map(lk -> new Object[] { lk })
394                 .toArray(Object[][]::new);
395     }
396 
397     enum SizedLayoutFactory {
398         VALUE_LE(size -> valueLayoutForSize((int)size).withOrder(ByteOrder.LITTLE_ENDIAN)),
399         VALUE_BE(size -> valueLayoutForSize((int)size).withOrder(ByteOrder.BIG_ENDIAN)),
400         PADDING(MemoryLayout::paddingLayout),
401         SEQUENCE(size -> MemoryLayout.sequenceLayout(size, MemoryLayout.paddingLayout(1)));
402 
403         private final LongFunction<MemoryLayout> factory;
404 
405         SizedLayoutFactory(LongFunction<MemoryLayout> factory) {
406             this.factory = factory;
407         }
408 
409         MemoryLayout make(long size) {
410             return factory.apply(size);
411         }
412     }
413 
414     static ValueLayout valueLayoutForSize(int size) {
415         return switch (size) {
416             case 1 -> JAVA_BYTE;
417             case 2 -> JAVA_SHORT;
418             case 4 -> JAVA_INT;
419             case 8 -> JAVA_LONG;
420             default -> throw new UnsupportedOperationException();
421         };
422     }
423 
424     enum LayoutKind {
425         VALUE(ValueLayout.JAVA_BYTE),
426         PADDING(MemoryLayout.paddingLayout(1)),
427         SEQUENCE(MemoryLayout.sequenceLayout(1, MemoryLayout.paddingLayout(1))),
428         STRUCT(MemoryLayout.structLayout(MemoryLayout.paddingLayout(1), MemoryLayout.paddingLayout(1))),
429         UNION(MemoryLayout.unionLayout(MemoryLayout.paddingLayout(1), MemoryLayout.paddingLayout(1)));
430 
431         final MemoryLayout layout;
432 
433         LayoutKind(MemoryLayout layout) {
434             this.layout = layout;
435         }
436     }
437 
438     @DataProvider(name = "basicLayouts")
439     public Object[][] basicLayouts() {
440         return Stream.of(basicLayouts)
441                 .map(l -> new Object[] { l })
442                 .toArray(Object[][]::new);
443     }
444 
445     @DataProvider(name = "basicLayoutsAndAddress")
446     public Object[][] basicLayoutsAndAddress() {
447         return Stream.concat(Stream.of(basicLayouts), Stream.of(ADDRESS))
448                 .map(l -> new Object[] { l })
449                 .toArray(Object[][]::new);
450     }
451 
452     @DataProvider(name = "basicLayoutsAndAddressAndGroups")
453     public Object[][] basicLayoutsAndAddressAndGroups() {
454         return Stream.concat(Stream.concat(Stream.of(basicLayouts), Stream.of(ADDRESS)), groupLayoutStream())
455                 .map(l -> new Object[] { l })
456                 .toArray(Object[][]::new);
457     }
458 
459     @DataProvider(name = "layoutsAndAlignments")
460     public Object[][] layoutsAndAlignments() {
461         List<Object[]> layoutsAndAlignments = new ArrayList<>();
462         int i = 0;
463         //add basic layouts
464         for (MemoryLayout l : basicLayoutsNoLongDouble) {
465             layoutsAndAlignments.add(new Object[] { l, l.byteAlignment() });
466         }
467         //add basic layouts wrapped in a sequence with given size
468         for (MemoryLayout l : basicLayoutsNoLongDouble) {
469             layoutsAndAlignments.add(new Object[] { MemoryLayout.sequenceLayout(4, l), l.byteAlignment() });
470         }
471         //add basic layouts wrapped in a struct
472         for (MemoryLayout l1 : basicLayoutsNoLongDouble) {
473             for (MemoryLayout l2 : basicLayoutsNoLongDouble) {
474                 if (l1.byteSize() % l2.byteAlignment() != 0) continue; // second element is not aligned, skip
475                 long align = Math.max(l1.byteAlignment(), l2.byteAlignment());
476                 layoutsAndAlignments.add(new Object[]{MemoryLayout.structLayout(l1, l2), align});
477             }
478         }
479         //add basic layouts wrapped in a union
480         for (MemoryLayout l1 : basicLayoutsNoLongDouble) {
481             for (MemoryLayout l2 : basicLayoutsNoLongDouble) {
482                 long align = Math.max(l1.byteAlignment(), l2.byteAlignment());
483                 layoutsAndAlignments.add(new Object[]{MemoryLayout.unionLayout(l1, l2), align});
484             }
485         }
486         return layoutsAndAlignments.toArray(Object[][]::new);
487     }
488 
489     @DataProvider(name = "groupLayouts")
490     public Object[][] groupLayouts() {
491         return groupLayoutStream()
492                 .map(l -> new Object[] { l })
493                 .toArray(Object[][]::new);
494     }
495 
496     @DataProvider(name = "validCarriers")
497     public Object[][] validCarriers() {
498         return Stream.of(
499                         boolean.class,
500                         byte.class,
501                         char.class,
502                         short.class,
503                         int.class,
504                         long.class,
505                         float.class,
506                         double.class,
507                         MemorySegment.class
508                 )
509                 .map(l -> new Object[]{l})
510                 .toArray(Object[][]::new);
511     }
512 
513     static Stream<MemoryLayout> groupLayoutStream() {
514         return Stream.of(
515                 MemoryLayout.sequenceLayout(10, JAVA_INT),
516                 MemoryLayout.structLayout(JAVA_INT, MemoryLayout.paddingLayout(4), JAVA_LONG),
517                 MemoryLayout.unionLayout(JAVA_LONG, JAVA_DOUBLE)
518         );
519     }
520 
521     static ValueLayout[] basicLayouts = {
522             ValueLayout.JAVA_BYTE,
523             ValueLayout.JAVA_CHAR,
524             ValueLayout.JAVA_SHORT,
525             ValueLayout.JAVA_INT,
526             ValueLayout.JAVA_FLOAT,
527             ValueLayout.JAVA_LONG,
528             ValueLayout.JAVA_DOUBLE,
529     };
530 
531     static MemoryLayout[] basicLayoutsNoLongDouble = Stream.of(basicLayouts)
532             .filter(l -> l.carrier() != long.class && l.carrier() != double.class)
533             .toArray(MemoryLayout[]::new);
534 }