Version 1.2.48.
Merge: Never lower const instruction past a cmp instruction on Lollipop.
CL: https://r8-review.googlesource.com/c/r8/+/27106
R=christofferqa@google.com
Bug: 115552239
Change-Id: Idce64af606a4befc63df03b9a459934b0183d6a1
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index ddbe2a1..c1d5255 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
// This field is accessed from release scripts using simple pattern matching.
// Therefore, changing this field could break our release scripts.
- public static final String LABEL = "1.2.47";
+ public static final String LABEL = "1.2.48";
private Version() {
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 748c078..d886120 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -1766,13 +1766,11 @@
InstructionListIterator insertAt = block.listIterator();
// Place the instruction as late in the block as we can. It needs to go before users
// and if we have catch handlers it needs to be placed before the throwing instruction.
- // Also, in an attempt to help Art VMs with broken bounds check elimination code,
- // we also do not lower the constants past array-length instructions in their
- // target block. See comment on options.canHaveBoundsCheckEliminationBug.
insertAt.nextUntil(i ->
i.inValues().contains(instruction.outValue())
- || i.isJumpInstruction()
- || (hasCatchHandlers && i.instructionTypeCanThrow()));
+ || i.isJumpInstruction()
+ || (hasCatchHandlers && i.instructionTypeCanThrow())
+ || (options.canHaveCmpIfFloatBug() && i.isCmp()));
Instruction next = insertAt.previous();
instruction.forceSetPosition(
next.isGoto() ? next.asGoto().getTarget().getPosition() : next.getPosition());
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 97b870c..c3a2298 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -591,6 +591,34 @@
return minApiLevel < AndroidApiLevel.L.getLevel();
}
+ // Some Lollipop VMs crash if there is a const instruction between a cmp and an if instruction.
+ //
+ // Crashing code:
+ //
+ // :goto_0
+ // cmpg-float v0, p0, p0
+ // const/4 v1, 0
+ // if-gez v0, :cond_0
+ // add-float/2addr p0, v1
+ // goto :goto_0
+ // :cond_0
+ // return p0
+ //
+ // Working code:
+ // :goto_0
+ // const/4 v1, 0
+ // cmpg-float v0, p0, p0
+ // if-gez v0, :cond_0
+ // add-float/2addr p0, v1
+ // goto :goto_0
+ // :cond_0
+ // return p0
+ //
+ // See b/115552239.
+ public boolean canHaveCmpIfFloatBug() {
+ return minApiLevel < AndroidApiLevel.M.getLevel();
+ }
+
// Some Lollipop VMs incorrectly optimize code with mul2addr instructions. In particular,
// the following hash code method produces wrong results after optimizations:
//
diff --git a/src/test/java/com/android/tools/r8/regress/b115552239/B115552239.java b/src/test/java/com/android/tools/r8/regress/b115552239/B115552239.java
new file mode 100644
index 0000000..80b7954
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b115552239/B115552239.java
@@ -0,0 +1,92 @@
+// 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.regress.b115552239;
+
+import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static junit.framework.TestCase.assertFalse;
+import static junit.framework.TestCase.assertTrue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.code.CmpgFloat;
+import com.android.tools.r8.code.IfGez;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+
+class TestClass {
+ public float f(float d) {
+ while (d < 0f) {
+ d += 360f;
+ }
+ return d % 360f;
+ }
+}
+
+public class B115552239 {
+
+ private MethodSubject compileTestClassAndGetMethod(int apiLevel)
+ throws IOException, CompilationFailedException, ExecutionException {
+ AndroidApp app =
+ ToolHelper.runD8(
+ D8Command.builder()
+ .addClassProgramData(ToolHelper.getClassAsBytes(TestClass.class), Origin.unknown())
+ .setMinApiLevel(apiLevel));
+ DexInspector inspector = new DexInspector(app);
+ ClassSubject clazz = inspector.clazz(TestClass.class);
+ assertThat(clazz, isPresent());
+ MethodSubject method = clazz.method("float", "f", ImmutableList.of("float"));
+ assertThat(method, isPresent());
+ return method;
+ }
+
+ @Test
+ public void noLowering()
+ throws IOException, CompilationFailedException, ExecutionException {
+ MethodSubject method = compileTestClassAndGetMethod(AndroidApiLevel.L.getLevel());
+ boolean previousWasCmp = false;
+ Instruction[] instructions = method.getMethod().getCode().asDexCode().instructions;
+ assertTrue(Arrays.stream(instructions).anyMatch(i -> i instanceof CmpgFloat));
+ for (Instruction instruction : instructions) {
+ if (instruction instanceof CmpgFloat) {
+ previousWasCmp = true;
+ continue;
+ } else if (previousWasCmp) {
+ assertTrue(instruction instanceof IfGez);
+ }
+ previousWasCmp = false;
+ }
+ }
+
+ @Test
+ public void lowering()
+ throws IOException, CompilationFailedException, ExecutionException {
+ MethodSubject method = compileTestClassAndGetMethod(AndroidApiLevel.M.getLevel());
+ boolean previousWasCmp = false;
+ Instruction[] instructions = method.getMethod().getCode().asDexCode().instructions;
+ assertTrue(Arrays.stream(instructions).anyMatch(i -> i instanceof CmpgFloat));
+ for (Instruction instruction : instructions) {
+ if (instruction instanceof CmpgFloat) {
+ previousWasCmp = true;
+ continue;
+ } else if (previousWasCmp) {
+ // We lowered the const instruction as close to its use as possible.
+ assertFalse(instruction instanceof IfGez);
+ }
+ previousWasCmp = false;
+ }
+ }
+}