From 8740fbb4eeaf742e88999d4f243e29d53d17be2b Mon Sep 17 00:00:00 2001 From: Jatin Bhateja Date: Fri, 26 Jun 2026 04:02:20 +0000 Subject: [PATCH] 8386255: Float16Vector NaN canonicalization for hashCode computation Reviewed-by: psandoz, sherman --- .../jdk/incubator/vector/Float16Vector.java | 16 ++++++++++++++-- .../incubator/vector/X-Vector.java.template | 19 +++++++++++++++++++ .../vector/Float16Vector128Tests.java | 16 +++++++++++++--- .../vector/Float16Vector256Tests.java | 16 +++++++++++++--- .../vector/Float16Vector512Tests.java | 16 +++++++++++++--- .../vector/Float16Vector64Tests.java | 16 +++++++++++++--- .../vector/Float16VectorMaxTests.java | 16 +++++++++++++--- .../templates/Unit-Miscellaneous.template | 14 ++++++++++++++ .../vector/templates/Unit-header.template | 6 ++++-- 9 files changed, 116 insertions(+), 19 deletions(-) diff --git a/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/Float16Vector.java b/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/Float16Vector.java index cf7eae5dd6a..ce3a67357f9 100644 --- a/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/Float16Vector.java +++ b/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/Float16Vector.java @@ -2861,6 +2861,17 @@ public abstract sealed class Float16Vector extends AbstractVector return a; } + // Returns the lane values boxed as Float16 elements. + @ForceInline + final Float16[] toFloat16Array() { + short[] bits = vec(); + Float16[] a = new Float16[bits.length]; + for (int i = 0; i < bits.length; i++) { + a[i] = Float16.shortBitsToFloat16(bits[i]); + } + return a; + } + /** {@inheritDoc} */ @ForceInline @@ -3734,8 +3745,9 @@ public abstract sealed class Float16Vector extends AbstractVector @ForceInline public final int hashCode() { - // now that toArray is strongly typed, we can define this - return Objects.hash(species(), Arrays.hashCode(toArray())); + // Hash the lanes as Float16 values; Float16.hashCode canonicalizes NaN + // so that all NaN representations contribute the same hash code. + return Objects.hash(species(), Arrays.hashCode(toFloat16Array())); } // ================================================ diff --git a/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/X-Vector.java.template b/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/X-Vector.java.template index 7c6fb3bcfb2..f11c6283685 100644 --- a/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/X-Vector.java.template +++ b/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/X-Vector.java.template @@ -3705,6 +3705,19 @@ public abstract sealed class $abstractvectortype$ extends AbstractVector<$Boxtyp return a; } +#if[FP16] + // Returns the lane values boxed as Float16 elements. + @ForceInline + final Float16[] toFloat16Array() { + short[] bits = vec(); + Float16[] a = new Float16[bits.length]; + for (int i = 0; i < bits.length; i++) { + a[i] = Float16.shortBitsToFloat16(bits[i]); + } + return a; + } + +#end[FP16] #if[int] /** * {@inheritDoc} @@ -5749,8 +5762,14 @@ public abstract sealed class $abstractvectortype$ extends AbstractVector<$Boxtyp @ForceInline public final int hashCode() { +#if[FP16] + // Hash the lanes as Float16 values; Float16.hashCode canonicalizes NaN + // so that all NaN representations contribute the same hash code. + return Objects.hash(species(), Arrays.hashCode(toFloat16Array())); +#else[FP16] // now that toArray is strongly typed, we can define this return Objects.hash(species(), Arrays.hashCode(toArray())); +#end[FP16] } // ================================================ diff --git a/test/jdk/jdk/incubator/vector/Float16Vector128Tests.java b/test/jdk/jdk/incubator/vector/Float16Vector128Tests.java index ad971e9b9bf..a33e83d14ea 100644 --- a/test/jdk/jdk/incubator/vector/Float16Vector128Tests.java +++ b/test/jdk/jdk/incubator/vector/Float16Vector128Tests.java @@ -1561,14 +1561,16 @@ public class Float16Vector128Tests extends AbstractVectorTest { } static short cornerCaseValue(int i) { - return switch(i % 8) { + return switch(i % 10) { case 0 -> float16ToRawShortBits(Float16.MAX_VALUE); case 1 -> float16ToRawShortBits(Float16.MIN_VALUE); case 2 -> float16ToRawShortBits(Float16.NEGATIVE_INFINITY); case 3 -> float16ToRawShortBits(Float16.POSITIVE_INFINITY); case 4 -> float16ToRawShortBits(Float16.NaN); case 5 -> float16ToRawShortBits(shortBitsToFloat16((short)0x7FFA)); - case 6 -> float16ToShortBits(Float16.valueOf(0.0f)); + case 6 -> float16ToRawShortBits(shortBitsToFloat16((short)0x7c01)); // signaling NaN + case 7 -> float16ToRawShortBits(shortBitsToFloat16((short)0x7e00)); // quiet NaN + case 8 -> float16ToShortBits(Float16.valueOf(0.0f)); default -> float16ToShortBits(Float16.valueOf(-0.0f)); }; } @@ -5423,11 +5425,19 @@ public class Float16Vector128Tests extends AbstractVectorTest { int hash = av.hashCode(); short subarr[] = Arrays.copyOfRange(a, i, i + SPECIES.length()); - int expectedHash = Objects.hash(SPECIES, Arrays.hashCode(subarr)); + int expectedHash = Objects.hash(SPECIES, Arrays.hashCode(toFloat16Array(subarr))); Assert.assertTrue(hash == expectedHash, "at index " + i + ", hash should be = " + expectedHash + ", but is = " + hash); } } + static Float16[] toFloat16Array(short[] bits) { + Float16[] a = new Float16[bits.length]; + for (int j = 0; j < bits.length; j++) { + a[j] = shortBitsToFloat16(bits[j]); + } + return a; + } + static long ADDReduceLong(short[] a, int idx) { short res = 0; diff --git a/test/jdk/jdk/incubator/vector/Float16Vector256Tests.java b/test/jdk/jdk/incubator/vector/Float16Vector256Tests.java index a946e0d8585..99b167d4024 100644 --- a/test/jdk/jdk/incubator/vector/Float16Vector256Tests.java +++ b/test/jdk/jdk/incubator/vector/Float16Vector256Tests.java @@ -1561,14 +1561,16 @@ public class Float16Vector256Tests extends AbstractVectorTest { } static short cornerCaseValue(int i) { - return switch(i % 8) { + return switch(i % 10) { case 0 -> float16ToRawShortBits(Float16.MAX_VALUE); case 1 -> float16ToRawShortBits(Float16.MIN_VALUE); case 2 -> float16ToRawShortBits(Float16.NEGATIVE_INFINITY); case 3 -> float16ToRawShortBits(Float16.POSITIVE_INFINITY); case 4 -> float16ToRawShortBits(Float16.NaN); case 5 -> float16ToRawShortBits(shortBitsToFloat16((short)0x7FFA)); - case 6 -> float16ToShortBits(Float16.valueOf(0.0f)); + case 6 -> float16ToRawShortBits(shortBitsToFloat16((short)0x7c01)); // signaling NaN + case 7 -> float16ToRawShortBits(shortBitsToFloat16((short)0x7e00)); // quiet NaN + case 8 -> float16ToShortBits(Float16.valueOf(0.0f)); default -> float16ToShortBits(Float16.valueOf(-0.0f)); }; } @@ -5423,11 +5425,19 @@ public class Float16Vector256Tests extends AbstractVectorTest { int hash = av.hashCode(); short subarr[] = Arrays.copyOfRange(a, i, i + SPECIES.length()); - int expectedHash = Objects.hash(SPECIES, Arrays.hashCode(subarr)); + int expectedHash = Objects.hash(SPECIES, Arrays.hashCode(toFloat16Array(subarr))); Assert.assertTrue(hash == expectedHash, "at index " + i + ", hash should be = " + expectedHash + ", but is = " + hash); } } + static Float16[] toFloat16Array(short[] bits) { + Float16[] a = new Float16[bits.length]; + for (int j = 0; j < bits.length; j++) { + a[j] = shortBitsToFloat16(bits[j]); + } + return a; + } + static long ADDReduceLong(short[] a, int idx) { short res = 0; diff --git a/test/jdk/jdk/incubator/vector/Float16Vector512Tests.java b/test/jdk/jdk/incubator/vector/Float16Vector512Tests.java index 0e70b4c85ec..1c391497015 100644 --- a/test/jdk/jdk/incubator/vector/Float16Vector512Tests.java +++ b/test/jdk/jdk/incubator/vector/Float16Vector512Tests.java @@ -1561,14 +1561,16 @@ public class Float16Vector512Tests extends AbstractVectorTest { } static short cornerCaseValue(int i) { - return switch(i % 8) { + return switch(i % 10) { case 0 -> float16ToRawShortBits(Float16.MAX_VALUE); case 1 -> float16ToRawShortBits(Float16.MIN_VALUE); case 2 -> float16ToRawShortBits(Float16.NEGATIVE_INFINITY); case 3 -> float16ToRawShortBits(Float16.POSITIVE_INFINITY); case 4 -> float16ToRawShortBits(Float16.NaN); case 5 -> float16ToRawShortBits(shortBitsToFloat16((short)0x7FFA)); - case 6 -> float16ToShortBits(Float16.valueOf(0.0f)); + case 6 -> float16ToRawShortBits(shortBitsToFloat16((short)0x7c01)); // signaling NaN + case 7 -> float16ToRawShortBits(shortBitsToFloat16((short)0x7e00)); // quiet NaN + case 8 -> float16ToShortBits(Float16.valueOf(0.0f)); default -> float16ToShortBits(Float16.valueOf(-0.0f)); }; } @@ -5423,11 +5425,19 @@ public class Float16Vector512Tests extends AbstractVectorTest { int hash = av.hashCode(); short subarr[] = Arrays.copyOfRange(a, i, i + SPECIES.length()); - int expectedHash = Objects.hash(SPECIES, Arrays.hashCode(subarr)); + int expectedHash = Objects.hash(SPECIES, Arrays.hashCode(toFloat16Array(subarr))); Assert.assertTrue(hash == expectedHash, "at index " + i + ", hash should be = " + expectedHash + ", but is = " + hash); } } + static Float16[] toFloat16Array(short[] bits) { + Float16[] a = new Float16[bits.length]; + for (int j = 0; j < bits.length; j++) { + a[j] = shortBitsToFloat16(bits[j]); + } + return a; + } + static long ADDReduceLong(short[] a, int idx) { short res = 0; diff --git a/test/jdk/jdk/incubator/vector/Float16Vector64Tests.java b/test/jdk/jdk/incubator/vector/Float16Vector64Tests.java index 94017042b7b..6ef651860ad 100644 --- a/test/jdk/jdk/incubator/vector/Float16Vector64Tests.java +++ b/test/jdk/jdk/incubator/vector/Float16Vector64Tests.java @@ -1561,14 +1561,16 @@ public class Float16Vector64Tests extends AbstractVectorTest { } static short cornerCaseValue(int i) { - return switch(i % 8) { + return switch(i % 10) { case 0 -> float16ToRawShortBits(Float16.MAX_VALUE); case 1 -> float16ToRawShortBits(Float16.MIN_VALUE); case 2 -> float16ToRawShortBits(Float16.NEGATIVE_INFINITY); case 3 -> float16ToRawShortBits(Float16.POSITIVE_INFINITY); case 4 -> float16ToRawShortBits(Float16.NaN); case 5 -> float16ToRawShortBits(shortBitsToFloat16((short)0x7FFA)); - case 6 -> float16ToShortBits(Float16.valueOf(0.0f)); + case 6 -> float16ToRawShortBits(shortBitsToFloat16((short)0x7c01)); // signaling NaN + case 7 -> float16ToRawShortBits(shortBitsToFloat16((short)0x7e00)); // quiet NaN + case 8 -> float16ToShortBits(Float16.valueOf(0.0f)); default -> float16ToShortBits(Float16.valueOf(-0.0f)); }; } @@ -5423,11 +5425,19 @@ public class Float16Vector64Tests extends AbstractVectorTest { int hash = av.hashCode(); short subarr[] = Arrays.copyOfRange(a, i, i + SPECIES.length()); - int expectedHash = Objects.hash(SPECIES, Arrays.hashCode(subarr)); + int expectedHash = Objects.hash(SPECIES, Arrays.hashCode(toFloat16Array(subarr))); Assert.assertTrue(hash == expectedHash, "at index " + i + ", hash should be = " + expectedHash + ", but is = " + hash); } } + static Float16[] toFloat16Array(short[] bits) { + Float16[] a = new Float16[bits.length]; + for (int j = 0; j < bits.length; j++) { + a[j] = shortBitsToFloat16(bits[j]); + } + return a; + } + static long ADDReduceLong(short[] a, int idx) { short res = 0; diff --git a/test/jdk/jdk/incubator/vector/Float16VectorMaxTests.java b/test/jdk/jdk/incubator/vector/Float16VectorMaxTests.java index d8649c838ee..61efa3de9a0 100644 --- a/test/jdk/jdk/incubator/vector/Float16VectorMaxTests.java +++ b/test/jdk/jdk/incubator/vector/Float16VectorMaxTests.java @@ -1567,14 +1567,16 @@ public class Float16VectorMaxTests extends AbstractVectorTest { } static short cornerCaseValue(int i) { - return switch(i % 8) { + return switch(i % 10) { case 0 -> float16ToRawShortBits(Float16.MAX_VALUE); case 1 -> float16ToRawShortBits(Float16.MIN_VALUE); case 2 -> float16ToRawShortBits(Float16.NEGATIVE_INFINITY); case 3 -> float16ToRawShortBits(Float16.POSITIVE_INFINITY); case 4 -> float16ToRawShortBits(Float16.NaN); case 5 -> float16ToRawShortBits(shortBitsToFloat16((short)0x7FFA)); - case 6 -> float16ToShortBits(Float16.valueOf(0.0f)); + case 6 -> float16ToRawShortBits(shortBitsToFloat16((short)0x7c01)); // signaling NaN + case 7 -> float16ToRawShortBits(shortBitsToFloat16((short)0x7e00)); // quiet NaN + case 8 -> float16ToShortBits(Float16.valueOf(0.0f)); default -> float16ToShortBits(Float16.valueOf(-0.0f)); }; } @@ -5429,11 +5431,19 @@ public class Float16VectorMaxTests extends AbstractVectorTest { int hash = av.hashCode(); short subarr[] = Arrays.copyOfRange(a, i, i + SPECIES.length()); - int expectedHash = Objects.hash(SPECIES, Arrays.hashCode(subarr)); + int expectedHash = Objects.hash(SPECIES, Arrays.hashCode(toFloat16Array(subarr))); Assert.assertTrue(hash == expectedHash, "at index " + i + ", hash should be = " + expectedHash + ", but is = " + hash); } } + static Float16[] toFloat16Array(short[] bits) { + Float16[] a = new Float16[bits.length]; + for (int j = 0; j < bits.length; j++) { + a[j] = shortBitsToFloat16(bits[j]); + } + return a; + } + static long ADDReduceLong(short[] a, int idx) { short res = 0; diff --git a/test/jdk/jdk/incubator/vector/templates/Unit-Miscellaneous.template b/test/jdk/jdk/incubator/vector/templates/Unit-Miscellaneous.template index 8606b9ba598..0ae9342539f 100644 --- a/test/jdk/jdk/incubator/vector/templates/Unit-Miscellaneous.template +++ b/test/jdk/jdk/incubator/vector/templates/Unit-Miscellaneous.template @@ -100,11 +100,25 @@ int hash = av.hashCode(); $type$ subarr[] = Arrays.copyOfRange(a, i, i + SPECIES.length()); +#if[FP16] + int expectedHash = Objects.hash(SPECIES, Arrays.hashCode(toFloat16Array(subarr))); +#else[FP16] int expectedHash = Objects.hash(SPECIES, Arrays.hashCode(subarr)); +#end[FP16] Assert.assertTrue(hash == expectedHash, "at index " + i + ", hash should be = " + expectedHash + ", but is = " + hash); } } +#if[FP16] + static Float16[] toFloat16Array(short[] bits) { + Float16[] a = new Float16[bits.length]; + for (int j = 0; j < bits.length; j++) { + a[j] = shortBitsToFloat16(bits[j]); + } + return a; + } + +#end[FP16] #if[byte] @Test(dataProvider = "$type$UnaryOpProvider") static void reinterpretAsBytes$vectorteststype$SmokeTest(IntFunction<$type$[]> fa) { diff --git a/test/jdk/jdk/incubator/vector/templates/Unit-header.template b/test/jdk/jdk/incubator/vector/templates/Unit-header.template index eac7edbbb3f..7047e27b797 100644 --- a/test/jdk/jdk/incubator/vector/templates/Unit-header.template +++ b/test/jdk/jdk/incubator/vector/templates/Unit-header.template @@ -2013,14 +2013,16 @@ relativeError)); static $type$ cornerCaseValue(int i) { #if[FP] #if[FP16] - return switch(i % 8) { + return switch(i % 10) { case 0 -> float16ToRawShortBits($Wideboxtype$.MAX_VALUE); case 1 -> float16ToRawShortBits($Wideboxtype$.MIN_VALUE); case 2 -> float16ToRawShortBits($Wideboxtype$.NEGATIVE_INFINITY); case 3 -> float16ToRawShortBits($Wideboxtype$.POSITIVE_INFINITY); case 4 -> float16ToRawShortBits($Wideboxtype$.NaN); case 5 -> float16ToRawShortBits(shortBitsToFloat16((short)0x7FFA)); - case 6 -> float16ToShortBits(Float16.valueOf(0.0f)); + case 6 -> float16ToRawShortBits(shortBitsToFloat16((short)0x7c01)); // signaling NaN + case 7 -> float16ToRawShortBits(shortBitsToFloat16((short)0x7e00)); // quiet NaN + case 8 -> float16ToShortBits(Float16.valueOf(0.0f)); default -> float16ToShortBits(Float16.valueOf(-0.0f)); }; #else[FP16]