8386255: Float16Vector NaN canonicalization for hashCode computation

Reviewed-by: psandoz, sherman
This commit is contained in:
Jatin Bhateja 2026-06-26 04:02:20 +00:00
parent a34314dd79
commit 8740fbb4ee
9 changed files with 116 additions and 19 deletions

View File

@ -2861,6 +2861,17 @@ public abstract sealed class Float16Vector extends AbstractVector<Float16>
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} <!--workaround-->
*/
@ForceInline
@ -3734,8 +3745,9 @@ public abstract sealed class Float16Vector extends AbstractVector<Float16>
@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()));
}
// ================================================

View File

@ -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} <!--workaround-->
@ -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]
}
// ================================================

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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) {

View File

@ -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]