diff --git a/src/java.base/share/classes/java/lang/foreign/MemorySegment.java b/src/java.base/share/classes/java/lang/foreign/MemorySegment.java index a58595cb541..95195ef81bf 100644 --- a/src/java.base/share/classes/java/lang/foreign/MemorySegment.java +++ b/src/java.base/share/classes/java/lang/foreign/MemorySegment.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 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 @@ -44,7 +44,6 @@ import java.util.stream.Stream; import jdk.internal.foreign.AbstractMemorySegmentImpl; import jdk.internal.foreign.MemorySessionImpl; import jdk.internal.foreign.SegmentFactories; -import jdk.internal.foreign.Utils; import jdk.internal.javac.Restricted; import jdk.internal.reflect.CallerSensitive; import jdk.internal.vm.annotation.ForceInline; @@ -95,6 +94,11 @@ import jdk.internal.vm.annotation.ForceInline; * address of the region of memory which backs the segment. * *
+ * Every memory segment has a {@linkplain #maxByteAlignment() maximum byte alignment}, + * expressed as a {@code long} value. The maximum alignment is always a power of two, + * derived from the segment address, and the segment type, as explained in more detail + * below. + *
* Every memory segment has a {@linkplain #byteSize() size}. The size of a heap segment * is derived from the Java array from which it is obtained. This size is predictable * across Java runtimes. The size of a native segment is either passed explicitly @@ -394,6 +398,14 @@ import jdk.internal.vm.annotation.ForceInline; * byteSegment.get(ValueLayout.JAVA_INT_UNALIGNED, 0); // ok: ValueLayout.JAVA_INT_UNALIGNED.byteAlignment() == ValueLayout.JAVA_BYTE.byteAlignment() * } * + * Clients can use the {@linkplain MemorySegment#maxByteAlignment()} method to check if + * a memory segment supports the alignment constraint of a memory layout, as follows: + * {@snippet lang=java: + * MemoryLayout layout = ... + * MemorySegment segment = ... + * boolean isAligned = segment.maxByteAlignment() >= layout.byteAlignment(); + * } + * *
+ * The returned alignment is always a power of two and is derived from + * the segment {@linkplain #address() address()} and, if it is a heap segment, + * the type of the {@linkplain #heapBase() backing heap storage}. + *
+ * This method can be used to ensure that a segment is sufficiently aligned + * with a layout: + * {@snippet lang=java: + * MemoryLayout layout = ... + * MemorySegment segment = ... + * if (segment.maxByteAlignment() < layout.byteAlignment()) { + * // Take action (e.g. throw an Exception) + * } + * } + */ + long maxByteAlignment(); + /** * Returns a slice of this memory segment, at the given offset. The returned * segment's address is the address of this segment plus the given offset; @@ -1424,6 +1456,9 @@ public sealed interface MemorySegment permits AbstractMemorySegmentImpl { /** * A zero-length native segment modelling the {@code NULL} address. Equivalent to * {@code MemorySegment.ofAddress(0L)}. + *
+ * The {@linkplain MemorySegment#maxByteAlignment() maximum byte alignment} for + * the {@code NULL} segment is of 262. */ MemorySegment NULL = MemorySegment.ofAddress(0L); diff --git a/src/java.base/share/classes/jdk/internal/foreign/HeapMemorySegmentImpl.java b/src/java.base/share/classes/jdk/internal/foreign/HeapMemorySegmentImpl.java index 0b95c43b440..2512b9e54a4 100644 --- a/src/java.base/share/classes/jdk/internal/foreign/HeapMemorySegmentImpl.java +++ b/src/java.base/share/classes/jdk/internal/foreign/HeapMemorySegmentImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 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 @@ -77,6 +77,13 @@ abstract sealed class HeapMemorySegmentImpl extends AbstractMemorySegmentImpl { return offset; } + @Override + public final long maxByteAlignment() { + return address() == 0 + ? maxAlignMask() + : Math.min(maxAlignMask(), Long.lowestOneBit(address())); + } + @Override abstract HeapMemorySegmentImpl dup(long offset, long size, boolean readOnly, MemorySessionImpl scope); diff --git a/src/java.base/share/classes/jdk/internal/foreign/NativeMemorySegmentImpl.java b/src/java.base/share/classes/jdk/internal/foreign/NativeMemorySegmentImpl.java index 5427c9f810f..c2043cf4267 100644 --- a/src/java.base/share/classes/jdk/internal/foreign/NativeMemorySegmentImpl.java +++ b/src/java.base/share/classes/jdk/internal/foreign/NativeMemorySegmentImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 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 @@ -26,7 +26,6 @@ package jdk.internal.foreign; -import java.lang.foreign.MemorySegment; import java.nio.ByteBuffer; import java.util.Optional; @@ -61,6 +60,12 @@ sealed class NativeMemorySegmentImpl extends AbstractMemorySegmentImpl permits M return Optional.empty(); } + public final long maxByteAlignment() { + return address() == 0 + ? 1L << 62 + : Long.lowestOneBit(address()); + } + @ForceInline @Override NativeMemorySegmentImpl dup(long offset, long size, boolean readOnly, MemorySessionImpl scope) { diff --git a/test/jdk/java/foreign/TestMemoryAlignment.java b/test/jdk/java/foreign/TestMemoryAlignment.java index 4d59ba9e4d7..44d28a07b05 100644 --- a/test/jdk/java/foreign/TestMemoryAlignment.java +++ b/test/jdk/java/foreign/TestMemoryAlignment.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 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 @@ -26,11 +26,25 @@ * @run testng TestMemoryAlignment */ +import java.io.File; +import java.io.IOException; import java.lang.foreign.*; import java.lang.foreign.MemoryLayout.PathElement; import java.lang.invoke.VarHandle; +import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.nio.CharBuffer; +import java.nio.DoubleBuffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.nio.LongBuffer; +import java.nio.ShortBuffer; +import java.nio.channels.FileChannel; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; import java.util.stream.LongStream; +import java.util.stream.Stream; import org.testng.annotations.*; import static org.testng.Assert.*; @@ -124,10 +138,76 @@ public class TestMemoryAlignment { } } + @Test(dataProvider = "alignments") + public void testActualByteAlignment(long align) { + if (align > (1L << 10)) { + return; + } + try (Arena arena = Arena.ofConfined()) { + var segment = arena.allocate(4, align); + assertTrue(segment.maxByteAlignment() >= align); + // Power of two? + assertEquals(Long.bitCount(segment.maxByteAlignment()), 1); + assertEquals(segment.asSlice(1).maxByteAlignment(), 1); + } + } + + public void testActualByteAlignmentMappedSegment() throws IOException { + File tmp = File.createTempFile("tmp", "txt"); + try (FileChannel channel = FileChannel.open(tmp.toPath(), StandardOpenOption.READ, StandardOpenOption.WRITE); + Arena arena = Arena.ofConfined()) { + var segment =channel.map(FileChannel.MapMode.READ_WRITE, 0L, 32L, arena); + // We do not know anything about mapping alignment other than it should + // be positive. + assertTrue(segment.maxByteAlignment() >= Byte.BYTES); + // Power of two? + assertEquals(Long.bitCount(segment.maxByteAlignment()), 1); + assertEquals(segment.asSlice(1).maxByteAlignment(), 1); + } finally { + tmp.delete(); + } + } + + @Test() + public void testActualByteAlignmentNull() { + long alignment = MemorySegment.NULL.maxByteAlignment(); + assertEquals(1L << 62, alignment); + } + + @Test(dataProvider = "heapSegments") + public void testActualByteAlignmentHeap(MemorySegment segment, int bytes) { + assertEquals(segment.maxByteAlignment(), bytes); + // A slice at offset 1 should always have an alignment of 1 + var segmentSlice = segment.asSlice(1); + assertEquals(segmentSlice.maxByteAlignment(), 1); + } + @DataProvider(name = "alignments") public Object[][] createAlignments() { return LongStream.range(1, 20) .mapToObj(v -> new Object[] { 1L << v }) .toArray(Object[][]::new); } + + @DataProvider(name = "heapSegments") + public Object[][] heapSegments() { + return Stream.of( + new Object[]{MemorySegment.ofArray(new byte[]{1}), Byte.BYTES}, + new Object[]{MemorySegment.ofArray(new short[]{1}), Short.BYTES}, + new Object[]{MemorySegment.ofArray(new char[]{1}), Character.BYTES}, + new Object[]{MemorySegment.ofArray(new int[]{1}), Integer.BYTES}, + new Object[]{MemorySegment.ofArray(new long[]{1}), Long.BYTES}, + new Object[]{MemorySegment.ofArray(new float[]{1}), Float.BYTES}, + new Object[]{MemorySegment.ofArray(new double[]{1}), Double.BYTES}, + new Object[]{MemorySegment.ofBuffer(ByteBuffer.allocate(8)), Byte.BYTES}, + new Object[]{MemorySegment.ofBuffer(CharBuffer.allocate(8)), Character.BYTES}, + new Object[]{MemorySegment.ofBuffer(ShortBuffer.allocate(8)), Short.BYTES}, + new Object[]{MemorySegment.ofBuffer(IntBuffer.allocate(8)), Integer.BYTES}, + new Object[]{MemorySegment.ofBuffer(LongBuffer.allocate(8)), Long.BYTES}, + new Object[]{MemorySegment.ofBuffer(FloatBuffer.allocate(8)), Float.BYTES}, + new Object[]{MemorySegment.ofBuffer(DoubleBuffer.allocate(8)), Double.BYTES} + ) + .toArray(Object[][]::new); + } + }