| /* | |
| * Copyright (c) 2021, 2024, 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 | |
| * @run testng/othervm --enable-native-access=ALL-UNNAMED TestMemoryAccessInstance | |
| * @run testng/othervm -Djava.lang.invoke.VarHandle.VAR_HANDLE_SEGMENT_FORCE_EXACT=true --enable-native-access=ALL-UNNAMED TestMemoryAccessInstance | |
| */ | |
| import java.lang.foreign.MemorySegment; | |
| import java.lang.foreign.Arena; | |
| import java.lang.foreign.ValueLayout; | |
| import java.nio.ByteBuffer; | |
| import java.nio.ByteOrder; | |
| import org.testng.annotations.*; | |
| import org.testng.SkipException; | |
| import static org.testng.Assert.*; | |
| public class TestMemoryAccessInstance { | |
| static class Accessor<X, L extends ValueLayout> { | |
| interface SegmentGetter<X, L> { | |
| X get(MemorySegment segment, L layout, long offset); | |
| } | |
| interface SegmentSetter<X, L> { | |
| void set(MemorySegment segment, L layout, long offset, X o); | |
| } | |
| interface BufferGetter<X> { | |
| X get(ByteBuffer segment, int offset); | |
| } | |
| interface BufferSetter<X> { | |
| void set(ByteBuffer buffer, int offset, X o); | |
| } | |
| final X value; | |
| final L layout; | |
| final SegmentGetter<X, L> segmentGetter; | |
| final SegmentSetter<X, L> segmentSetter; | |
| final BufferGetter<X> bufferGetter; | |
| final BufferSetter<X> bufferSetter; | |
| Accessor(L layout, X value, | |
| SegmentGetter<X, L> segmentGetter, SegmentSetter<X, L> segmentSetter, | |
| BufferGetter<X> bufferGetter, BufferSetter<X> bufferSetter) { | |
| this.layout = layout; | |
| this.value = value; | |
| this.segmentGetter = segmentGetter; | |
| this.segmentSetter = segmentSetter; | |
| this.bufferGetter = bufferGetter; | |
| this.bufferSetter = bufferSetter; | |
| } | |
| void test() { | |
| try (Arena arena = Arena.ofConfined()) { | |
| MemorySegment segment = arena.allocate(128, 1); | |
| ByteBuffer buffer = segment.asByteBuffer(); | |
| segmentSetter.set(segment, layout, 8, value); | |
| assertEquals(bufferGetter.get(buffer, 8), value); | |
| bufferSetter.set(buffer, 8, value); | |
| assertEquals(value, segmentGetter.get(segment, layout, 8)); | |
| } | |
| } | |
| @SuppressWarnings("unchecked") | |
| void testHyperAligned() { | |
| try (Arena arena = Arena.ofConfined()) { | |
| MemorySegment segment = arena.allocate(64, 1); | |
| L alignedLayout = (L)layout.withByteAlignment(layout.byteSize() * 2); | |
| try { | |
| segmentSetter.set(segment, alignedLayout, 0, value); | |
| fail(); | |
| } catch (IllegalArgumentException exception) { | |
| assertTrue(exception.getMessage().contains("greater")); | |
| } | |
| try { | |
| segmentGetter.get(segment, alignedLayout, 0); | |
| fail(); | |
| } catch (IllegalArgumentException exception) { | |
| assertTrue(exception.getMessage().contains("greater")); | |
| } | |
| } | |
| } | |
| X get(MemorySegment segment, long offset) { | |
| return segmentGetter.get(segment, layout, offset); | |
| } | |
| void set(MemorySegment segment, long offset, X value) { | |
| segmentSetter.set(segment, layout, offset, value); | |
| } | |
| static <L extends ValueLayout, X> Accessor<X, L> of(L layout, X value, | |
| SegmentGetter<X, L> segmentGetter, SegmentSetter<X, L> segmentSetter, | |
| BufferGetter<X> bufferGetter, BufferSetter<X> bufferSetter) { | |
| return new Accessor<>(layout, value, segmentGetter, segmentSetter, bufferGetter, bufferSetter); | |
| } | |
| } | |
| @Test(dataProvider = "segmentAccessors") | |
| public void testSegmentAccess(String testName, Accessor<?, ?> accessor) { | |
| accessor.test(); | |
| } | |
| @Test(dataProvider = "segmentAccessors") | |
| public void testSegmentAccessHyper(String testName, Accessor<?, ?> accessor) { | |
| if (testName.contains("index")) { | |
| accessor.testHyperAligned(); | |
| } else { | |
| throw new SkipException("Skipping"); | |
| } | |
| } | |
| @Test(expectedExceptions = IllegalArgumentException.class, | |
| expectedExceptionsMessageRegExp = ".*Heap segment not allowed.*") | |
| public void badHeapSegmentSet() { | |
| long byteSize = ValueLayout.ADDRESS.byteSize(); | |
| Arena scope = Arena.ofAuto(); | |
| MemorySegment targetSegment = scope.allocate(byteSize, 1); | |
| MemorySegment segment = MemorySegment.ofArray(new byte[]{ 0, 1, 2 }); | |
| targetSegment.set(ValueLayout.ADDRESS, 0, segment); // should throw | |
| } | |
| @Test(expectedExceptions = IllegalArgumentException.class, | |
| expectedExceptionsMessageRegExp = ".*Heap segment not allowed.*") | |
| public void badHeapSegmentSetAtIndex() { | |
| long byteSize = ValueLayout.ADDRESS.byteSize(); | |
| Arena scope = Arena.ofAuto(); | |
| MemorySegment targetSegment = scope.allocate(byteSize, 1); | |
| MemorySegment segment = MemorySegment.ofArray(new byte[]{ 0, 1, 2 }); | |
| targetSegment.setAtIndex(ValueLayout.ADDRESS, 0, segment); // should throw | |
| } | |
| @Test(dataProvider = "segmentAccessors") | |
| public <X, L extends ValueLayout> void badAccessOverflowInIndexedAccess(String testName, Accessor<X, L> accessor) { | |
| MemorySegment segment = MemorySegment.ofArray(new byte[100]); | |
| if (testName.contains("/index") && accessor.layout.byteSize() > 1) { | |
| assertThrows(IndexOutOfBoundsException.class, () -> accessor.get(segment, Long.MAX_VALUE)); | |
| assertThrows(IndexOutOfBoundsException.class, () -> accessor.set(segment, Long.MAX_VALUE, accessor.value)); | |
| } | |
| } | |
| @Test(dataProvider = "segmentAccessors") | |
| public <X, L extends ValueLayout> void negativeOffset(String testName, Accessor<X, L> accessor) { | |
| MemorySegment segment = MemorySegment.ofArray(new byte[100]); | |
| assertThrows(IndexOutOfBoundsException.class, () -> accessor.get(segment, -ValueLayout.JAVA_LONG.byteSize())); | |
| assertThrows(IndexOutOfBoundsException.class, () -> accessor.set(segment, -ValueLayout.JAVA_LONG.byteSize(), accessor.value)); | |
| } | |
| static final ByteOrder NE = ByteOrder.nativeOrder(); | |
| @DataProvider(name = "segmentAccessors") | |
| static Object[][] segmentAccessors() { | |
| return new Object[][]{ | |
| {"byte", Accessor.of(ValueLayout.JAVA_BYTE, (byte) 42, | |
| MemorySegment::get, MemorySegment::set, | |
| ByteBuffer::get, ByteBuffer::put) | |
| }, | |
| {"boolean", Accessor.of(ValueLayout.JAVA_BOOLEAN, false, | |
| MemorySegment::get, MemorySegment::set, | |
| (bb, pos) -> bb.get(pos) != 0, (bb, pos, v) -> bb.put(pos, v ? (byte)1 : (byte)0)) | |
| }, | |
| {"char", Accessor.of(ValueLayout.JAVA_CHAR, (char) 42, | |
| MemorySegment::get, MemorySegment::set, | |
| (bb, pos) -> bb.order(NE).getChar(pos), (bb, pos, v) -> bb.order(NE).putChar(pos, v)) | |
| }, | |
| {"short", Accessor.of(ValueLayout.JAVA_SHORT, (short) 42, | |
| MemorySegment::get, MemorySegment::set, | |
| (bb, pos) -> bb.order(NE).getShort(pos), (bb, pos, v) -> bb.order(NE).putShort(pos, v)) | |
| }, | |
| {"int", Accessor.of(ValueLayout.JAVA_INT, 42, | |
| MemorySegment::get, MemorySegment::set, | |
| (bb, pos) -> bb.order(NE).getInt(pos), (bb, pos, v) -> bb.order(NE).putInt(pos, v)) | |
| }, | |
| {"float", Accessor.of(ValueLayout.JAVA_FLOAT, 42f, | |
| MemorySegment::get, MemorySegment::set, | |
| (bb, pos) -> bb.order(NE).getFloat(pos), (bb, pos, v) -> bb.order(NE).putFloat(pos, v)) | |
| }, | |
| {"long", Accessor.of(ValueLayout.JAVA_LONG, 42L, | |
| MemorySegment::get, MemorySegment::set, | |
| (bb, pos) -> bb.order(NE).getLong(pos), (bb, pos, v) -> bb.order(NE).putLong(pos, v)) | |
| }, | |
| {"double", Accessor.of(ValueLayout.JAVA_DOUBLE, 42d, | |
| MemorySegment::get, MemorySegment::set, | |
| (bb, pos) -> bb.order(NE).getDouble(pos), (bb, pos, v) -> bb.order(NE).putDouble(pos, v)) | |
| }, | |
| { "address", Accessor.of(ValueLayout.ADDRESS, MemorySegment.ofAddress(42), | |
| MemorySegment::get, MemorySegment::set, | |
| (bb, pos) -> { | |
| ByteBuffer nb = bb.order(NE); | |
| long addr = ValueLayout.ADDRESS.byteSize() == 8 ? | |
| nb.getLong(pos) : nb.getInt(pos); | |
| return MemorySegment.ofAddress(addr); | |
| }, | |
| (bb, pos, v) -> { | |
| ByteBuffer nb = bb.order(NE); | |
| if (ValueLayout.ADDRESS.byteSize() == 8) { | |
| nb.putLong(pos, v.address()); | |
| } else { | |
| nb.putInt(pos, (int)v.address()); | |
| } | |
| }) | |
| }, | |
| {"byte/index", Accessor.of(ValueLayout.JAVA_BYTE, (byte) 42, | |
| MemorySegment::getAtIndex, MemorySegment::setAtIndex, | |
| (bb, pos) -> bb.order(NE).get(pos), (bb, pos, v) -> bb.order(NE).put(pos, v)) | |
| }, | |
| {"boolean/index", Accessor.of(ValueLayout.JAVA_BOOLEAN, true, | |
| MemorySegment::getAtIndex, MemorySegment::setAtIndex, | |
| (bb, pos) -> bb.order(NE).get(pos) != 0, (bb, pos, v) -> bb.order(NE).put(pos, (byte) (v ? 1 : 0))) | |
| }, | |
| {"char/index", Accessor.of(ValueLayout.JAVA_CHAR, (char) 42, | |
| MemorySegment::getAtIndex, MemorySegment::setAtIndex, | |
| (bb, pos) -> bb.order(NE).getChar(pos * 2), (bb, pos, v) -> bb.order(NE).putChar(pos * 2, v)) | |
| }, | |
| {"short/index", Accessor.of(ValueLayout.JAVA_SHORT, (short) 42, | |
| MemorySegment::getAtIndex, MemorySegment::setAtIndex, | |
| (bb, pos) -> bb.order(NE).getShort(pos * 2), (bb, pos, v) -> bb.order(NE).putShort(pos * 2, v)) | |
| }, | |
| {"int/index", Accessor.of(ValueLayout.JAVA_INT, 42, | |
| MemorySegment::getAtIndex, MemorySegment::setAtIndex, | |
| (bb, pos) -> bb.order(NE).getInt(pos * 4), (bb, pos, v) -> bb.order(NE).putInt(pos * 4, v)) | |
| }, | |
| {"float/index", Accessor.of(ValueLayout.JAVA_FLOAT, 42f, | |
| MemorySegment::getAtIndex, MemorySegment::setAtIndex, | |
| (bb, pos) -> bb.order(NE).getFloat(pos * 4), (bb, pos, v) -> bb.order(NE).putFloat(pos * 4, v)) | |
| }, | |
| {"long/index", Accessor.of(ValueLayout.JAVA_LONG, 42L, | |
| MemorySegment::getAtIndex, MemorySegment::setAtIndex, | |
| (bb, pos) -> bb.order(NE).getLong(pos * 8), (bb, pos, v) -> bb.order(NE).putLong(pos * 8, v)) | |
| }, | |
| {"double/index", Accessor.of(ValueLayout.JAVA_DOUBLE, 42d, | |
| MemorySegment::getAtIndex, MemorySegment::setAtIndex, | |
| (bb, pos) -> bb.order(NE).getDouble(pos * 8), (bb, pos, v) -> bb.order(NE).putDouble(pos * 8, v)) | |
| }, | |
| { "address/index", Accessor.of(ValueLayout.ADDRESS, MemorySegment.ofAddress(42), | |
| MemorySegment::getAtIndex, MemorySegment::setAtIndex, | |
| (bb, pos) -> { | |
| ByteBuffer nb = bb.order(NE); | |
| long addr = ValueLayout.ADDRESS.byteSize() == 8 ? | |
| nb.getLong(pos * 8) : nb.getInt(pos * 4); | |
| return MemorySegment.ofAddress(addr); | |
| }, | |
| (bb, pos, v) -> { | |
| ByteBuffer nb = bb.order(NE); | |
| if (ValueLayout.ADDRESS.byteSize() == 8) { | |
| nb.putLong(pos * 8, v.address()); | |
| } else { | |
| nb.putInt(pos * 4, (int)v.address()); | |
| } | |
| }) | |
| }, | |
| }; | |
| } | |
| } |