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