CfConstNumber: Handle negative zero correctly

When `value` is float or double, `value == 0` is true even for
negative zero. The FCONST_0 and DCONST_0 opcodes output positive zero,
so they must be followed by FNEG or DNEG in the negative zero case.

An alternate fix would be handling negative zero with a LDC; however,
this always requires 3 bytes of opcode for double (LDC2_W takes 16-bit
index) and would sometimes require 3 bytes for float as well (depending
whether LDC or LDC_W is emitted). OTOH, ?CONST_0 + ?NEG is only 2 bytes.

Change-Id: I447339a0644abf50f2c0c84fc6ebf031ba92d555
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java b/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
index c3b4490..246cf0b 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
@@ -75,6 +75,9 @@
           float value = getFloatValue();
           if (value == 0 || value == 1 || value == 2) {
             visitor.visitInsn(Opcodes.FCONST_0 + (int) value);
+            if (isNegativeZeroFloat(value)) {
+              visitor.visitInsn(Opcodes.FNEG);
+            }
           } else {
             visitor.visitLdcInsn(value);
           }
@@ -85,6 +88,9 @@
           double value = getDoubleValue();
           if (value == 0 || value == 1) {
             visitor.visitInsn(Opcodes.DCONST_0 + (int) value);
+            if (isNegativeZeroDouble(value)) {
+              visitor.visitInsn(Opcodes.DNEG);
+            }
           } else {
             visitor.visitLdcInsn(value);
           }
@@ -95,6 +101,14 @@
     }
   }
 
+  private static boolean isNegativeZeroDouble(double value) {
+    return Double.doubleToLongBits(value) == Double.doubleToLongBits(-0.0);
+  }
+
+  private static boolean isNegativeZeroFloat(float value) {
+    return Float.floatToIntBits(value) == Float.floatToIntBits(-0.0f);
+  }
+
   @Override
   public void print(CfPrinter printer) {
     printer.print(this);
diff --git a/src/test/java/com/android/tools/r8/cf/NegativeZeroTest.java b/src/test/java/com/android/tools/r8/cf/NegativeZeroTest.java
new file mode 100644
index 0000000..e63e86d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/NegativeZeroTest.java
@@ -0,0 +1,20 @@
+// Copyright (c) 2018, 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.cf;
+
+public class NegativeZeroTest {
+
+  public static void main(String[] args) {
+    System.out.println(-0.0f);
+    System.out.println(Float.floatToIntBits(-0.0f));
+    System.out.println(Float.floatToIntBits(0.0f));
+    if (Float.floatToIntBits(-0.0f) == Float.floatToIntBits(0.0f)) {
+      throw new AssertionError("Negative float not preserved");
+    }
+    if (Double.doubleToLongBits(-0.0) == Double.doubleToLongBits(0.0)) {
+      throw new AssertionError("Negative double not preserved");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/NegativeZeroTestRunner.java b/src/test/java/com/android/tools/r8/cf/NegativeZeroTestRunner.java
new file mode 100644
index 0000000..7196120
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/NegativeZeroTestRunner.java
@@ -0,0 +1,34 @@
+// Copyright (c) 2018, 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.cf;
+
+import com.android.tools.r8.ClassFileConsumer.DirectoryConsumer;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.R8;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidAppConsumers;
+import java.nio.file.Path;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+public class NegativeZeroTestRunner {
+  static final Class CLASS = NegativeZeroTest.class;
+  @Rule public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+
+  @Test
+  public void test() throws Exception {
+    Path out = temp.getRoot().toPath();
+    R8.run(
+        R8Command.builder()
+            .setMode(CompilationMode.DEBUG)
+            .addClassProgramData(ToolHelper.getClassAsBytes(CLASS), Origin.unknown())
+            .addLibraryFiles(ToolHelper.getAndroidJar(ToolHelper.getMinApiLevelForDexVm()))
+            .setProgramConsumer(new DirectoryConsumer(out))
+            .build());
+    assert ToolHelper.runJava(out, CLASS.getCanonicalName()).exitCode == 0;
+  }
+}