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;
+ }
+}