Status Update
Comments
ag...@google.com <ag...@google.com>
ag...@google.com <ag...@google.com> #2
Regarding the first issue, that is because NoosaScriptNoLighting
instances are only ever created by reflection in Script
I think. R8 does not know what a reflective new instance is instantiating. Therefore, it will assume that there are no instances of NoosaScriptNoLighting
and therefore, it will conclude that the shader()
method call in NoosaScript
can never hit the shader
method in the subclass as no instance ever exist of that.
The fix is to add an explicit keep rule for anything that is only instantiated by reflection. Something like:
-keep class com.watabou.noosa.NoosaScript
-keep class com.watabou.noosa.NoosaScriptNoLighting
should do it.
Regarding the second issue, I have only validated that the reset method is actually in the output and that it does assign a new empty array to the field. Not yet sure why that doesn't take effect.
ev...@gmail.com <ev...@gmail.com> #3
@2 Thank you, that's a good point regarding the script classes, but it's worth noting that it's the shader() method in NoosaScript that is giving incorrect behavior, not the one in NoosaScriptNoLighting. I just did a quick test and adding a pointless call to new NoosaScript() does fix the issue, adding in new NoosaScriptNoLighting() does not. Using the keep rule does fix the issue as well, of course.
There are also various other game elements that are only ever instantiated via reflection though. For example many of the game's items are only ever instantiated via reflection in
Does R8 only optimize away members of classes created exclusively via reflection in some cases?
ag...@google.com <ag...@google.com> #4
What is happening is that R8 determines that there is only one shader()
method that can possibly be hit at runtime. Therefore, it inlines that method at the call-site. Therefore, in the resulting APK there are no shader()
methods at all and the same shader string is always used. The keep rules tells R8 that both classes are instantiated, which let's R8 see that the shader()
method call has two possible targets, and therefore the methods remain and different strings are used for the two classes. I hope that makes sense.
In general, if things are only instantiated via reflection you typically need some kind of keep rule to tell R8 that this class will be instantiated at runtime. Otherwise, when we add further optimizations to R8, we might end up deducing that there can be no instances of these classes and remove things that are actually needed (like leaving static methods but removing instance methods as the instance methods can never be hit if there is no instance).
ag...@google.com <ag...@google.com> #5
The second issue definitely looks like a bug in R8. The reset
call seems to disappear from Dungeon.init
which is inline into InterlevelScene.descend
(which again is inline into an accessibility bridge). I'll continue looking into that. Thank you for the report!
ag...@google.com <ag...@google.com> #6
Alright, at least I know what the problem is now.
In the Dungeon.init()
method R8
decides to class inline the reset
call on the QuickSlot
instance as it thinks the QuickSlot
instance doesn't escape and therefore the side effects on the object is not visible elsewhere. That is of course incorrect as the QuickSlot
instance is used directly from other classes and methods as well.
The effect is that the reset
call is incorrectly removed. Stay tuned for a fix.
ev...@gmail.com <ev...@gmail.com> #7
@6 excellent, thank you very much for looking into the reset bug, and for the explanation regarding the script classes! I'll make adjustments to my app's R8 config and will keep that in mind when testing new R8 versions.
ze...@google.com <ze...@google.com> #8
ch...@google.com <ch...@google.com>
ap...@google.com <ap...@google.com> #9
Branch: master
commit 1a510fe321235d65c680f3dfb583363bb9232e4a
Author: Christoffer Quist Adamsen <christofferqa@google.com>
Date: Wed Aug 05 08:44:22 2020
Reproduce class inliner bug that incorrectly removed global side effect
Bug: 160942326
Change-Id: Ib4e21a73aa573f2ad37409b0b9f9244cbcdd801c
A src/test/java/com/android/tools/r8/ir/optimize/classinliner/StatefulSingletonClassInliningTest.java
ap...@google.com <ap...@google.com> #10
Branch: master
commit fd3c66fbfa4887338df323749dfd670ba6c04d43
Author: Christoffer Quist Adamsen <christofferqa@google.com>
Date: Wed Aug 05 15:34:24 2020
Fix class inliner error when singleton instance flows to static invoke
Bug: 160942326
Change-Id: If7ee1996c00f914c49e914fd3917c73ab48fedbb
M src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
M src/test/java/com/android/tools/r8/ir/optimize/classinliner/StatefulSingletonClassInliningWithStaticEscapeTest.java
ap...@google.com <ap...@google.com> #11
Branch: master
commit 1516a18804746723845d7e56a77949a63e45712e
Author: Christoffer Quist Adamsen <christofferqa@google.com>
Date: Wed Aug 05 15:32:21 2020
Reproduce class inliner bug when singleton instance follows to static invoke
Bug: 160942326
Change-Id: Iec506902672cd54c0d15a7bdc48fd307cb29b80c
A src/test/java/com/android/tools/r8/ir/optimize/classinliner/StatefulSingletonClassInliningWithStaticEscapeTest.java
ap...@google.com <ap...@google.com> #12
Branch: master
commit ed9b6e343c3cd1623ad3ea9fd3ffc15f8a54f353
Author: Mads Ager <ager@google.com>
Date: Fri Jul 17 10:17:05 2020
Restrict class inliner to maintain visible side-effects.
In the case of class inlining an object from a static field,
make sure that methods inlined do not modify the object state
as those updates could be visible to other accesses to the
object in the static field.
Bug: 160942326
Change-Id: I3088e56c8184a9b2e4a409e67284852b09ccc412
M src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerEligibilityInfo.java
M src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
M src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
ap...@google.com <ap...@google.com> #13
Branch: 2.1
commit 7b64b11a37a5e7dfb27d8970876e173dc651cc97
Author: Christoffer Quist Adamsen <christofferqa@google.com>
Date: Thu Aug 06 15:55:21 2020
Version 2.1.59
Cherry pick: Fix class inliner error when singleton instance flows to static invoke
CL:
Cherry pick: Reproduce class inliner bug when singleton instance follows to static invoke
CL:
Cherry pick: Reproduce class inliner bug that incorrectly removed global side effect
CL:
Cherry pick: Restrict class inliner to maintain visible side-effects.
CL:
Bug: 160942326
Change-Id: Ie4262245fa2e2c0feb14dab5d3192497b375a734
M src/main/java/com/android/tools/r8/Version.java
M src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerEligibilityInfo.java
M src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
M src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
A src/test/java/com/android/tools/r8/ir/optimize/classinliner/StatefulSingletonClassInliningTest.java
A src/test/java/com/android/tools/r8/ir/optimize/classinliner/StatefulSingletonClassInliningWithStaticEscapeTest.java
ga...@gmail.com <ga...@gmail.com> #14
I have similar issues on my app with R8 2.0.x
And I can confirm my issue is fixed since R8 2.1.59
Can google provide this fix on R8 2.0.x ?
ap...@google.com <ap...@google.com> #15
Branch: 2.0
commit 2023aebfb16cb062e7f1288bc3ff839348c37cd6
Author: Christoffer Quist Adamsen <christofferqa@google.com>
Date: Mon Aug 10 12:23:07 2020
Version 2.0.99
Cherry pick: Fix class inliner error when singleton instance flows to static invoke
CL:
Cherry pick: Reproduce class inliner bug when singleton instance follows to static invoke
CL:
Cherry pick: Reproduce class inliner bug that incorrectly removed global side effect
CL:
Cherry pick: Restrict class inliner to maintain visible side-effects.
CL:
Bug: 160942326
Change-Id: I3088e56c8184a9b2e4a409e67284852b09ccc412
M src/main/java/com/android/tools/r8/Version.java
M src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerEligibilityInfo.java
M src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
M src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
A src/test/java/com/android/tools/r8/ir/optimize/classinliner/StatefulSingletonClassInliningTest.java
A src/test/java/com/android/tools/r8/ir/optimize/classinliner/StatefulSingletonClassInliningWithStaticEscapeTest.java
ch...@google.com <ch...@google.com> #16
The fix is now also available on the 2.0 branch, starting from R8 version 2.0.99.
Description
Tested with issues present on R8 2.1.52, 2.0.14, and R8 version packaged with AGP 4.0.0. Tested with no issues on R8 1.6.90 and version packaged with AGP 3.6.3.
my game ( source code here ) encounters the following runtime issues when using R8 2.0+:
The game uses two openGL programs for performance reasons on very old android devices. One has color tinting in the fragment shader ( NoosaScript.java ) and one does not ( NoosaScriptNoLighting.java ). After R8 optimizing color tinting effects are disabled for all graphical objects, not just the ones using NoosaScriptNoLighting. From doing a bit of debugging it appears the shader() method in NoosaScript is being made equivalent to the shader() method in NoosaScriptNoLighting.
The game has a 'quickslot' system for players to be able to quickly use items without having to enter their inventory. The method QuickSlot.reset() clears out any items that are currently in the quickslots (such as when a new game is being started, or a game is being loaded). This method appears to do nothing after R8 optimizing, resulting in quickslot items persisting between games. Currently the method clears the quickslots by simply assigning their array object reference to a new array. Changing the method to instead manually set each array index to null fixes this runtime issue.
As both of these are runtime errors relating to the logic of my game, I'd be happy to provide more information or specific reproduction steps if needed.