Add double_to_float conversion to value propagation.

Bug: b/465094702
Change-Id: I923239e146b4ba08d6f2d9a38952e0f49d6f6471
diff --git a/src/main/java/com/android/tools/r8/ir/code/NumberConversion.java b/src/main/java/com/android/tools/r8/ir/code/NumberConversion.java
index d16ecb9..5e24100 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NumberConversion.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NumberConversion.java
@@ -207,6 +207,9 @@
       case DOUBLE_TO_LONG:
         long rawDoubleToLongValue = (long) num.getDoubleValue();
         return valueFactory.createSingleNumberValue(rawDoubleToLongValue, typeElement);
+      case DOUBLE_TO_FLOAT:
+        long rawDoubleToFloatValue = LongUtils.encodeFloat((float) num.getDoubleValue());
+        return valueFactory.createSingleNumberValue(rawDoubleToFloatValue, typeElement);
       default:
         return super.getAbstractValue(appView, context, abstractValueSupplier);
     }
diff --git a/src/test/java/com/android/tools/r8/optimize/numberconversion/DoubleToFloatTest.java b/src/test/java/com/android/tools/r8/optimize/numberconversion/DoubleToFloatTest.java
new file mode 100644
index 0000000..458428d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/optimize/numberconversion/DoubleToFloatTest.java
@@ -0,0 +1,86 @@
+// Copyright (c) 2025, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.optimize.numberconversion;
+
+import static org.junit.Assume.assumeFalse;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ir.code.NumericType;
+import com.android.tools.r8.utils.LongUtils;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class DoubleToFloatTest extends NumberConversionTestBase {
+
+  @Parameterized.Parameter(0)
+  public double input;
+
+  @Parameterized.Parameter(1)
+  public TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}, {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        new Double[] {
+          128.12D,
+          65408.61623D,
+          -65408.61623D,
+          32768.574D,
+          -32768.574D,
+          42D,
+          -32D,
+          0D,
+          -0D,
+          (double) Integer.MAX_VALUE,
+          (double) Integer.MIN_VALUE,
+          Double.MAX_VALUE,
+          Double.MIN_VALUE,
+          -Double.MAX_VALUE,
+          -Double.MIN_VALUE,
+          Double.NaN,
+          Double.longBitsToDouble(0xFFFFFFFFFFFFFFFFL), // Non-canonical NaN
+          Double.longBitsToDouble(0xFFFFF0000000000FL), // Non-canonical NaN
+          Double.longBitsToDouble(0x7F90000000000000L), // Non-canonical NaN
+          Double.longBitsToDouble(0x7FBFFFFFFFFFFFFFL), // Non-canonical NaN
+          Double.POSITIVE_INFINITY,
+          Double.NEGATIVE_INFINITY
+        },
+        TestParameters.builder().withNoneRuntime().build());
+  }
+
+  @Test
+  public void testBitwise() throws Exception {
+    // Check that input is bitwise preserved.
+    testConversion(
+        mv -> {
+          mv.visitLdcInsn(input);
+          mv.visitInsn(Opcodes.D2F);
+        },
+        NumericType.FLOAT,
+        LongUtils.encodeFloat((float) input));
+  }
+
+  @Test
+  public void testCompare() throws Exception {
+    // FCMPG returns 1 if any of the inputs are NaN.
+    assumeFalse(Double.isNaN(input));
+    // Comparison is used to verify the internal state of constants rather than just the output
+    // instructions. This avoids the situation where the input is not truncated during
+    // optimization, but is still truncated in the final instructions because of typed extraction.
+    testConversion(
+        mv -> {
+          mv.visitLdcInsn(input);
+          mv.visitInsn(Opcodes.D2F);
+          mv.visitLdcInsn((float) input);
+          mv.visitInsn(Opcodes.FCMPG);
+        },
+        NumericType.INT,
+        0);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/optimize/numberconversion/FloatToDoubleTest.java b/src/test/java/com/android/tools/r8/optimize/numberconversion/FloatToDoubleTest.java
index bb7383a..df7ccaf 100644
--- a/src/test/java/com/android/tools/r8/optimize/numberconversion/FloatToDoubleTest.java
+++ b/src/test/java/com/android/tools/r8/optimize/numberconversion/FloatToDoubleTest.java
@@ -8,6 +8,7 @@
 
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ir.code.NumericType;
+import com.android.tools.r8.utils.LongUtils;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -62,13 +63,13 @@
           mv.visitInsn(Opcodes.F2D);
         },
         NumericType.DOUBLE,
-        Double.doubleToRawLongBits((double) input));
+        LongUtils.encodeDouble((double) input));
   }
 
   @Test
   public void testCompare() throws Exception {
     // DCMPG returns 1 if any of the inputs are NaN.
-    assumeFalse(Double.isNaN(input));
+    assumeFalse(Float.isNaN(input));
     // Comparison is used to verify the internal state of constants rather than just the output
     // instructions. This avoids the situation where the input is not truncated during
     // optimization, but is still truncated in the final instructions because of typed extraction.