/* * Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. * */ /* * @test * @enablePreview * @run testng TestLayoutPaths */ import java.lang.foreign.*; import java.lang.foreign.MemoryLayout.PathElement; import org.testng.SkipException; import org.testng.annotations.*; import java.lang.invoke.MethodHandle; import java.util.ArrayList; import java.util.List; import java.util.function.IntFunction; import static java.lang.foreign.MemoryLayout.PathElement.groupElement; import static java.lang.foreign.MemoryLayout.PathElement.sequenceElement; import static java.lang.foreign.ValueLayout.JAVA_INT; import static org.testng.Assert.*; public class TestLayoutPaths { @Test(expectedExceptions = IllegalArgumentException.class) public void testBadBitSelectFromSeq() { SequenceLayout seq = MemoryLayout.sequenceLayout(5, JAVA_INT); seq.bitOffset(groupElement("foo")); } @Test(expectedExceptions = IllegalArgumentException.class) public void testBadByteSelectFromSeq() { SequenceLayout seq = MemoryLayout.sequenceLayout(5, JAVA_INT); seq.byteOffset(groupElement("foo")); } @Test(expectedExceptions = IllegalArgumentException.class) public void testBadBitSelectFromStruct() { GroupLayout g = MemoryLayout.structLayout(JAVA_INT); g.bitOffset(sequenceElement()); } @Test(expectedExceptions = IllegalArgumentException.class) public void testBadByteSelectFromStruct() { GroupLayout g = MemoryLayout.structLayout(JAVA_INT); g.byteOffset(sequenceElement()); } @Test(expectedExceptions = IllegalArgumentException.class) public void testBadBitSelectFromValue() { SequenceLayout seq = MemoryLayout.sequenceLayout(5, JAVA_INT); seq.bitOffset(sequenceElement(), sequenceElement()); } @Test(expectedExceptions = IllegalArgumentException.class) public void testBadByteSelectFromValue() { SequenceLayout seq = MemoryLayout.sequenceLayout(5, JAVA_INT); seq.byteOffset(sequenceElement(), sequenceElement()); } @Test(expectedExceptions = IllegalArgumentException.class) public void testUnknownBitStructField() { GroupLayout g = MemoryLayout.structLayout(JAVA_INT); g.bitOffset(groupElement("foo")); } @Test(expectedExceptions = IllegalArgumentException.class) public void testUnknownByteStructField() { GroupLayout g = MemoryLayout.structLayout(JAVA_INT); g.byteOffset(groupElement("foo")); } @Test(expectedExceptions = IllegalArgumentException.class) public void testTooBigGroupElementIndex() { GroupLayout g = MemoryLayout.structLayout(JAVA_INT); g.byteOffset(groupElement(1)); } @Test(expectedExceptions = IllegalArgumentException.class) public void testNegativeGroupElementIndex() { GroupLayout g = MemoryLayout.structLayout(JAVA_INT); g.byteOffset(groupElement(-1)); } @Test(expectedExceptions = IllegalArgumentException.class) public void testBitOutOfBoundsSeqIndex() { SequenceLayout seq = MemoryLayout.sequenceLayout(5, JAVA_INT); seq.bitOffset(sequenceElement(6)); } @Test(expectedExceptions = IllegalArgumentException.class) public void testByteOutOfBoundsSeqIndex() { SequenceLayout seq = MemoryLayout.sequenceLayout(5, JAVA_INT); seq.byteOffset(sequenceElement(6)); } @Test(expectedExceptions = IllegalArgumentException.class) public void testNegativeSeqIndex() { sequenceElement(-2); } @Test(expectedExceptions = IllegalArgumentException.class) public void testBitNegativeSeqIndex() { SequenceLayout seq = MemoryLayout.sequenceLayout(5, JAVA_INT); seq.bitOffset(sequenceElement(-2)); } @Test(expectedExceptions = IllegalArgumentException.class) public void testByteNegativeSeqIndex() { SequenceLayout seq = MemoryLayout.sequenceLayout(5, JAVA_INT); seq.byteOffset(sequenceElement(-2)); } @Test(expectedExceptions = IllegalArgumentException.class) public void testOutOfBoundsSeqRange() { SequenceLayout seq = MemoryLayout.sequenceLayout(5, JAVA_INT); seq.bitOffset(sequenceElement(6, 2)); } @Test(expectedExceptions = IllegalArgumentException.class) public void testNegativeSeqRange() { sequenceElement(-2, 2); } @Test(expectedExceptions = IllegalArgumentException.class) public void testBitNegativeSeqRange() { SequenceLayout seq = MemoryLayout.sequenceLayout(5, JAVA_INT); seq.bitOffset(sequenceElement(-2, 2)); } @Test(expectedExceptions = IllegalArgumentException.class) public void testByteNegativeSeqRange() { SequenceLayout seq = MemoryLayout.sequenceLayout(5, JAVA_INT); seq.byteOffset(sequenceElement(-2, 2)); } @Test(expectedExceptions = IllegalArgumentException.class) public void testIncompleteAccess() { SequenceLayout seq = MemoryLayout.sequenceLayout(5, MemoryLayout.structLayout(JAVA_INT)); seq.varHandle(sequenceElement()); } @Test(expectedExceptions = IllegalArgumentException.class) public void testBitOffsetHandleBadRange() { SequenceLayout seq = MemoryLayout.sequenceLayout(5, MemoryLayout.structLayout(JAVA_INT)); seq.bitOffsetHandle(sequenceElement(0, 1)); // ranges not accepted } @Test(expectedExceptions = IllegalArgumentException.class) public void testByteOffsetHandleBadRange() { SequenceLayout seq = MemoryLayout.sequenceLayout(5, MemoryLayout.structLayout(JAVA_INT)); seq.byteOffsetHandle(sequenceElement(0, 1)); // ranges not accepted } @Test public void testBadSequencePathInOffset() { SequenceLayout seq = MemoryLayout.sequenceLayout(10, JAVA_INT); // bad path elements for (PathElement e : List.of( sequenceElement(), sequenceElement(0, 2) )) { try { seq.bitOffset(e); fail(); } catch (IllegalArgumentException ex) { assertTrue(true); } try { seq.byteOffset(e); fail(); } catch (IllegalArgumentException ex) { assertTrue(true); } } } @Test public void testBadSequencePathInSelect() { SequenceLayout seq = MemoryLayout.sequenceLayout(10, JAVA_INT); for (PathElement e : List.of( sequenceElement(0), sequenceElement(0, 2) )) { try { seq.select(e); fail(); } catch (IllegalArgumentException ex) { assertTrue(true); } } } @Test(dataProvider = "groupSelectors") public void testStructPaths(IntFunction groupSelector) { long[] offsets = { 0, 8, 24, 56 }; GroupLayout g = MemoryLayout.structLayout( ValueLayout.JAVA_BYTE.withName("0"), ValueLayout.JAVA_CHAR.withBitAlignment(8).withName("1"), ValueLayout.JAVA_FLOAT.withBitAlignment(8).withName("2"), ValueLayout.JAVA_LONG.withBitAlignment(8).withName("3") ); // test select for (int i = 0 ; i < 4 ; i++) { MemoryLayout selected = g.select(groupSelector.apply(i)); assertTrue(selected == g.memberLayouts().get(i)); } // test offset for (int i = 0 ; i < 4 ; i++) { long bitOffset = g.bitOffset(groupSelector.apply(i)); assertEquals(offsets[i], bitOffset); long byteOffset = g.byteOffset(groupSelector.apply(i)); assertEquals((offsets[i]) >>> 3, byteOffset); } } @Test(dataProvider = "groupSelectors") public void testUnionPaths(IntFunction groupSelector) { long[] offsets = { 0, 0, 0, 0 }; GroupLayout g = MemoryLayout.unionLayout( ValueLayout.JAVA_BYTE.withName("0"), ValueLayout.JAVA_CHAR.withName("1"), ValueLayout.JAVA_FLOAT.withName("2"), ValueLayout.JAVA_LONG.withName("3") ); // test select for (int i = 0 ; i < 4 ; i++) { MemoryLayout selected = g.select(groupSelector.apply(i)); assertTrue(selected == g.memberLayouts().get(i)); } // test offset for (int i = 0 ; i < 4 ; i++) { long bitOffset = g.bitOffset(groupSelector.apply(i)); assertEquals(offsets[i], bitOffset); long byteOffset = g.byteOffset(groupSelector.apply(i)); assertEquals((offsets[i]) >>> 3, byteOffset); } } @DataProvider public static Object[][] groupSelectors() { return new Object[][] { { (IntFunction) PathElement::groupElement }, // by index { (IntFunction) i -> PathElement.groupElement(String.valueOf(i)) } // by name }; } @Test public void testSequencePaths() { long[] offsets = { 0, 8, 16, 24 }; SequenceLayout g = MemoryLayout.sequenceLayout(4, ValueLayout.JAVA_BYTE); // test select MemoryLayout selected = g.select(sequenceElement()); assertTrue(selected == ValueLayout.JAVA_BYTE); // test offset for (int i = 0 ; i < 4 ; i++) { long bitOffset = g.bitOffset(sequenceElement(i)); assertEquals(offsets[i], bitOffset); long byteOffset = g.byteOffset(sequenceElement(i)); assertEquals((offsets[i]) >>> 3, byteOffset); } } @Test(dataProvider = "testLayouts") public void testOffsetHandle(MemoryLayout layout, PathElement[] pathElements, long[] indexes, long expectedBitOffset) throws Throwable { MethodHandle bitOffsetHandle = layout.bitOffsetHandle(pathElements); bitOffsetHandle = bitOffsetHandle.asSpreader(long[].class, indexes.length); long actualBitOffset = (long) bitOffsetHandle.invokeExact(indexes); assertEquals(actualBitOffset, expectedBitOffset); if (expectedBitOffset % 8 == 0) { MethodHandle byteOffsetHandle = layout.byteOffsetHandle(pathElements); byteOffsetHandle = byteOffsetHandle.asSpreader(long[].class, indexes.length); long actualByteOffset = (long) byteOffsetHandle.invokeExact(indexes); assertEquals(actualByteOffset, expectedBitOffset / 8); } } @DataProvider public static Object[][] testLayouts() { List testCases = new ArrayList<>(); testCases.add(new Object[] { MemoryLayout.sequenceLayout(10, JAVA_INT), new PathElement[] { sequenceElement() }, new long[] { 4 }, JAVA_INT.bitSize() * 4 }); testCases.add(new Object[] { MemoryLayout.sequenceLayout(10, MemoryLayout.structLayout(JAVA_INT, JAVA_INT.withName("y"))), new PathElement[] { sequenceElement(), groupElement("y") }, new long[] { 4 }, (JAVA_INT.bitSize() * 2) * 4 + JAVA_INT.bitSize() }); testCases.add(new Object[] { MemoryLayout.sequenceLayout(10, MemoryLayout.structLayout(MemoryLayout.paddingLayout(32), JAVA_INT.withName("y"))), new PathElement[] { sequenceElement(), groupElement("y") }, new long[] { 4 }, (JAVA_INT.bitSize() + 32) * 4 + 32 }); testCases.add(new Object[] { MemoryLayout.sequenceLayout(10, JAVA_INT), new PathElement[] { sequenceElement() }, new long[] { 4 }, JAVA_INT.bitSize() * 4 }); testCases.add(new Object[] { MemoryLayout.structLayout( MemoryLayout.sequenceLayout(10, JAVA_INT).withName("data") ), new PathElement[] { groupElement("data"), sequenceElement() }, new long[] { 4 }, JAVA_INT.bitSize() * 4 }); MemoryLayout complexLayout = MemoryLayout.structLayout( MemoryLayout.sequenceLayout(10, MemoryLayout.sequenceLayout(10, MemoryLayout.structLayout( JAVA_INT.withName("x"), JAVA_INT.withName("y") ) ) ).withName("data") ); testCases.add(new Object[] { complexLayout, new PathElement[] { groupElement("data"), sequenceElement(), sequenceElement(), groupElement("x") }, new long[] { 0, 1 }, (JAVA_INT.bitSize() * 2) }); testCases.add(new Object[] { complexLayout, new PathElement[] { groupElement("data"), sequenceElement(), sequenceElement(), groupElement("x") }, new long[] { 1, 0 }, (JAVA_INT.bitSize() * 2) * 10 }); testCases.add(new Object[] { complexLayout, new PathElement[] { groupElement("data"), sequenceElement(), sequenceElement(), groupElement("y") }, new long[] { 0, 1 }, (JAVA_INT.bitSize() * 2) + JAVA_INT.bitSize() }); testCases.add(new Object[] { complexLayout, new PathElement[] { groupElement("data"), sequenceElement(), sequenceElement(), groupElement("y") }, new long[] { 1, 0 }, (JAVA_INT.bitSize() * 2) * 10 + JAVA_INT.bitSize() }); return testCases.toArray(Object[][]::new); } @Test(dataProvider = "testLayouts") public void testSliceHandle(MemoryLayout layout, PathElement[] pathElements, long[] indexes, long expectedBitOffset) throws Throwable { if (expectedBitOffset % 8 != 0) throw new SkipException("Offset not a multiple of 8"); MemoryLayout selected = layout.select(pathElements); MethodHandle sliceHandle = layout.sliceHandle(pathElements); sliceHandle = sliceHandle.asSpreader(long[].class, indexes.length); try (Arena arena = Arena.ofConfined()) { MemorySegment segment = arena.allocate(layout); MemorySegment slice = (MemorySegment) sliceHandle.invokeExact(segment, indexes); assertEquals(slice.address() - segment.address(), expectedBitOffset / 8); assertEquals(slice.byteSize(), selected.byteSize()); } } }