Status Update
Comments
ag...@google.com <ag...@google.com> #2
Thanks for the report! This sounds like there is a bug in the JIT compiler on this device. We have seen that type of issue before on MediaTek CPUs. We will investigate and see what we can do to workaround the issue.
ag...@google.com <ag...@google.com>
sg...@google.com <sg...@google.com> #3
Seems like the issue is that when the code is jitted, a cmp-long instruction where the result overlaps with one of the arguments is not handled correctly. The code for org.joda.time.field.BaseDurationField.compareTo(org.joda.time.DurationField) looks like this:
int org.joda.time.field.BaseDurationField.compareTo(org.joda.time.DurationField)
registers: 4, inputs: 2, outputs: 1
------------------------------------------------------------
inst# offset instruction arguments
------------------------------------------------------------
0x00, line 146, locals: [2 -> this]
0: 0x00: InvokeVirtual { v3 } Lorg/joda/time/DurationField;->getUnitMillis()J
1: 0x03: MoveResultWide v0
0x04, line 147, locals: [2 -> this]
2: 0x04: InvokeVirtual { v2 } Lorg/joda/time/field/BaseDurationField;->getUnitMillis()J
3: 0x07: MoveResultWide v2
4: 0x08: CmpLong v2, v2, v0
5: 0x0a: IfNez v2, 0x0e (+4)
6: 0x0c: Const4 v2, 0x0 (0)
7: 0x0d: Return v2
8: 0x0e: IfGez v2, 0x12 (+4)
9: 0x10: Const4 v2, 0xffffffff (-1)
10: 0x11: Return v2
11: 0x12: Const4 v2, 0x1 (1)
12: 0x13: Return v2
This is perfectly valid dex code, but "CmpLong v2, v2, v0" seems to cause issues on the devices.
Tracing what this returns when the code is jitted shows that comparing the same two instances change from returning -1 to 1 which cause the sorting of the iSavedFields in DateTimeParserBucket to change breaking the conversion and materializing as the failure reported.
With the fix inhttps://r8-review.googlesource.com/c/r8/+/17600 the code instead looks like below, and that seems to work. At least the provided example no longer breaks.
int org.joda.time.field.BaseDurationField.compareTo(org.joda.time.DurationField)
registers: 6, inputs: 2, outputs: 1
------------------------------------------------------------
inst# offset instruction arguments
------------------------------------------------------------
0x00, line 146, locals: [4 -> this]
0: 0x00: InvokeVirtual { v5 } Lorg/joda/time/DurationField;->getUnitMillis()J
1: 0x03: MoveResultWide v0
0x04, line 147, locals: [4 -> this]
2: 0x04: InvokeVirtual { v4 } Lorg/joda/time/field/BaseDurationField;->getUnitMillis()J
3: 0x07: MoveResultWide v2
4: 0x08: CmpLong v5, v2, v0
5: 0x0a: IfNez v5, 0x0e (+4)
6: 0x0c: Const4 v5, 0x0 (0)
7: 0x0d: Return v5
8: 0x0e: IfGez v5, 0x12 (+4)
9: 0x10: Const4 v5, 0xffffffff (-1)
10: 0x11: Return v5
11: 0x12: Const4 v5, 0x1 (1)
12: 0x13: Return v5
int org.joda.time.field.BaseDurationField.compareTo(org.joda.time.DurationField)
registers: 4, inputs: 2, outputs: 1
------------------------------------------------------------
inst# offset instruction arguments
------------------------------------------------------------
0x00, line 146, locals: [2 -> this]
0: 0x00: InvokeVirtual { v3 } Lorg/joda/time/DurationField;->getUnitMillis()J
1: 0x03: MoveResultWide v0
0x04, line 147, locals: [2 -> this]
2: 0x04: InvokeVirtual { v2 } Lorg/joda/time/field/BaseDurationField;->getUnitMillis()J
3: 0x07: MoveResultWide v2
4: 0x08: CmpLong v2, v2, v0
5: 0x0a: IfNez v2, 0x0e (+4)
6: 0x0c: Const4 v2, 0x0 (0)
7: 0x0d: Return v2
8: 0x0e: IfGez v2, 0x12 (+4)
9: 0x10: Const4 v2, 0xffffffff (-1)
10: 0x11: Return v2
11: 0x12: Const4 v2, 0x1 (1)
12: 0x13: Return v2
This is perfectly valid dex code, but "CmpLong v2, v2, v0" seems to cause issues on the devices.
Tracing what this returns when the code is jitted shows that comparing the same two instances change from returning -1 to 1 which cause the sorting of the iSavedFields in DateTimeParserBucket to change breaking the conversion and materializing as the failure reported.
With the fix in
int org.joda.time.field.BaseDurationField.compareTo(org.joda.time.DurationField)
registers: 6, inputs: 2, outputs: 1
------------------------------------------------------------
inst# offset instruction arguments
------------------------------------------------------------
0x00, line 146, locals: [4 -> this]
0: 0x00: InvokeVirtual { v5 } Lorg/joda/time/DurationField;->getUnitMillis()J
1: 0x03: MoveResultWide v0
0x04, line 147, locals: [4 -> this]
2: 0x04: InvokeVirtual { v4 } Lorg/joda/time/field/BaseDurationField;->getUnitMillis()J
3: 0x07: MoveResultWide v2
4: 0x08: CmpLong v5, v2, v0
5: 0x0a: IfNez v5, 0x0e (+4)
6: 0x0c: Const4 v5, 0x0 (0)
7: 0x0d: Return v5
8: 0x0e: IfGez v5, 0x12 (+4)
9: 0x10: Const4 v5, 0xffffffff (-1)
10: 0x11: Return v5
11: 0x12: Const4 v5, 0x1 (1)
12: 0x13: Return v5
sg...@google.com <sg...@google.com> #4
Turns out that the working generated code above was caused by the change https://r8-review.googlesource.com/c/r8/+/17201 kicking in in this case (I was also surprised that the register count went from 4 to 6). This happened because moving from the R8 in Android Studio 3.1 Beta 4 directly to ToT R8. Hwoever with https://r8-review.googlesource.com/c/r8/+/17201 turned off and https://r8-review.googlesource.com/c/r8/+/17600 turned on the code below is generated, and with that code the issue is also fixed. And now the code goes from using 4 to 5 registers.
int org.joda.time.field.BaseDurationField.compareTo(org.joda.time.DurationField)
registers: 5, inputs: 2, outputs: 1
------------------------------------------------------------
inst# offset instruction arguments
------------------------------------------------------------
0x00, line 146, locals: [3 -> this]
0: 0x00: InvokeVirtual { v4 } Lorg/joda/time/DurationField;->getUnitMillis()J
1: 0x03: MoveResultWide v0
0x04, line 147, locals: [3 -> this]
2: 0x04: InvokeVirtual { v3 } Lorg/joda/time/field/BaseDurationField;->getUnitMillis()J
3: 0x07: MoveResultWide v3
4: 0x08: CmpLong v2, v3, v0
5: 0x0a: IfNez v2, 0x0e (+4)
6: 0x0c: Const4 v3, 0x0 (0)
7: 0x0d: Return v3
8: 0x0e: IfGez v2, 0x12 (+4)
9: 0x10: Const4 v3, 0xffffffff (-1)
10: 0x11: Return v3
11: 0x12: Const4 v3, 0x1 (1)
12: 0x13: Return v3
int org.joda.time.field.BaseDurationField.compareTo(org.joda.time.DurationField)
registers: 5, inputs: 2, outputs: 1
------------------------------------------------------------
inst# offset instruction arguments
------------------------------------------------------------
0x00, line 146, locals: [3 -> this]
0: 0x00: InvokeVirtual { v4 } Lorg/joda/time/DurationField;->getUnitMillis()J
1: 0x03: MoveResultWide v0
0x04, line 147, locals: [3 -> this]
2: 0x04: InvokeVirtual { v3 } Lorg/joda/time/field/BaseDurationField;->getUnitMillis()J
3: 0x07: MoveResultWide v3
4: 0x08: CmpLong v2, v3, v0
5: 0x0a: IfNez v2, 0x0e (+4)
6: 0x0c: Const4 v3, 0x0 (0)
7: 0x0d: Return v3
8: 0x0e: IfGez v2, 0x12 (+4)
9: 0x10: Const4 v3, 0xffffffff (-1)
10: 0x11: Return v3
11: 0x12: Const4 v3, 0x1 (1)
12: 0x13: Return v3
ap...@google.com <ap...@google.com> #5
Project: r8
Branch: master
commit 818c7aa6430961a92e2ccdb1e54b58ad590d872b
Author: Søren Gjesse <sgjesse@google.com>
Date: Tue Mar 06 12:12:28 2018
Extend the check for instructions taking long operands overlapping their result
Support for not overlapping the result of cmp-long with its operands and
extend the current check for add-long, sub-long, or-long, and-long and xor-long
to disallow any overlap between operands and result registers.
All these check now only apply on all versions of dalvik.
Bug: 70909581
Bug: 74084493
Change-Id: I49a4903cc1d707f0ab92ff2a3e018778084b7c18
M src/main/java/com/android/tools/r8/ir/code/Add.java
M src/main/java/com/android/tools/r8/ir/code/And.java
M src/main/java/com/android/tools/r8/ir/code/Or.java
M src/main/java/com/android/tools/r8/ir/code/Sub.java
M src/main/java/com/android/tools/r8/ir/code/Xor.java
M src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
M src/main/java/com/android/tools/r8/utils/InternalOptions.java
M src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
https://r8-review.googlesource.com/17600
Branch: master
commit 818c7aa6430961a92e2ccdb1e54b58ad590d872b
Author: Søren Gjesse <sgjesse@google.com>
Date: Tue Mar 06 12:12:28 2018
Extend the check for instructions taking long operands overlapping their result
Support for not overlapping the result of cmp-long with its operands and
extend the current check for add-long, sub-long, or-long, and-long and xor-long
to disallow any overlap between operands and result registers.
All these check now only apply on all versions of dalvik.
Bug: 70909581
Bug: 74084493
Change-Id: I49a4903cc1d707f0ab92ff2a3e018778084b7c18
M src/main/java/com/android/tools/r8/ir/code/Add.java
M src/main/java/com/android/tools/r8/ir/code/And.java
M src/main/java/com/android/tools/r8/ir/code/Or.java
M src/main/java/com/android/tools/r8/ir/code/Sub.java
M src/main/java/com/android/tools/r8/ir/code/Xor.java
M src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
M src/main/java/com/android/tools/r8/utils/InternalOptions.java
M src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
st...@gmail.com <st...@gmail.com> #6
Thanks for the quick resolution!
I'm trying to test the fix, but I'm still experiencing the same issue on the test project.
I built D8 as described onhttps://r8.googlesource.com/r8 and used the resulting d8.jar as described on https://issuetracker.google.com/issues/73787793#comment15 .
Am I doing something wrong?
I'm trying to test the fix, but I'm still experiencing the same issue on the test project.
I built D8 as described on
Am I doing something wrong?
ag...@google.com <ag...@google.com> #7
Let's try to figure out what is going on. I just built a tip of tree version of D8 and used it in android-studio and cannot reproduce the problem on this test case.
The version of D8 that I built prints out a line for each method that it compiles and prints out 'WORKAROUND' whenever it avoids a register pair that could trigger this bug in the VM. I have attached that D8 jar.
In android studio I change project level build.gradle to contain:
dependencies {
classpath files('/usr/local/google/home/ager/ssd/r8/build/libs/d8.jar') // UPDATE THIS
classpath 'com.android.tools.build:gradle:3.2.0-alpha04'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
I then sync the project, by pressing the 'Sync Now' button in the bar at the top of studio.
Then build the release apk. The build output is spammed with lines such as:
Converting: org.joda.time.DateTime org.joda.time.DateTime.withTimeAtStartOfDay()
Converting: org.joda.time.DateMidnight org.joda.time.DateMidnight.now()
Converting: org.joda.time.Seconds org.joda.time.Days.toStandardSeconds()
Converting: org.joda.time.DateTime org.joda.time.DateTime.withFields(org.joda.time.ReadablePartial)
WORKAROUND
WORKAROUND
Then I create signed apk and install.
Could you try out these steps with the d8.jar attached here, Mike? If you get all this extra output in the build output you are using the right D8 and hopefully the app will work.
The version of D8 that I built prints out a line for each method that it compiles and prints out 'WORKAROUND' whenever it avoids a register pair that could trigger this bug in the VM. I have attached that D8 jar.
In android studio I change project level build.gradle to contain:
dependencies {
classpath files('/usr/local/google/home/ager/ssd/r8/build/libs/d8.jar') // UPDATE THIS
classpath 'com.android.tools.build:gradle:3.2.0-alpha04'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
I then sync the project, by pressing the 'Sync Now' button in the bar at the top of studio.
Then build the release apk. The build output is spammed with lines such as:
Converting: org.joda.time.DateTime org.joda.time.DateTime.withTimeAtStartOfDay()
Converting: org.joda.time.DateMidnight org.joda.time.DateMidnight.now()
Converting: org.joda.time.Seconds org.joda.time.Days.toStandardSeconds()
Converting: org.joda.time.DateTime org.joda.time.DateTime.withFields(org.joda.time.ReadablePartial)
WORKAROUND
WORKAROUND
Then I create signed apk and install.
Could you try out these steps with the d8.jar attached here, Mike? If you get all this extra output in the build output you are using the right D8 and hopefully the app will work.
st...@gmail.com <st...@gmail.com> #8
Apparently I had to do "gradle cleanBuildCache" ("gradle clean" wasn't enough).
I can confirm that the issue has been fixed on all of my affected devices!
I can confirm that the issue has been fixed on all of my affected devices!
ag...@google.com <ag...@google.com> #9
Awesome! Thank you so much both for reporting this awesome small reproduction of the issue and also for verifying the fix! :)
Description
Check the attached screenshot for a better explanation of what I mean. As you can see, the first few calls to parseDateTime() return a correct result, but after a while, the time of the original string is parsed as 00:00:00 (red lines).
This only happens on release builds with android.enableD8=true. It does not happen on debug D8 builds, or on release builds with enableD8=false. I have only managed to reproduce this on 4.4.2 devices.
I'm attaching a small project which reproduces this issue (make sure to compile a release build). Press the "Parse strings" button, and scroll down the TextView. If you see any red lines (like the ones in the screenshot), then your device is affected by the bug.
I'm also attaching an adb bugreport from a device (Lenovo Tab 2 A7-10F) which is affected by this bug.