diff --git a/src/java.base/share/classes/jdk/internal/foreign/SegmentBulkOperations.java b/src/java.base/share/classes/jdk/internal/foreign/SegmentBulkOperations.java index 11f1914a707..d928f8fc425 100644 --- a/src/java.base/share/classes/jdk/internal/foreign/SegmentBulkOperations.java +++ b/src/java.base/share/classes/jdk/internal/foreign/SegmentBulkOperations.java @@ -28,7 +28,9 @@ package jdk.internal.foreign; import jdk.internal.misc.ScopedMemoryAccess; import jdk.internal.util.Architecture; import jdk.internal.util.ArraysSupport; +import jdk.internal.util.ByteArrayLittleEndian; import jdk.internal.vm.annotation.ForceInline; +import jdk.internal.vm.annotation.Stable; import java.lang.foreign.MemorySegment; @@ -156,6 +158,94 @@ public final class SegmentBulkOperations { } } + private static final @Stable int[] POWERS_OF_31 = new int[]{ + 0x0000001f, 0x000003c1, 0x0000745f, 0x000e1781, + 0x01b4d89f, 0x34e63b41, 0x67e12cdf, 0x94446f01}; + + /** + * {@return a 32-bit hash value calculated from the content in the provided + * {@code segment} between the provided offsets} + *
+ * The method is implemented as a 32-bit polynomial hash function equivalent to:
+ * {@snippet lang=java :
+ * final long length = toOffset - fromOffset;
+ * segment.checkBounds(fromOffset, length);
+ * int result = 1;
+ * for (long i = fromOffset; i < toOffset; i++) {
+ * result = 31 * result + segment.get(JAVA_BYTE, i);
+ * }
+ * return result;
+ * }
+ * but is potentially more performant.
+ *
+ * @param segment from which a content hash should be computed
+ * @param fromOffset starting offset (inclusive) in the segment
+ * @param toOffset ending offset (non-inclusive) in the segment
+ * @throws WrongThreadException if this method is called from a thread {@code T},
+ * such that {@code srcSegment.isAccessibleBy(T) == false}
+ * @throws IllegalStateException if the {@linkplain MemorySegment#scope() scope}
+ * associated with {@code segment} is not
+ * {@linkplain MemorySegment.Scope#isAlive() alive}
+ * @throws IndexOutOfBoundsException if either {@code fromOffset} or {@code toOffset}
+ * are {@code > segment.byteSize}
+ * @throws IndexOutOfBoundsException if either {@code fromOffset} or {@code toOffset}
+ * are {@code < 0}
+ * @throws IndexOutOfBoundsException if {@code toOffset - fromOffset} is {@code < 0}
+ */
+ @ForceInline
+ public static int contentHash(AbstractMemorySegmentImpl segment, long fromOffset, long toOffset) {
+ final long length = toOffset - fromOffset;
+ segment.checkBounds(fromOffset, length);
+ if (length == 0) {
+ // The state has to be checked explicitly for zero-length segments
+ segment.scope.checkValidState();
+ return 1;
+ }
+ int result = 1;
+ final long longBytes = length & ((1L << 62) - 8);
+ final long limit = fromOffset + longBytes;
+ for (; fromOffset < limit; fromOffset += 8) {
+ long val = SCOPED_MEMORY_ACCESS.getLongUnaligned(segment.sessionImpl(), segment.unsafeGetBase(), segment.unsafeGetOffset() + fromOffset, !Architecture.isLittleEndian());
+ result = result * POWERS_OF_31[7]
+ + ((byte) (val >>> 56)) * POWERS_OF_31[6]
+ + ((byte) (val >>> 48)) * POWERS_OF_31[5]
+ + ((byte) (val >>> 40)) * POWERS_OF_31[4]
+ + ((byte) (val >>> 32)) * POWERS_OF_31[3]
+ + ((byte) (val >>> 24)) * POWERS_OF_31[2]
+ + ((byte) (val >>> 16)) * POWERS_OF_31[1]
+ + ((byte) (val >>> 8)) * POWERS_OF_31[0]
+ + ((byte) val);
+ }
+ int remaining = (int) (length - longBytes);
+ // 0...0X00
+ if (remaining >= 4) {
+ int val = SCOPED_MEMORY_ACCESS.getIntUnaligned(segment.sessionImpl(), segment.unsafeGetBase(), segment.unsafeGetOffset() + fromOffset, !Architecture.isLittleEndian());
+ result = result * POWERS_OF_31[3]
+ + ((byte) (val >>> 24)) * POWERS_OF_31[2]
+ + ((byte) (val >>> 16)) * POWERS_OF_31[1]
+ + ((byte) (val >>> 8)) * POWERS_OF_31[0]
+ + ((byte) val);
+ fromOffset += 4;
+ remaining -= 4;
+ }
+ // 0...00X0
+ if (remaining >= 2) {
+ short val = SCOPED_MEMORY_ACCESS.getShortUnaligned(segment.sessionImpl(), segment.unsafeGetBase(), segment.unsafeGetOffset() + fromOffset, !Architecture.isLittleEndian());
+ result = result * POWERS_OF_31[1]
+ + ((byte) (val >>> 8)) * POWERS_OF_31[0]
+ + ((byte) val);
+ fromOffset += 2;
+ remaining -= 2;
+ }
+ // 0...000X
+ if (remaining == 1) {
+ byte val = SCOPED_MEMORY_ACCESS.getByte(segment.sessionImpl(), segment.unsafeGetBase(), segment.unsafeGetOffset() + fromOffset);
+ result = result * POWERS_OF_31[0]
+ + val;
+ }
+ return result;
+ }
+
@ForceInline
public static long mismatch(AbstractMemorySegmentImpl src, long srcFromOffset, long srcToOffset,
AbstractMemorySegmentImpl dst, long dstFromOffset, long dstToOffset) {
diff --git a/test/jdk/java/foreign/TestSegmentBulkOperationsContentHash.java b/test/jdk/java/foreign/TestSegmentBulkOperationsContentHash.java
new file mode 100644
index 00000000000..96b73e7eee6
--- /dev/null
+++ b/test/jdk/java/foreign/TestSegmentBulkOperationsContentHash.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 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
+ * @summary Test SegmentBulkOperations::contentHash
+ * @modules java.base/jdk.internal.foreign
+ * @run junit TestSegmentBulkOperationsContentHash
+ */
+
+import jdk.internal.foreign.AbstractMemorySegmentImpl;
+import jdk.internal.foreign.SegmentBulkOperations;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.lang.foreign.Arena;
+import java.lang.foreign.MemorySegment;
+import java.lang.foreign.ValueLayout;
+import java.util.Arrays;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+final class TestSegmentBulkOperationsContentHash {
+
+ @ParameterizedTest
+ @MethodSource("sizes")
+ @Disabled
+ void testHashValues(int len) {
+ try (var arena = Arena.ofConfined()) {
+ for (int i = Byte.MIN_VALUE; i <= Byte.MAX_VALUE ; i++) {
+ var segment = arena.allocate(len);
+ segment.fill((byte) i);
+ int hash = hash(segment);
+ int expected = Arrays.hashCode(segment.toArray(ValueLayout.JAVA_BYTE));
+ assertEquals(expected, hash, Arrays.toString(segment.toArray(ValueLayout.JAVA_BYTE)));
+ }
+ }
+ }
+
+ @ParameterizedTest
+ @MethodSource("sizes")
+ void testOffsets(int len) {
+ if (len < 2) {
+ return;
+ }
+ try (var arena = Arena.ofConfined()) {
+ for (int i = Byte.MIN_VALUE; i <= Byte.MAX_VALUE ; i++) {
+ var segment = arena.allocate(len);
+ segment.fill((byte) i);
+ int hash = hash(segment, 1, segment.byteSize() - 1);
+ MemorySegment slice = segment.asSlice(1, segment.byteSize() - 2);
+ byte[] arr = slice.toArray(ValueLayout.JAVA_BYTE);
+ System.out.println(Arrays.toString(arr));
+ int expected = Arrays.hashCode(arr);
+ assertEquals(expected, hash, Arrays.toString(segment.toArray(ValueLayout.JAVA_BYTE)));
+ }
+ }
+ }
+
+ @ParameterizedTest
+ @MethodSource("sizes")
+ void testOutOfBounds(int len) {
+ try (var arena = Arena.ofConfined()) {
+ var segment = arena.allocate(len);
+ assertThrows(IndexOutOfBoundsException.class,
+ () -> hash(segment, 0, segment.byteSize() + 1));
+ assertThrows(IndexOutOfBoundsException.class,
+ () -> hash(segment, 0, -1));
+ assertThrows(IndexOutOfBoundsException.class,
+ () -> hash(segment, -1, 0));
+ if (len > 2) {
+ assertThrows(IndexOutOfBoundsException.class,
+ () -> hash(segment, 2, 1));
+ }
+ }
+ }
+
+ @ParameterizedTest
+ @MethodSource("sizes")
+ void testConfinement(int len) {
+ try (var arena = Arena.ofConfined()) {
+ var segment = arena.allocate(len);
+ AtomicReference